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