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