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