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 return QAbstractScrollArea::event(e);
281}
282
283
284/*!
285 \reimp
286 */
287bool QScrollArea::eventFilter(QObject *o, QEvent *e)
288{
289 Q_D(QScrollArea);
290 if (o == d->widget && e->type() == QEvent::Resize)
291 d->updateScrollBars();
292
293 return QAbstractScrollArea::eventFilter(o, e);
294}
295
296/*!
297 \reimp
298 */
299void QScrollArea::resizeEvent(QResizeEvent *)
300{
301 Q_D(QScrollArea);
302 d->updateScrollBars();
303
304}
305
306
307/*!\reimp
308 */
309void QScrollArea::scrollContentsBy(int, int)
310{
311 Q_D(QScrollArea);
312 if (!d->widget)
313 return;
314 d->updateWidgetPosition();
315}
316
317
318/*!
319 \property QScrollArea::widgetResizable
320 \brief whether the scroll area should resize the view widget
321
322 If this property is set to false (the default), the scroll area
323 honors the size of its widget. Regardless of this property, you
324 can programmatically resize the widget using widget()->resize(),
325 and the scroll area will automatically adjust itself to the new
326 size.
327
328 If this property is set to true, the scroll area will
329 automatically resize the widget in order to avoid scroll bars
330 where they can be avoided, or to take advantage of extra space.
331*/
332bool QScrollArea::widgetResizable() const
333{
334 Q_D(const QScrollArea);
335 return d->resizable;
336}
337
338void QScrollArea::setWidgetResizable(bool resizable)
339{
340 Q_D(QScrollArea);
341 d->resizable = resizable;
342 updateGeometry();
343 d->updateScrollBars();
344}
345
346/*!
347 \reimp
348 */
349QSize QScrollArea::sizeHint() const
350{
351 Q_D(const QScrollArea);
352 int f = 2 * d->frameWidth;
353 QSize sz(f, f);
354 int h = fontMetrics().height();
355 if (d->widget) {
356 if (!d->widgetSize.isValid())
357 d->widgetSize = d->resizable ? d->widget->sizeHint() : d->widget->size();
358 sz += d->widgetSize;
359 } else {
360 sz += QSize(12 * h, 8 * h);
361 }
362 if (d->vbarpolicy == Qt::ScrollBarAlwaysOn)
363 sz.setWidth(sz.width() + d->vbar->sizeHint().width());
364 if (d->hbarpolicy == Qt::ScrollBarAlwaysOn)
365 sz.setHeight(sz.height() + d->hbar->sizeHint().height());
366 return sz.boundedTo(QSize(36 * h, 24 * h));
367}
368
369/*!
370 \reimp
371 */
372QSize QScrollArea::viewportSizeHint() const
373{
374 Q_D(const QScrollArea);
375 if (d->widget) {
376 return d->resizable ? d->widget->sizeHint() : d->widget->size();
377 }
378 const int h = fontMetrics().height();
379 return QSize(6 * h, 4 * h);
380}
381
382
383/*!
384 \reimp
385 */
386bool QScrollArea::focusNextPrevChild(bool next)
387{
388 if (QWidget::focusNextPrevChild(next)) {
389 if (QWidget *fw = focusWidget())
390 ensureWidgetVisible(fw);
391 return true;
392 }
393 return false;
394}
395
396/*!
397 Scrolls the contents of the scroll area so that the point (\a x, \a y) is visible
398 inside the region of the viewport with margins specified in pixels by \a xmargin and
399 \a ymargin. If the specified point cannot be reached, the contents are scrolled to
400 the nearest valid position. The default value for both margins is 50 pixels.
401*/
402void QScrollArea::ensureVisible(int x, int y, int xmargin, int ymargin)
403{
404 Q_D(QScrollArea);
405
406 int logicalX = QStyle::visualPos(layoutDirection(), d->viewport->rect(), QPoint(x, y)).x();
407
408 if (logicalX - xmargin < d->hbar->value()) {
409 d->hbar->setValue(qMax(0, logicalX - xmargin));
410 } else if (logicalX > d->hbar->value() + d->viewport->width() - xmargin) {
411 d->hbar->setValue(qMin(logicalX - d->viewport->width() + xmargin, d->hbar->maximum()));
412 }
413
414 if (y - ymargin < d->vbar->value()) {
415 d->vbar->setValue(qMax(0, y - ymargin));
416 } else if (y > d->vbar->value() + d->viewport->height() - ymargin) {
417 d->vbar->setValue(qMin(y - d->viewport->height() + ymargin, d->vbar->maximum()));
418 }
419}
420
421/*!
422 \since 4.2
423
424 Scrolls the contents of the scroll area so that the \a childWidget
425 of QScrollArea::widget() is visible inside the viewport with
426 margins specified in pixels by \a xmargin and \a ymargin. If the
427 specified point cannot be reached, the contents are scrolled to
428 the nearest valid position. The default value for both margins is
429 50 pixels.
430
431*/
432void QScrollArea::ensureWidgetVisible(QWidget *childWidget, int xmargin, int ymargin)
433{
434 Q_D(QScrollArea);
435
436 if (!d->widget->isAncestorOf(childWidget))
437 return;
438
439 const QRect microFocus = childWidget->inputMethodQuery(Qt::ImCursorRectangle).toRect();
440 const QRect defaultMicroFocus =
441 childWidget->QWidget::inputMethodQuery(Qt::ImCursorRectangle).toRect();
442 QRect focusRect = (microFocus != defaultMicroFocus)
443 ? QRect(childWidget->mapTo(d->widget, microFocus.topLeft()), microFocus.size())
444 : QRect(childWidget->mapTo(d->widget, QPoint(0,0)), childWidget->size());
445 const QRect visibleRect(-d->widget->pos(), d->viewport->size());
446
447 if (visibleRect.contains(focusRect))
448 return;
449
450 focusRect.adjust(-xmargin, -ymargin, xmargin, ymargin);
451
452 if (focusRect.width() > visibleRect.width())
453 d->hbar->setValue(focusRect.center().x() - d->viewport->width() / 2);
454 else if (focusRect.right() > visibleRect.right())
455 d->hbar->setValue(focusRect.right() - d->viewport->width() + 1);
456 else if (focusRect.left() < visibleRect.left())
457 d->hbar->setValue(focusRect.left());
458
459 if (focusRect.height() > visibleRect.height())
460 d->vbar->setValue(focusRect.center().y() - d->viewport->height() / 2);
461 else if (focusRect.bottom() > visibleRect.bottom())
462 d->vbar->setValue(focusRect.bottom() - d->viewport->height() + 1);
463 else if (focusRect.top() < visibleRect.top())
464 d->vbar->setValue(focusRect.top());
465}
466
467
468/*!
469 \property QScrollArea::alignment
470 \brief the alignment of the scroll area's widget
471 \since 4.2
472
473 A valid alignment is a combination of the following flags:
474 \list
475 \li \c Qt::AlignLeft
476 \li \c Qt::AlignHCenter
477 \li \c Qt::AlignRight
478 \li \c Qt::AlignTop
479 \li \c Qt::AlignVCenter
480 \li \c Qt::AlignBottom
481 \endlist
482 By default, the widget stays rooted to the top-left corner of the
483 scroll area.
484*/
485
486void QScrollArea::setAlignment(Qt::Alignment alignment)
487{
488 Q_D(QScrollArea);
489 d->alignment = alignment;
490 if (d->widget)
491 d->updateWidgetPosition();
492}
493
494Qt::Alignment QScrollArea::alignment() const
495{
496 Q_D(const QScrollArea);
497 return d->alignment;
498}
499
500QT_END_NAMESPACE
501
502#include "moc_qscrollarea.cpp"
Combined button and popup list for selecting options.