src/gui/widgets/qscrollarea.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the QtGui module of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include "qscrollarea.h"
       
    43 #include "private/qscrollarea_p.h"
       
    44 
       
    45 #ifndef QT_NO_SCROLLAREA
       
    46 
       
    47 #include "qscrollbar.h"
       
    48 #include "qlayout.h"
       
    49 #include "qstyle.h"
       
    50 #include "qapplication.h"
       
    51 #include "qvariant.h"
       
    52 #include "qdebug.h"
       
    53 #include "private/qlayoutengine_p.h"
       
    54 
       
    55 QT_BEGIN_NAMESPACE
       
    56 
       
    57 /*!
       
    58     \class QScrollArea
       
    59 
       
    60     \brief The QScrollArea class provides a scrolling view onto
       
    61     another widget.
       
    62 
       
    63     \ingroup basicwidgets
       
    64 
       
    65 
       
    66     A scroll area is used to display the contents of a child widget
       
    67     within a frame. If the widget exceeds the size of the frame, the
       
    68     view can provide scroll bars so that the entire area of the child
       
    69     widget can be viewed. The child widget must be specified with
       
    70     setWidget(). For example:
       
    71 
       
    72     \snippet doc/src/snippets/code/src_gui_widgets_qscrollarea.cpp 0
       
    73 
       
    74     The code above creates a scroll area (shown in the images below)
       
    75     containing an image label. When scaling the image, the scroll area
       
    76     can provide the necessary scroll bars:
       
    77 
       
    78     \table
       
    79     \row
       
    80     \o \inlineimage qscrollarea-noscrollbars.png
       
    81     \o \inlineimage qscrollarea-onescrollbar.png
       
    82     \o \inlineimage qscrollarea-twoscrollbars.png
       
    83     \endtable
       
    84 
       
    85     The scroll bars appearance depends on the currently set \l
       
    86     {Qt::ScrollBarPolicy}{scroll bar policies}. You can control the
       
    87     appearance of the scroll bars using the inherited functionality
       
    88     from QAbstractScrollArea.
       
    89 
       
    90     For example, you can set the
       
    91     QAbstractScrollArea::horizontalScrollBarPolicy and
       
    92     QAbstractScrollArea::verticalScrollBarPolicy properties. Or if you
       
    93     want the scroll bars to adjust dynamically when the contents of
       
    94     the scroll area changes, you can use the \l
       
    95     {QAbstractScrollArea::horizontalScrollBar()}{horizontalScrollBar()}
       
    96     and \l
       
    97     {QAbstractScrollArea::verticalScrollBar()}{verticalScrollBar()}
       
    98     functions (which enable you to access the scroll bars) and set the
       
    99     scroll bars' values whenever the scroll area's contents change,
       
   100     using the QScrollBar::setValue() function.
       
   101 
       
   102     You can retrieve the child widget using the widget() function. The
       
   103     view can be made to be resizable with the setWidgetResizable()
       
   104     function. The alignment of the widget can be specified with
       
   105     setAlignment().
       
   106 
       
   107     Two convenience functions ensureVisible() and
       
   108     ensureWidgetVisible() ensure a certain region of the contents is
       
   109     visible inside the viewport, by scrolling the contents if
       
   110     necessary.
       
   111 
       
   112     \section1 Size Hints and Layouts
       
   113 
       
   114     When using a scroll area to display the contents of a custom
       
   115     widget, it is important to ensure that the
       
   116     \l{QWidget::sizeHint}{size hint} of the child widget is set to a
       
   117     suitable value. If a standard QWidget is used for the child
       
   118     widget, it may be necessary to call QWidget::setMinimumSize() to
       
   119     ensure that the contents of the widget are shown correctly within
       
   120     the scroll area.
       
   121 
       
   122     If a scroll area is used to display the contents of a widget that
       
   123     contains child widgets arranged in a layout, it is important to
       
   124     realise that the size policy of the layout will also determine the
       
   125     size of the widget. This is especially useful to know if you intend
       
   126     to dynamically change the contents of the layout. In such cases,
       
   127     setting the layout's \l{QLayout::sizeConstraint}{size constraint}
       
   128     property to one which provides constraints on the minimum and/or
       
   129     maximum size of the layout (e.g., QLayout::SetMinAndMaxSize) will
       
   130     cause the size of the scroll area to be updated whenever the
       
   131     contents of the layout changes.
       
   132 
       
   133     For a complete example using the QScrollArea class, see the \l
       
   134     {widgets/imageviewer}{Image Viewer} example. The example shows how
       
   135     to combine QLabel and QScrollArea to display an image.
       
   136 
       
   137     \sa QAbstractScrollArea, QScrollBar, {Image Viewer Example}
       
   138 */
       
   139 
       
   140 
       
   141 /*!
       
   142     Constructs an empty scroll area with the given \a parent.
       
   143 
       
   144     \sa setWidget()
       
   145 */
       
   146 QScrollArea::QScrollArea(QWidget *parent)
       
   147     : QAbstractScrollArea(*new QScrollAreaPrivate,parent)
       
   148 {
       
   149     Q_D(QScrollArea);
       
   150     d->viewport->setBackgroundRole(QPalette::NoRole);
       
   151     d->vbar->setSingleStep(20);
       
   152     d->hbar->setSingleStep(20);
       
   153     d->layoutChildren();
       
   154 }
       
   155 
       
   156 /*!
       
   157     \internal
       
   158 */
       
   159 QScrollArea::QScrollArea(QScrollAreaPrivate &dd, QWidget *parent)
       
   160     : QAbstractScrollArea(dd, parent)
       
   161 {
       
   162     Q_D(QScrollArea);
       
   163     d->viewport->setBackgroundRole(QPalette::NoRole);
       
   164     d->vbar->setSingleStep(20);
       
   165     d->hbar->setSingleStep(20);
       
   166     d->layoutChildren();
       
   167 }
       
   168 
       
   169 /*!
       
   170     Destroys the scroll area and its child widget.
       
   171 
       
   172     \sa setWidget()
       
   173 */
       
   174 QScrollArea::~QScrollArea()
       
   175 {
       
   176 }
       
   177 
       
   178 void QScrollAreaPrivate::updateWidgetPosition()
       
   179 {
       
   180     Q_Q(QScrollArea);
       
   181     Qt::LayoutDirection dir = q->layoutDirection();
       
   182     QRect scrolled = QStyle::visualRect(dir, viewport->rect(), QRect(QPoint(-hbar->value(), -vbar->value()), widget->size()));
       
   183     QRect aligned = QStyle::alignedRect(dir, alignment, widget->size(), viewport->rect());
       
   184     widget->move(widget->width() < viewport->width() ? aligned.x() : scrolled.x(),
       
   185                  widget->height() < viewport->height() ? aligned.y() : scrolled.y());
       
   186 }
       
   187 
       
   188 void QScrollAreaPrivate::updateScrollBars()
       
   189 {
       
   190     Q_Q(QScrollArea);
       
   191     if (!widget)
       
   192         return;
       
   193     QSize p = viewport->size();
       
   194     QSize m = q->maximumViewportSize();
       
   195 
       
   196     QSize min = qSmartMinSize(widget);
       
   197     QSize max = qSmartMaxSize(widget);
       
   198 
       
   199     if (resizable) {
       
   200         if ((widget->layout() ? widget->layout()->hasHeightForWidth() : widget->sizePolicy().hasHeightForWidth())) {
       
   201             QSize p_hfw = p.expandedTo(min).boundedTo(max);
       
   202             int h = widget->heightForWidth( p_hfw.width() );
       
   203             min = QSize(p_hfw.width(), qMax(p_hfw.height(), h));
       
   204         }
       
   205     }
       
   206 
       
   207     if ((resizable && m.expandedTo(min) == m && m.boundedTo(max) == m)
       
   208         || (!resizable && m.expandedTo(widget->size()) == m))
       
   209         p = m; // no scroll bars needed
       
   210 
       
   211     if (resizable)
       
   212         widget->resize(p.expandedTo(min).boundedTo(max));
       
   213     QSize v = widget->size();
       
   214 
       
   215     hbar->setRange(0, v.width() - p.width());
       
   216     hbar->setPageStep(p.width());
       
   217     vbar->setRange(0, v.height() - p.height());
       
   218     vbar->setPageStep(p.height());
       
   219     updateWidgetPosition();
       
   220 
       
   221 }
       
   222 
       
   223 /*!
       
   224     Returns the scroll area's widget, or 0 if there is none.
       
   225 
       
   226     \sa setWidget()
       
   227 */
       
   228 
       
   229 QWidget *QScrollArea::widget() const
       
   230 {
       
   231     Q_D(const QScrollArea);
       
   232     return d->widget;
       
   233 }
       
   234 
       
   235 /*!
       
   236     \fn void QScrollArea::setWidget(QWidget *widget)
       
   237 
       
   238     Sets the scroll area's \a widget.
       
   239 
       
   240     The \a widget becomes a child of the scroll area, and will be
       
   241     destroyed when the scroll area is deleted or when a new widget is
       
   242     set.
       
   243     
       
   244     The widget's \l{QWidget::setAutoFillBackground()}{autoFillBackground}
       
   245     property will be set to \c{true}.
       
   246 
       
   247     If the scroll area is visible when the \a widget is
       
   248     added, you must \l{QWidget::}{show()} it explicitly.
       
   249 
       
   250     Note that You must add the layout of \a widget before you call
       
   251     this function; if you add it later, the \a widget will not be
       
   252     visible - regardless of when you \l{QWidget::}{show()} the scroll
       
   253     area. In this case, you can also not \l{QWidget::}{show()} the \a
       
   254     widget later.
       
   255 
       
   256     \sa widget()
       
   257 */
       
   258 void QScrollArea::setWidget(QWidget *widget)
       
   259 {
       
   260     Q_D(QScrollArea);
       
   261     if (widget == d->widget || !widget)
       
   262         return;
       
   263 
       
   264     delete d->widget;
       
   265     d->widget = 0;
       
   266     d->hbar->setValue(0);
       
   267     d->vbar->setValue(0);
       
   268     if (widget->parentWidget() != d->viewport)
       
   269         widget->setParent(d->viewport);
       
   270     if (!widget->testAttribute(Qt::WA_Resized))
       
   271         widget->resize(widget->sizeHint());
       
   272     d->widget = widget;
       
   273     d->widget->setAutoFillBackground(true);
       
   274     widget->installEventFilter(this);
       
   275     d->widgetSize = QSize();
       
   276     d->updateScrollBars();
       
   277     d->widget->show();
       
   278 
       
   279 }
       
   280 
       
   281 /*!
       
   282     Removes the scroll area's widget, and passes ownership of the
       
   283     widget to the caller.
       
   284 
       
   285     \sa widget()
       
   286  */
       
   287 QWidget *QScrollArea::takeWidget()
       
   288 {
       
   289     Q_D(QScrollArea);
       
   290     QWidget *w = d->widget;
       
   291     d->widget = 0;
       
   292     if (w)
       
   293         w->setParent(0);
       
   294     return w;
       
   295 }
       
   296 
       
   297 /*!
       
   298     \reimp
       
   299  */
       
   300 bool QScrollArea::event(QEvent *e)
       
   301 {
       
   302     Q_D(QScrollArea);
       
   303     if (e->type() == QEvent::StyleChange || e->type() == QEvent::LayoutRequest) {
       
   304         d->updateScrollBars();
       
   305     }
       
   306 #ifdef QT_KEYPAD_NAVIGATION
       
   307     else if (QApplication::keypadNavigationEnabled()) {
       
   308         if (e->type() == QEvent::Show)
       
   309             QApplication::instance()->installEventFilter(this);
       
   310         else if (e->type() == QEvent::Hide)
       
   311             QApplication::instance()->removeEventFilter(this);
       
   312     }
       
   313 #endif
       
   314     return QAbstractScrollArea::event(e);
       
   315 }
       
   316 
       
   317 
       
   318 /*!
       
   319     \reimp
       
   320  */
       
   321 bool QScrollArea::eventFilter(QObject *o, QEvent *e)
       
   322 {
       
   323     Q_D(QScrollArea);
       
   324 #ifdef QT_KEYPAD_NAVIGATION
       
   325     if (d->widget && o != d->widget && e->type() == QEvent::FocusIn
       
   326             && QApplication::keypadNavigationEnabled()) {
       
   327         if (o->isWidgetType())
       
   328             ensureWidgetVisible(static_cast<QWidget *>(o));
       
   329     }
       
   330 #endif
       
   331     if (o == d->widget && e->type() == QEvent::Resize)
       
   332         d->updateScrollBars();
       
   333 
       
   334     return false;
       
   335 }
       
   336 
       
   337 /*!
       
   338     \reimp
       
   339  */
       
   340 void QScrollArea::resizeEvent(QResizeEvent *)
       
   341 {
       
   342     Q_D(QScrollArea);
       
   343     d->updateScrollBars();
       
   344 
       
   345 }
       
   346 
       
   347 
       
   348 /*!\reimp
       
   349  */
       
   350 void QScrollArea::scrollContentsBy(int, int)
       
   351 {
       
   352     Q_D(QScrollArea);
       
   353     if (!d->widget)
       
   354         return;
       
   355     d->updateWidgetPosition();
       
   356 }
       
   357 
       
   358 
       
   359 /*!
       
   360     \property QScrollArea::widgetResizable
       
   361     \brief whether the scroll area should resize the view widget
       
   362 
       
   363     If this property is set to false (the default), the scroll area
       
   364     honors the size of its widget. Regardless of this property, you
       
   365     can programmatically resize the widget using widget()->resize(),
       
   366     and the scroll area will automatically adjust itself to the new
       
   367     size.
       
   368 
       
   369     If this property is set to true, the scroll area will
       
   370     automatically resize the widget in order to avoid scroll bars
       
   371     where they can be avoided, or to take advantage of extra space.
       
   372 */
       
   373 bool QScrollArea::widgetResizable() const
       
   374 {
       
   375     Q_D(const QScrollArea);
       
   376     return d->resizable;
       
   377 }
       
   378 
       
   379 void QScrollArea::setWidgetResizable(bool resizable)
       
   380 {
       
   381     Q_D(QScrollArea);
       
   382     d->resizable = resizable;
       
   383     updateGeometry();
       
   384     d->updateScrollBars();
       
   385 }
       
   386 
       
   387 /*!
       
   388     \reimp
       
   389  */
       
   390 QSize QScrollArea::sizeHint() const
       
   391 {
       
   392     Q_D(const QScrollArea);
       
   393     int f = 2 * d->frameWidth;
       
   394     QSize sz(f, f);
       
   395     int h = fontMetrics().height();
       
   396     if (d->widget) {
       
   397         if (!d->widgetSize.isValid())
       
   398             d->widgetSize = d->resizable ? d->widget->sizeHint() : d->widget->size();
       
   399         sz += d->widgetSize;
       
   400     } else {
       
   401         sz += QSize(12 * h, 8 * h);
       
   402     }
       
   403     if (d->vbarpolicy == Qt::ScrollBarAlwaysOn)
       
   404         sz.setWidth(sz.width() + d->vbar->sizeHint().width());
       
   405     if (d->hbarpolicy == Qt::ScrollBarAlwaysOn)
       
   406         sz.setHeight(sz.height() + d->hbar->sizeHint().height());
       
   407     return sz.boundedTo(QSize(36 * h, 24 * h));
       
   408 }
       
   409 
       
   410 
       
   411 
       
   412 /*!
       
   413     \reimp
       
   414  */
       
   415 bool QScrollArea::focusNextPrevChild(bool next)
       
   416 {
       
   417     if (QWidget::focusNextPrevChild(next)) {
       
   418         if (QWidget *fw = focusWidget())
       
   419             ensureWidgetVisible(fw);
       
   420         return true;
       
   421     }
       
   422     return false;
       
   423 }
       
   424 
       
   425 /*!
       
   426     Scrolls the contents of the scroll area so that the point (\a x, \a y) is visible
       
   427     inside the region of the viewport with margins specified in pixels by \a xmargin and
       
   428     \a ymargin. If the specified point cannot be reached, the contents are scrolled to
       
   429     the nearest valid position. The default value for both margins is 50 pixels.
       
   430 */
       
   431 void QScrollArea::ensureVisible(int x, int y, int xmargin, int ymargin)
       
   432 {
       
   433     Q_D(QScrollArea);
       
   434 
       
   435     int logicalX = QStyle::visualPos(layoutDirection(), d->viewport->rect(), QPoint(x, y)).x();
       
   436 
       
   437     if (logicalX - xmargin < d->hbar->value()) {
       
   438         d->hbar->setValue(qMax(0, logicalX - xmargin));
       
   439     } else if (logicalX > d->hbar->value() + d->viewport->width() - xmargin) {
       
   440         d->hbar->setValue(qMin(logicalX - d->viewport->width() + xmargin, d->hbar->maximum()));
       
   441     }
       
   442 
       
   443     if (y - ymargin < d->vbar->value()) {
       
   444         d->vbar->setValue(qMax(0, y - ymargin));
       
   445     } else if (y > d->vbar->value() + d->viewport->height() - ymargin) {
       
   446         d->vbar->setValue(qMin(y - d->viewport->height() + ymargin, d->vbar->maximum()));
       
   447     }
       
   448 }
       
   449 
       
   450 /*!
       
   451     \since 4.2
       
   452 
       
   453     Scrolls the contents of the scroll area so that the \a childWidget
       
   454     of QScrollArea::widget() is visible inside the viewport with
       
   455     margins specified in pixels by \a xmargin and \a ymargin. If the
       
   456     specified point cannot be reached, the contents are scrolled to
       
   457     the nearest valid position. The default value for both margins is
       
   458     50 pixels.
       
   459 
       
   460 */
       
   461 void QScrollArea::ensureWidgetVisible(QWidget *childWidget, int xmargin, int ymargin)
       
   462 {
       
   463     Q_D(QScrollArea);
       
   464 
       
   465     if (!d->widget->isAncestorOf(childWidget))
       
   466         return;
       
   467 
       
   468     const QRect microFocus = childWidget->inputMethodQuery(Qt::ImMicroFocus).toRect();
       
   469     const QRect defaultMicroFocus =
       
   470         childWidget->QWidget::inputMethodQuery(Qt::ImMicroFocus).toRect();
       
   471     QRect focusRect = (microFocus != defaultMicroFocus)
       
   472         ? QRect(childWidget->mapTo(d->widget, microFocus.topLeft()), microFocus.size())
       
   473         : QRect(childWidget->mapTo(d->widget, QPoint(0,0)), childWidget->size());
       
   474     const QRect visibleRect(-d->widget->pos(), d->viewport->size());
       
   475 
       
   476     if (visibleRect.contains(focusRect))
       
   477         return;
       
   478 
       
   479     focusRect.adjust(-xmargin, -ymargin, xmargin, ymargin);
       
   480 
       
   481     if (focusRect.width() > visibleRect.width())
       
   482         d->hbar->setValue(focusRect.center().x() - d->viewport->width() / 2);
       
   483     else if (focusRect.right() > visibleRect.right())
       
   484         d->hbar->setValue(focusRect.right() - d->viewport->width());
       
   485     else
       
   486         d->hbar->setValue(focusRect.left());
       
   487 
       
   488     if (focusRect.height() > visibleRect.height())
       
   489         d->vbar->setValue(focusRect.center().y() - d->viewport->height() / 2);
       
   490     else if (focusRect.bottom() > visibleRect.bottom())
       
   491         d->vbar->setValue(focusRect.bottom() - d->viewport->height());
       
   492     else
       
   493         d->vbar->setValue(focusRect.top());
       
   494 }
       
   495 
       
   496 
       
   497 /*!
       
   498     \property QScrollArea::alignment
       
   499     \brief the alignment of the scroll area's widget
       
   500     \since 4.2
       
   501 
       
   502     By default, the widget stays rooted to the top-left corner of the
       
   503     scroll area.
       
   504 */
       
   505 
       
   506 void QScrollArea::setAlignment(Qt::Alignment alignment)
       
   507 {
       
   508     Q_D(QScrollArea);
       
   509     d->alignment = alignment;
       
   510     if (d->widget)
       
   511         d->updateWidgetPosition();
       
   512 }
       
   513 
       
   514 Qt::Alignment QScrollArea::alignment() const
       
   515 {
       
   516     Q_D(const QScrollArea);
       
   517     return d->alignment;
       
   518 }
       
   519 
       
   520 QT_END_NAMESPACE
       
   521 
       
   522 #endif // QT_NO_SCROLLAREA