src/gui/widgets/qscrollarea.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui/widgets/qscrollarea.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,522 @@
+/****************************************************************************
+**
+** 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 "qscrollarea.h"
+#include "private/qscrollarea_p.h"
+
+#ifndef QT_NO_SCROLLAREA
+
+#include "qscrollbar.h"
+#include "qlayout.h"
+#include "qstyle.h"
+#include "qapplication.h"
+#include "qvariant.h"
+#include "qdebug.h"
+#include "private/qlayoutengine_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+    \class QScrollArea
+
+    \brief The QScrollArea class provides a scrolling view onto
+    another widget.
+
+    \ingroup basicwidgets
+
+
+    A scroll area is used to display the contents of a child widget
+    within a frame. If the widget exceeds the size of the frame, the
+    view can provide scroll bars so that the entire area of the child
+    widget can be viewed. The child widget must be specified with
+    setWidget(). For example:
+
+    \snippet doc/src/snippets/code/src_gui_widgets_qscrollarea.cpp 0
+
+    The code above creates a scroll area (shown in the images below)
+    containing an image label. When scaling the image, the scroll area
+    can provide the necessary scroll bars:
+
+    \table
+    \row
+    \o \inlineimage qscrollarea-noscrollbars.png
+    \o \inlineimage qscrollarea-onescrollbar.png
+    \o \inlineimage qscrollarea-twoscrollbars.png
+    \endtable
+
+    The scroll bars appearance depends on the currently set \l
+    {Qt::ScrollBarPolicy}{scroll bar policies}. You can control the
+    appearance of the scroll bars using the inherited functionality
+    from QAbstractScrollArea.
+
+    For example, you can set the
+    QAbstractScrollArea::horizontalScrollBarPolicy and
+    QAbstractScrollArea::verticalScrollBarPolicy properties. Or if you
+    want the scroll bars to adjust dynamically when the contents of
+    the scroll area changes, you can use the \l
+    {QAbstractScrollArea::horizontalScrollBar()}{horizontalScrollBar()}
+    and \l
+    {QAbstractScrollArea::verticalScrollBar()}{verticalScrollBar()}
+    functions (which enable you to access the scroll bars) and set the
+    scroll bars' values whenever the scroll area's contents change,
+    using the QScrollBar::setValue() function.
+
+    You can retrieve the child widget using the widget() function. The
+    view can be made to be resizable with the setWidgetResizable()
+    function. The alignment of the widget can be specified with
+    setAlignment().
+
+    Two convenience functions ensureVisible() and
+    ensureWidgetVisible() ensure a certain region of the contents is
+    visible inside the viewport, by scrolling the contents if
+    necessary.
+
+    \section1 Size Hints and Layouts
+
+    When using a scroll area to display the contents of a custom
+    widget, it is important to ensure that the
+    \l{QWidget::sizeHint}{size hint} of the child widget is set to a
+    suitable value. If a standard QWidget is used for the child
+    widget, it may be necessary to call QWidget::setMinimumSize() to
+    ensure that the contents of the widget are shown correctly within
+    the scroll area.
+
+    If a scroll area is used to display the contents of a widget that
+    contains child widgets arranged in a layout, it is important to
+    realise that the size policy of the layout will also determine the
+    size of the widget. This is especially useful to know if you intend
+    to dynamically change the contents of the layout. In such cases,
+    setting the layout's \l{QLayout::sizeConstraint}{size constraint}
+    property to one which provides constraints on the minimum and/or
+    maximum size of the layout (e.g., QLayout::SetMinAndMaxSize) will
+    cause the size of the scroll area to be updated whenever the
+    contents of the layout changes.
+
+    For a complete example using the QScrollArea class, see the \l
+    {widgets/imageviewer}{Image Viewer} example. The example shows how
+    to combine QLabel and QScrollArea to display an image.
+
+    \sa QAbstractScrollArea, QScrollBar, {Image Viewer Example}
+*/
+
+
+/*!
+    Constructs an empty scroll area with the given \a parent.
+
+    \sa setWidget()
+*/
+QScrollArea::QScrollArea(QWidget *parent)
+    : QAbstractScrollArea(*new QScrollAreaPrivate,parent)
+{
+    Q_D(QScrollArea);
+    d->viewport->setBackgroundRole(QPalette::NoRole);
+    d->vbar->setSingleStep(20);
+    d->hbar->setSingleStep(20);
+    d->layoutChildren();
+}
+
+/*!
+    \internal
+*/
+QScrollArea::QScrollArea(QScrollAreaPrivate &dd, QWidget *parent)
+    : QAbstractScrollArea(dd, parent)
+{
+    Q_D(QScrollArea);
+    d->viewport->setBackgroundRole(QPalette::NoRole);
+    d->vbar->setSingleStep(20);
+    d->hbar->setSingleStep(20);
+    d->layoutChildren();
+}
+
+/*!
+    Destroys the scroll area and its child widget.
+
+    \sa setWidget()
+*/
+QScrollArea::~QScrollArea()
+{
+}
+
+void QScrollAreaPrivate::updateWidgetPosition()
+{
+    Q_Q(QScrollArea);
+    Qt::LayoutDirection dir = q->layoutDirection();
+    QRect scrolled = QStyle::visualRect(dir, viewport->rect(), QRect(QPoint(-hbar->value(), -vbar->value()), widget->size()));
+    QRect aligned = QStyle::alignedRect(dir, alignment, widget->size(), viewport->rect());
+    widget->move(widget->width() < viewport->width() ? aligned.x() : scrolled.x(),
+                 widget->height() < viewport->height() ? aligned.y() : scrolled.y());
+}
+
+void QScrollAreaPrivate::updateScrollBars()
+{
+    Q_Q(QScrollArea);
+    if (!widget)
+        return;
+    QSize p = viewport->size();
+    QSize m = q->maximumViewportSize();
+
+    QSize min = qSmartMinSize(widget);
+    QSize max = qSmartMaxSize(widget);
+
+    if (resizable) {
+        if ((widget->layout() ? widget->layout()->hasHeightForWidth() : widget->sizePolicy().hasHeightForWidth())) {
+            QSize p_hfw = p.expandedTo(min).boundedTo(max);
+            int h = widget->heightForWidth( p_hfw.width() );
+            min = QSize(p_hfw.width(), qMax(p_hfw.height(), h));
+        }
+    }
+
+    if ((resizable && m.expandedTo(min) == m && m.boundedTo(max) == m)
+        || (!resizable && m.expandedTo(widget->size()) == m))
+        p = m; // no scroll bars needed
+
+    if (resizable)
+        widget->resize(p.expandedTo(min).boundedTo(max));
+    QSize v = widget->size();
+
+    hbar->setRange(0, v.width() - p.width());
+    hbar->setPageStep(p.width());
+    vbar->setRange(0, v.height() - p.height());
+    vbar->setPageStep(p.height());
+    updateWidgetPosition();
+
+}
+
+/*!
+    Returns the scroll area's widget, or 0 if there is none.
+
+    \sa setWidget()
+*/
+
+QWidget *QScrollArea::widget() const
+{
+    Q_D(const QScrollArea);
+    return d->widget;
+}
+
+/*!
+    \fn void QScrollArea::setWidget(QWidget *widget)
+
+    Sets the scroll area's \a widget.
+
+    The \a widget becomes a child of the scroll area, and will be
+    destroyed when the scroll area is deleted or when a new widget is
+    set.
+    
+    The widget's \l{QWidget::setAutoFillBackground()}{autoFillBackground}
+    property will be set to \c{true}.
+
+    If the scroll area is visible when the \a widget is
+    added, you must \l{QWidget::}{show()} it explicitly.
+
+    Note that You must add the layout of \a widget before you call
+    this function; if you add it later, the \a widget will not be
+    visible - regardless of when you \l{QWidget::}{show()} the scroll
+    area. In this case, you can also not \l{QWidget::}{show()} the \a
+    widget later.
+
+    \sa widget()
+*/
+void QScrollArea::setWidget(QWidget *widget)
+{
+    Q_D(QScrollArea);
+    if (widget == d->widget || !widget)
+        return;
+
+    delete d->widget;
+    d->widget = 0;
+    d->hbar->setValue(0);
+    d->vbar->setValue(0);
+    if (widget->parentWidget() != d->viewport)
+        widget->setParent(d->viewport);
+    if (!widget->testAttribute(Qt::WA_Resized))
+        widget->resize(widget->sizeHint());
+    d->widget = widget;
+    d->widget->setAutoFillBackground(true);
+    widget->installEventFilter(this);
+    d->widgetSize = QSize();
+    d->updateScrollBars();
+    d->widget->show();
+
+}
+
+/*!
+    Removes the scroll area's widget, and passes ownership of the
+    widget to the caller.
+
+    \sa widget()
+ */
+QWidget *QScrollArea::takeWidget()
+{
+    Q_D(QScrollArea);
+    QWidget *w = d->widget;
+    d->widget = 0;
+    if (w)
+        w->setParent(0);
+    return w;
+}
+
+/*!
+    \reimp
+ */
+bool QScrollArea::event(QEvent *e)
+{
+    Q_D(QScrollArea);
+    if (e->type() == QEvent::StyleChange || e->type() == QEvent::LayoutRequest) {
+        d->updateScrollBars();
+    }
+#ifdef QT_KEYPAD_NAVIGATION
+    else if (QApplication::keypadNavigationEnabled()) {
+        if (e->type() == QEvent::Show)
+            QApplication::instance()->installEventFilter(this);
+        else if (e->type() == QEvent::Hide)
+            QApplication::instance()->removeEventFilter(this);
+    }
+#endif
+    return QAbstractScrollArea::event(e);
+}
+
+
+/*!
+    \reimp
+ */
+bool QScrollArea::eventFilter(QObject *o, QEvent *e)
+{
+    Q_D(QScrollArea);
+#ifdef QT_KEYPAD_NAVIGATION
+    if (d->widget && o != d->widget && e->type() == QEvent::FocusIn
+            && QApplication::keypadNavigationEnabled()) {
+        if (o->isWidgetType())
+            ensureWidgetVisible(static_cast<QWidget *>(o));
+    }
+#endif
+    if (o == d->widget && e->type() == QEvent::Resize)
+        d->updateScrollBars();
+
+    return false;
+}
+
+/*!
+    \reimp
+ */
+void QScrollArea::resizeEvent(QResizeEvent *)
+{
+    Q_D(QScrollArea);
+    d->updateScrollBars();
+
+}
+
+
+/*!\reimp
+ */
+void QScrollArea::scrollContentsBy(int, int)
+{
+    Q_D(QScrollArea);
+    if (!d->widget)
+        return;
+    d->updateWidgetPosition();
+}
+
+
+/*!
+    \property QScrollArea::widgetResizable
+    \brief whether the scroll area should resize the view widget
+
+    If this property is set to false (the default), the scroll area
+    honors the size of its widget. Regardless of this property, you
+    can programmatically resize the widget using widget()->resize(),
+    and the scroll area will automatically adjust itself to the new
+    size.
+
+    If this property is set to true, the scroll area will
+    automatically resize the widget in order to avoid scroll bars
+    where they can be avoided, or to take advantage of extra space.
+*/
+bool QScrollArea::widgetResizable() const
+{
+    Q_D(const QScrollArea);
+    return d->resizable;
+}
+
+void QScrollArea::setWidgetResizable(bool resizable)
+{
+    Q_D(QScrollArea);
+    d->resizable = resizable;
+    updateGeometry();
+    d->updateScrollBars();
+}
+
+/*!
+    \reimp
+ */
+QSize QScrollArea::sizeHint() const
+{
+    Q_D(const QScrollArea);
+    int f = 2 * d->frameWidth;
+    QSize sz(f, f);
+    int h = fontMetrics().height();
+    if (d->widget) {
+        if (!d->widgetSize.isValid())
+            d->widgetSize = d->resizable ? d->widget->sizeHint() : d->widget->size();
+        sz += d->widgetSize;
+    } else {
+        sz += QSize(12 * h, 8 * h);
+    }
+    if (d->vbarpolicy == Qt::ScrollBarAlwaysOn)
+        sz.setWidth(sz.width() + d->vbar->sizeHint().width());
+    if (d->hbarpolicy == Qt::ScrollBarAlwaysOn)
+        sz.setHeight(sz.height() + d->hbar->sizeHint().height());
+    return sz.boundedTo(QSize(36 * h, 24 * h));
+}
+
+
+
+/*!
+    \reimp
+ */
+bool QScrollArea::focusNextPrevChild(bool next)
+{
+    if (QWidget::focusNextPrevChild(next)) {
+        if (QWidget *fw = focusWidget())
+            ensureWidgetVisible(fw);
+        return true;
+    }
+    return false;
+}
+
+/*!
+    Scrolls the contents of the scroll area so that the point (\a x, \a y) is visible
+    inside the region of the viewport with margins specified in pixels by \a xmargin and
+    \a ymargin. If the specified point cannot be reached, the contents are scrolled to
+    the nearest valid position. The default value for both margins is 50 pixels.
+*/
+void QScrollArea::ensureVisible(int x, int y, int xmargin, int ymargin)
+{
+    Q_D(QScrollArea);
+
+    int logicalX = QStyle::visualPos(layoutDirection(), d->viewport->rect(), QPoint(x, y)).x();
+
+    if (logicalX - xmargin < d->hbar->value()) {
+        d->hbar->setValue(qMax(0, logicalX - xmargin));
+    } else if (logicalX > d->hbar->value() + d->viewport->width() - xmargin) {
+        d->hbar->setValue(qMin(logicalX - d->viewport->width() + xmargin, d->hbar->maximum()));
+    }
+
+    if (y - ymargin < d->vbar->value()) {
+        d->vbar->setValue(qMax(0, y - ymargin));
+    } else if (y > d->vbar->value() + d->viewport->height() - ymargin) {
+        d->vbar->setValue(qMin(y - d->viewport->height() + ymargin, d->vbar->maximum()));
+    }
+}
+
+/*!
+    \since 4.2
+
+    Scrolls the contents of the scroll area so that the \a childWidget
+    of QScrollArea::widget() is visible inside the viewport with
+    margins specified in pixels by \a xmargin and \a ymargin. If the
+    specified point cannot be reached, the contents are scrolled to
+    the nearest valid position. The default value for both margins is
+    50 pixels.
+
+*/
+void QScrollArea::ensureWidgetVisible(QWidget *childWidget, int xmargin, int ymargin)
+{
+    Q_D(QScrollArea);
+
+    if (!d->widget->isAncestorOf(childWidget))
+        return;
+
+    const QRect microFocus = childWidget->inputMethodQuery(Qt::ImMicroFocus).toRect();
+    const QRect defaultMicroFocus =
+        childWidget->QWidget::inputMethodQuery(Qt::ImMicroFocus).toRect();
+    QRect focusRect = (microFocus != defaultMicroFocus)
+        ? QRect(childWidget->mapTo(d->widget, microFocus.topLeft()), microFocus.size())
+        : QRect(childWidget->mapTo(d->widget, QPoint(0,0)), childWidget->size());
+    const QRect visibleRect(-d->widget->pos(), d->viewport->size());
+
+    if (visibleRect.contains(focusRect))
+        return;
+
+    focusRect.adjust(-xmargin, -ymargin, xmargin, ymargin);
+
+    if (focusRect.width() > visibleRect.width())
+        d->hbar->setValue(focusRect.center().x() - d->viewport->width() / 2);
+    else if (focusRect.right() > visibleRect.right())
+        d->hbar->setValue(focusRect.right() - d->viewport->width());
+    else
+        d->hbar->setValue(focusRect.left());
+
+    if (focusRect.height() > visibleRect.height())
+        d->vbar->setValue(focusRect.center().y() - d->viewport->height() / 2);
+    else if (focusRect.bottom() > visibleRect.bottom())
+        d->vbar->setValue(focusRect.bottom() - d->viewport->height());
+    else
+        d->vbar->setValue(focusRect.top());
+}
+
+
+/*!
+    \property QScrollArea::alignment
+    \brief the alignment of the scroll area's widget
+    \since 4.2
+
+    By default, the widget stays rooted to the top-left corner of the
+    scroll area.
+*/
+
+void QScrollArea::setAlignment(Qt::Alignment alignment)
+{
+    Q_D(QScrollArea);
+    d->alignment = alignment;
+    if (d->widget)
+        d->updateWidgetPosition();
+}
+
+Qt::Alignment QScrollArea::alignment() const
+{
+    Q_D(const QScrollArea);
+    return d->alignment;
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_SCROLLAREA