diff -r 000000000000 -r 1918ee327afb src/gui/widgets/qscrollarea.cpp --- /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(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