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
52QT_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*/
143QScrollArea::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*/
156QScrollArea::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*/
171QScrollArea::~QScrollArea()
172{
173}
174
175void QScrollAreaPrivate::updateWidgetPosition()
176{
177 Q_Q(QScrollArea);
178 Qt::LayoutDirection dir = q->layoutDirection();
179 QRect scrolled = QStyle::visualRect(direction: dir, boundingRect: viewport->rect(), logicalRect: QRect(QPoint(-hbar->value(), -vbar->value()), widget->size()));
180 QRect aligned = QStyle::alignedRect(direction: dir, alignment, size: widget->size(), rectangle: viewport->rect());
181 widget->move(ax: widget->width() < viewport->width() ? aligned.x() : scrolled.x(),
182 ay: widget->height() < viewport->height() ? aligned.y() : scrolled.y());
183}
184
185void 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(w: widget);
194 QSize max = qSmartMaxSize(w: widget);
195
196 if (resizable) {
197 if ((widget->layout() ? widget->layout()->hasHeightForWidth() : widget->sizePolicy().hasHeightForWidth())) {
198 QSize p_hfw = p.expandedTo(otherSize: min).boundedTo(otherSize: max);
199 int h = widget->heightForWidth(p_hfw.width());
200 // If the height we calculated requires a vertical scrollbar,
201 // then we need to constrain the width and calculate the height again,
202 // otherwise we end up flipping the scrollbar on and off all the time.
203 if (vbarpolicy == Qt::ScrollBarAsNeeded) {
204 int vbarWidth = vbar->sizeHint().width();
205 QSize m_hfw = m.expandedTo(otherSize: min).boundedTo(otherSize: max);
206 // is there any point in searching?
207 if (widget->heightForWidth(m_hfw.width() - vbarWidth) <= m.height()) {
208 while (h > m.height() && vbarWidth) {
209 --vbarWidth;
210 --m_hfw.rwidth();
211 h = widget->heightForWidth(m_hfw.width());
212 }
213 }
214 max = QSize(m_hfw.width(), qMax(a: m_hfw.height(), b: h));
215 }
216 min = QSize(p_hfw.width(), qMax(a: p_hfw.height(), b: h));
217 }
218 }
219
220 if ((resizable && m.expandedTo(otherSize: min) == m && m.boundedTo(otherSize: max) == m)
221 || (!resizable && m.expandedTo(otherSize: widget->size()) == m))
222 p = m; // no scroll bars needed
223
224 if (resizable)
225 widget->resize(p.expandedTo(otherSize: min).boundedTo(otherSize: max));
226 QSize v = widget->size();
227
228 hbar->setRange(min: 0, max: v.width() - p.width());
229 hbar->setPageStep(p.width());
230 vbar->setRange(min: 0, max: v.height() - p.height());
231 vbar->setPageStep(p.height());
232 updateWidgetPosition();
233
234}
235
236/*!
237 Returns the scroll area's widget, or \nullptr if there is none.
238
239 \sa setWidget()
240*/
241
242QWidget *QScrollArea::widget() const
243{
244 Q_D(const QScrollArea);
245 return d->widget;
246}
247
248/*!
249 \fn void QScrollArea::setWidget(QWidget *widget)
250
251 Sets the scroll area's \a widget.
252
253 The \a widget becomes a child of the scroll area, and will be
254 destroyed when the scroll area is deleted or when a new widget is
255 set.
256
257 The widget's \l{QWidget::setAutoFillBackground()}{autoFillBackground}
258 property will be set to \c{true}.
259
260 If the scroll area is visible when the \a widget is
261 added, you must \l{QWidget::}{show()} it explicitly.
262
263 Note that You must add the layout of \a widget before you call
264 this function; if you add it later, the \a widget will not be
265 visible - regardless of when you \l{QWidget::}{show()} the scroll
266 area. In this case, you can also not \l{QWidget::}{show()} the \a
267 widget later.
268
269 \sa widget()
270*/
271void QScrollArea::setWidget(QWidget *widget)
272{
273 Q_D(QScrollArea);
274 if (widget == d->widget || !widget)
275 return;
276
277 delete d->widget;
278 d->widget = nullptr;
279 d->hbar->setValue(0);
280 d->vbar->setValue(0);
281 if (widget->parentWidget() != d->viewport)
282 widget->setParent(d->viewport);
283 if (!widget->testAttribute(attribute: Qt::WA_Resized))
284 widget->resize(widget->sizeHint());
285 d->widget = widget;
286 d->widget->setAutoFillBackground(true);
287 widget->installEventFilter(filterObj: this);
288 d->widgetSize = QSize();
289 d->updateScrollBars();
290 d->widget->show();
291
292}
293
294/*!
295 Removes the scroll area's widget, and passes ownership of the
296 widget to the caller.
297
298 \sa widget()
299 */
300QWidget *QScrollArea::takeWidget()
301{
302 Q_D(QScrollArea);
303 QWidget *w = d->widget;
304 d->widget = nullptr;
305 if (w)
306 w->setParent(nullptr);
307 return w;
308}
309
310/*!
311 \reimp
312 */
313bool QScrollArea::event(QEvent *e)
314{
315 Q_D(QScrollArea);
316 if (e->type() == QEvent::StyleChange || e->type() == QEvent::LayoutRequest) {
317 d->updateScrollBars();
318 }
319#ifdef QT_KEYPAD_NAVIGATION
320 else if (QApplicationPrivate::keypadNavigationEnabled()) {
321 if (e->type() == QEvent::Show)
322 QApplication::instance()->installEventFilter(this);
323 else if (e->type() == QEvent::Hide)
324 QApplication::instance()->removeEventFilter(this);
325 }
326#endif
327 return QAbstractScrollArea::event(e);
328}
329
330
331/*!
332 \reimp
333 */
334bool QScrollArea::eventFilter(QObject *o, QEvent *e)
335{
336 Q_D(QScrollArea);
337#ifdef QT_KEYPAD_NAVIGATION
338 if (d->widget && o != d->widget && e->type() == QEvent::FocusIn
339 && QApplicationPrivate::keypadNavigationEnabled()) {
340 if (o->isWidgetType())
341 ensureWidgetVisible(static_cast<QWidget *>(o));
342 }
343#endif
344 if (o == d->widget && e->type() == QEvent::Resize)
345 d->updateScrollBars();
346
347 return QAbstractScrollArea::eventFilter(o, e);
348}
349
350/*!
351 \reimp
352 */
353void QScrollArea::resizeEvent(QResizeEvent *)
354{
355 Q_D(QScrollArea);
356 d->updateScrollBars();
357
358}
359
360
361/*!\reimp
362 */
363void QScrollArea::scrollContentsBy(int, int)
364{
365 Q_D(QScrollArea);
366 if (!d->widget)
367 return;
368 d->updateWidgetPosition();
369}
370
371
372/*!
373 \property QScrollArea::widgetResizable
374 \brief whether the scroll area should resize the view widget
375
376 If this property is set to false (the default), the scroll area
377 honors the size of its widget. Regardless of this property, you
378 can programmatically resize the widget using widget()->resize(),
379 and the scroll area will automatically adjust itself to the new
380 size.
381
382 If this property is set to true, the scroll area will
383 automatically resize the widget in order to avoid scroll bars
384 where they can be avoided, or to take advantage of extra space.
385*/
386bool QScrollArea::widgetResizable() const
387{
388 Q_D(const QScrollArea);
389 return d->resizable;
390}
391
392void QScrollArea::setWidgetResizable(bool resizable)
393{
394 Q_D(QScrollArea);
395 d->resizable = resizable;
396 updateGeometry();
397 d->updateScrollBars();
398}
399
400/*!
401 \reimp
402 */
403QSize QScrollArea::sizeHint() const
404{
405 Q_D(const QScrollArea);
406 int f = 2 * d->frameWidth;
407 QSize sz(f, f);
408 int h = fontMetrics().height();
409 if (d->widget) {
410 if (!d->widgetSize.isValid())
411 d->widgetSize = d->resizable ? d->widget->sizeHint() : d->widget->size();
412 sz += d->widgetSize;
413 } else {
414 sz += QSize(12 * h, 8 * h);
415 }
416 if (d->vbarpolicy == Qt::ScrollBarAlwaysOn)
417 sz.setWidth(sz.width() + d->vbar->sizeHint().width());
418 if (d->hbarpolicy == Qt::ScrollBarAlwaysOn)
419 sz.setHeight(sz.height() + d->hbar->sizeHint().height());
420 return sz.boundedTo(otherSize: QSize(36 * h, 24 * h));
421}
422
423/*!
424 \reimp
425 */
426QSize QScrollArea::viewportSizeHint() const
427{
428 Q_D(const QScrollArea);
429 if (d->widget) {
430 return d->resizable ? d->widget->sizeHint() : d->widget->size();
431 }
432 const int h = fontMetrics().height();
433 return QSize(6 * h, 4 * h);
434}
435
436
437/*!
438 \reimp
439 */
440bool QScrollArea::focusNextPrevChild(bool next)
441{
442 if (QWidget::focusNextPrevChild(next)) {
443 if (QWidget *fw = focusWidget())
444 ensureWidgetVisible(childWidget: fw);
445 return true;
446 }
447 return false;
448}
449
450/*!
451 Scrolls the contents of the scroll area so that the point (\a x, \a y) is visible
452 inside the region of the viewport with margins specified in pixels by \a xmargin and
453 \a ymargin. If the specified point cannot be reached, the contents are scrolled to
454 the nearest valid position. The default value for both margins is 50 pixels.
455*/
456void QScrollArea::ensureVisible(int x, int y, int xmargin, int ymargin)
457{
458 Q_D(QScrollArea);
459
460 int logicalX = QStyle::visualPos(direction: layoutDirection(), boundingRect: d->viewport->rect(), logicalPos: QPoint(x, y)).x();
461
462 if (logicalX - xmargin < d->hbar->value()) {
463 d->hbar->setValue(qMax(a: 0, b: logicalX - xmargin));
464 } else if (logicalX > d->hbar->value() + d->viewport->width() - xmargin) {
465 d->hbar->setValue(qMin(a: logicalX - d->viewport->width() + xmargin, b: d->hbar->maximum()));
466 }
467
468 if (y - ymargin < d->vbar->value()) {
469 d->vbar->setValue(qMax(a: 0, b: y - ymargin));
470 } else if (y > d->vbar->value() + d->viewport->height() - ymargin) {
471 d->vbar->setValue(qMin(a: y - d->viewport->height() + ymargin, b: d->vbar->maximum()));
472 }
473}
474
475/*!
476 \since 4.2
477
478 Scrolls the contents of the scroll area so that the \a childWidget
479 of QScrollArea::widget() is visible inside the viewport with
480 margins specified in pixels by \a xmargin and \a ymargin. If the
481 specified point cannot be reached, the contents are scrolled to
482 the nearest valid position. The default value for both margins is
483 50 pixels.
484
485*/
486void QScrollArea::ensureWidgetVisible(QWidget *childWidget, int xmargin, int ymargin)
487{
488 Q_D(QScrollArea);
489
490 if (!d->widget->isAncestorOf(child: childWidget))
491 return;
492
493 const QRect microFocus = childWidget->inputMethodQuery(Qt::ImCursorRectangle).toRect();
494 const QRect defaultMicroFocus =
495 childWidget->QWidget::inputMethodQuery(Qt::ImCursorRectangle).toRect();
496 QRect focusRect = (microFocus != defaultMicroFocus)
497 ? QRect(childWidget->mapTo(d->widget, microFocus.topLeft()), microFocus.size())
498 : QRect(childWidget->mapTo(d->widget, QPoint(0,0)), childWidget->size());
499 const QRect visibleRect(-d->widget->pos(), d->viewport->size());
500
501 if (visibleRect.contains(r: focusRect))
502 return;
503
504 focusRect.adjust(dx1: -xmargin, dy1: -ymargin, dx2: xmargin, dy2: ymargin);
505
506 if (focusRect.width() > visibleRect.width())
507 d->hbar->setValue(focusRect.center().x() - d->viewport->width() / 2);
508 else if (focusRect.right() > visibleRect.right())
509 d->hbar->setValue(focusRect.right() - d->viewport->width() + 1);
510 else if (focusRect.left() < visibleRect.left())
511 d->hbar->setValue(focusRect.left());
512
513 if (focusRect.height() > visibleRect.height())
514 d->vbar->setValue(focusRect.center().y() - d->viewport->height() / 2);
515 else if (focusRect.bottom() > visibleRect.bottom())
516 d->vbar->setValue(focusRect.bottom() - d->viewport->height() + 1);
517 else if (focusRect.top() < visibleRect.top())
518 d->vbar->setValue(focusRect.top());
519}
520
521
522/*!
523 \property QScrollArea::alignment
524 \brief the alignment of the scroll area's widget
525 \since 4.2
526
527 A valid alignment is a combination of the following flags:
528 \list
529 \li \c Qt::AlignLeft
530 \li \c Qt::AlignHCenter
531 \li \c Qt::AlignRight
532 \li \c Qt::AlignTop
533 \li \c Qt::AlignVCenter
534 \li \c Qt::AlignBottom
535 \endlist
536 By default, the widget stays rooted to the top-left corner of the
537 scroll area.
538*/
539
540void QScrollArea::setAlignment(Qt::Alignment alignment)
541{
542 Q_D(QScrollArea);
543 d->alignment = alignment;
544 if (d->widget)
545 d->updateWidgetPosition();
546}
547
548Qt::Alignment QScrollArea::alignment() const
549{
550 Q_D(const QScrollArea);
551 return d->alignment;
552}
553
554QT_END_NAMESPACE
555
556#include "moc_qscrollarea.cpp"
557

source code of qtbase/src/widgets/widgets/qscrollarea.cpp