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 | |
4 | #include "qabstractscrollarea.h" |
5 | |
6 | #if QT_CONFIG(scrollarea) |
7 | |
8 | #include "qscrollbar.h" |
9 | #include "qapplication.h" |
10 | #include "qstyle.h" |
11 | #include "qstyleoption.h" |
12 | #include "qevent.h" |
13 | #include "qdebug.h" |
14 | #include "qboxlayout.h" |
15 | #include "qpainter.h" |
16 | #include "qmargins.h" |
17 | #if QT_CONFIG(itemviews) |
18 | #include "qheaderview.h" |
19 | #endif |
20 | |
21 | #include <QDebug> |
22 | |
23 | #include "qabstractscrollarea_p.h" |
24 | #include "qscrollbar_p.h" |
25 | #include <qwidget.h> |
26 | |
27 | #include <private/qapplication_p.h> |
28 | |
29 | #ifdef Q_OS_WIN |
30 | # include <qt_windows.h> |
31 | #endif |
32 | |
33 | QT_BEGIN_NAMESPACE |
34 | |
35 | using namespace Qt::StringLiterals; |
36 | |
37 | /*! |
38 | \class QAbstractScrollArea |
39 | \brief The QAbstractScrollArea widget provides a scrolling area with |
40 | on-demand scroll bars. |
41 | |
42 | \ingroup abstractwidgets |
43 | \inmodule QtWidgets |
44 | |
45 | QAbstractScrollArea is a low-level abstraction of a scrolling |
46 | area. The area provides a central widget called the viewport, in |
47 | which the contents of the area is to be scrolled (i.e, the |
48 | visible parts of the contents are rendered in the viewport). |
49 | |
50 | Next to the viewport is a vertical scroll bar, and below is a |
51 | horizontal scroll bar. When all of the area contents fits in the |
52 | viewport, each scroll bar can be either visible or hidden |
53 | depending on the scroll bar's Qt::ScrollBarPolicy. When a scroll |
54 | bar is hidden, the viewport expands in order to cover all |
55 | available space. When a scroll bar becomes visible again, the |
56 | viewport shrinks in order to make room for the scroll bar. |
57 | |
58 | It is possible to reserve a margin area around the viewport, see |
59 | setViewportMargins(). The feature is mostly used to place a |
60 | QHeaderView widget above or beside the scrolling area. Subclasses |
61 | of QAbstractScrollArea should implement margins. |
62 | |
63 | When inheriting QAbstractScrollArea, you need to do the |
64 | following: |
65 | |
66 | \list |
67 | \li Control the scroll bars by setting their |
68 | range, value, page step, and tracking their |
69 | movements. |
70 | \li Draw the contents of the area in the viewport according |
71 | to the values of the scroll bars. |
72 | \li Handle events received by the viewport in |
73 | viewportEvent() - notably resize events. |
74 | \li Use \c{viewport->update()} to update the contents of the |
75 | viewport instead of \l{QWidget::update()}{update()} |
76 | as all painting operations take place on the viewport. |
77 | \endlist |
78 | |
79 | With a scroll bar policy of Qt::ScrollBarAsNeeded (the default), |
80 | QAbstractScrollArea shows scroll bars when they provide a non-zero |
81 | scrolling range, and hides them otherwise. |
82 | |
83 | The scroll bars and viewport should be updated whenever the viewport |
84 | receives a resize event or the size of the contents changes. |
85 | The viewport also needs to be updated when the scroll bars |
86 | values change. The initial values of the scroll bars are often |
87 | set when the area receives new contents. |
88 | |
89 | We give a simple example, in which we have implemented a scroll area |
90 | that can scroll any QWidget. We make the widget a child of the |
91 | viewport; this way, we do not have to calculate which part of |
92 | the widget to draw but can simply move the widget with |
93 | QWidget::move(). When the area contents or the viewport size |
94 | changes, we do the following: |
95 | |
96 | \snippet myscrollarea/myscrollarea.cpp 1 |
97 | |
98 | When the scroll bars change value, we need to update the widget |
99 | position, i.e., find the part of the widget that is to be drawn in |
100 | the viewport: |
101 | |
102 | \snippet myscrollarea/myscrollarea.cpp 0 |
103 | |
104 | In order to track scroll bar movements, reimplement the virtual |
105 | function scrollContentsBy(). In order to fine-tune scrolling |
106 | behavior, connect to a scroll bar's |
107 | QAbstractSlider::actionTriggered() signal and adjust the \l |
108 | QAbstractSlider::sliderPosition as you wish. |
109 | |
110 | For convenience, QAbstractScrollArea makes all viewport events |
111 | available in the virtual viewportEvent() handler. QWidget's |
112 | specialized handlers are remapped to viewport events in the cases |
113 | where this makes sense. The remapped specialized handlers are: |
114 | paintEvent(), mousePressEvent(), mouseReleaseEvent(), |
115 | mouseDoubleClickEvent(), mouseMoveEvent(), wheelEvent(), |
116 | dragEnterEvent(), dragMoveEvent(), dragLeaveEvent(), dropEvent(), |
117 | contextMenuEvent(), and resizeEvent(). |
118 | |
119 | QScrollArea, which inherits QAbstractScrollArea, provides smooth |
120 | scrolling for any QWidget (i.e., the widget is scrolled pixel by |
121 | pixel). You only need to subclass QAbstractScrollArea if you need |
122 | more specialized behavior. This is, for instance, true if the |
123 | entire contents of the area is not suitable for being drawn on a |
124 | QWidget or if you do not want smooth scrolling. |
125 | |
126 | \sa QScrollArea |
127 | */ |
128 | |
129 | QAbstractScrollAreaPrivate::QAbstractScrollAreaPrivate() |
130 | :hbar(nullptr), vbar(nullptr), vbarpolicy(Qt::ScrollBarAsNeeded), hbarpolicy(Qt::ScrollBarAsNeeded), |
131 | shownOnce(false), inResize(false), sizeAdjustPolicy(QAbstractScrollArea::AdjustIgnored), |
132 | viewport(nullptr), cornerWidget(nullptr), left(0), top(0), right(0), bottom(0), |
133 | xoffset(0), yoffset(0), viewportFilter(nullptr) |
134 | { |
135 | } |
136 | |
137 | QAbstractScrollAreaPrivate::~QAbstractScrollAreaPrivate() |
138 | { |
139 | } |
140 | |
141 | QAbstractScrollAreaScrollBarContainer::QAbstractScrollAreaScrollBarContainer(Qt::Orientation orientation, QWidget *parent) |
142 | :QWidget(parent), scrollBar(new QScrollBar(orientation, this)), |
143 | layout(new QBoxLayout(orientation == Qt::Horizontal ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom)), |
144 | orientation(orientation) |
145 | { |
146 | setLayout(layout); |
147 | layout->setContentsMargins(QMargins()); |
148 | layout->setSpacing(0); |
149 | layout->addWidget(scrollBar); |
150 | layout->setSizeConstraint(QLayout::SetMaximumSize); |
151 | } |
152 | |
153 | /*! \internal |
154 | Adds a widget to the scroll bar container. |
155 | */ |
156 | void QAbstractScrollAreaScrollBarContainer::addWidget(QWidget *widget, LogicalPosition position) |
157 | { |
158 | QSizePolicy policy = widget->sizePolicy(); |
159 | if (orientation == Qt::Vertical) |
160 | policy.setHorizontalPolicy(QSizePolicy::Ignored); |
161 | else |
162 | policy.setVerticalPolicy(QSizePolicy::Ignored); |
163 | widget->setSizePolicy(policy); |
164 | widget->setParent(this); |
165 | |
166 | const int insertIndex = (position & LogicalLeft) ? 0 : scrollBarLayoutIndex() + 1; |
167 | layout->insertWidget(index: insertIndex, widget); |
168 | } |
169 | |
170 | /*! \internal |
171 | Returns a list of scroll-bar widgets for the given position. The scroll bar |
172 | itself is not returned. |
173 | */ |
174 | QWidgetList QAbstractScrollAreaScrollBarContainer::widgets(LogicalPosition position) |
175 | { |
176 | QWidgetList list; |
177 | const int scrollBarIndex = scrollBarLayoutIndex(); |
178 | if (position == LogicalLeft) { |
179 | list.reserve(asize: scrollBarIndex); |
180 | for (int i = 0; i < scrollBarIndex; ++i) |
181 | list.append(t: layout->itemAt(i)->widget()); |
182 | } else if (position == LogicalRight) { |
183 | const int layoutItemCount = layout->count(); |
184 | list.reserve(asize: layoutItemCount - (scrollBarIndex + 1)); |
185 | for (int i = scrollBarIndex + 1; i < layoutItemCount; ++i) |
186 | list.append(t: layout->itemAt(i)->widget()); |
187 | } |
188 | return list; |
189 | } |
190 | |
191 | /*! \internal |
192 | Returns the layout index for the scroll bar. This needs to be |
193 | recalculated by a linear search for each use, since items in |
194 | the layout can be removed at any time (i.e. when a widget is |
195 | deleted or re-parented). |
196 | */ |
197 | int QAbstractScrollAreaScrollBarContainer::scrollBarLayoutIndex() const |
198 | { |
199 | const int layoutItemCount = layout->count(); |
200 | for (int i = 0; i < layoutItemCount; ++i) { |
201 | if (qobject_cast<QScrollBar *>(object: layout->itemAt(i)->widget())) |
202 | return i; |
203 | } |
204 | return -1; |
205 | } |
206 | |
207 | /*! \internal |
208 | */ |
209 | void QAbstractScrollAreaPrivate::replaceScrollBar(QScrollBar *scrollBar, |
210 | Qt::Orientation orientation) |
211 | { |
212 | Q_Q(QAbstractScrollArea); |
213 | |
214 | QAbstractScrollAreaScrollBarContainer *container = scrollBarContainers[orientation]; |
215 | bool horizontal = (orientation == Qt::Horizontal); |
216 | QScrollBar *oldBar = horizontal ? hbar : vbar; |
217 | if (horizontal) |
218 | hbar = scrollBar; |
219 | else |
220 | vbar = scrollBar; |
221 | scrollBar->setParent(container); |
222 | container->scrollBar = scrollBar; |
223 | container->layout->removeWidget(w: oldBar); |
224 | container->layout->insertWidget(index: 0, widget: scrollBar); |
225 | scrollBar->setVisible(oldBar->isVisibleTo(container)); |
226 | scrollBar->setInvertedAppearance(oldBar->invertedAppearance()); |
227 | scrollBar->setInvertedControls(oldBar->invertedControls()); |
228 | scrollBar->setRange(min: oldBar->minimum(), max: oldBar->maximum()); |
229 | scrollBar->setOrientation(oldBar->orientation()); |
230 | scrollBar->setPageStep(oldBar->pageStep()); |
231 | scrollBar->setSingleStep(oldBar->singleStep()); |
232 | scrollBar->d_func()->viewMayChangeSingleStep = oldBar->d_func()->viewMayChangeSingleStep; |
233 | scrollBar->setSliderDown(oldBar->isSliderDown()); |
234 | scrollBar->setSliderPosition(oldBar->sliderPosition()); |
235 | scrollBar->setTracking(oldBar->hasTracking()); |
236 | scrollBar->setValue(oldBar->value()); |
237 | scrollBar->installEventFilter(filterObj: q); |
238 | oldBar->removeEventFilter(obj: q); |
239 | delete oldBar; |
240 | |
241 | QObject::connect(sender: scrollBar, SIGNAL(valueChanged(int)), |
242 | receiver: q, member: horizontal ? SLOT(_q_hslide(int)) : SLOT(_q_vslide(int))); |
243 | QObject::connect(sender: scrollBar, SIGNAL(rangeChanged(int,int)), |
244 | receiver: q, SLOT(_q_showOrHideScrollBars()), Qt::QueuedConnection); |
245 | } |
246 | |
247 | void QAbstractScrollAreaPrivate::init() |
248 | { |
249 | Q_Q(QAbstractScrollArea); |
250 | viewport = new QWidget(q); |
251 | viewport->setObjectName("qt_scrollarea_viewport"_L1 ); |
252 | viewport->setBackgroundRole(QPalette::Base); |
253 | viewport->setAutoFillBackground(true); |
254 | scrollBarContainers[Qt::Horizontal] = new QAbstractScrollAreaScrollBarContainer(Qt::Horizontal, q); |
255 | scrollBarContainers[Qt::Horizontal]->setObjectName("qt_scrollarea_hcontainer"_L1 ); |
256 | hbar = scrollBarContainers[Qt::Horizontal]->scrollBar; |
257 | hbar->setRange(min: 0,max: 0); |
258 | scrollBarContainers[Qt::Horizontal]->setVisible(false); |
259 | hbar->installEventFilter(filterObj: q); |
260 | QObject::connect(sender: hbar, SIGNAL(valueChanged(int)), receiver: q, SLOT(_q_hslide(int))); |
261 | QObject::connect(sender: hbar, SIGNAL(rangeChanged(int,int)), receiver: q, SLOT(_q_showOrHideScrollBars()), Qt::QueuedConnection); |
262 | scrollBarContainers[Qt::Vertical] = new QAbstractScrollAreaScrollBarContainer(Qt::Vertical, q); |
263 | scrollBarContainers[Qt::Vertical]->setObjectName("qt_scrollarea_vcontainer"_L1 ); |
264 | vbar = scrollBarContainers[Qt::Vertical]->scrollBar; |
265 | vbar->setRange(min: 0,max: 0); |
266 | scrollBarContainers[Qt::Vertical]->setVisible(false); |
267 | vbar->installEventFilter(filterObj: q); |
268 | QObject::connect(sender: vbar, SIGNAL(valueChanged(int)), receiver: q, SLOT(_q_vslide(int))); |
269 | QObject::connect(sender: vbar, SIGNAL(rangeChanged(int,int)), receiver: q, SLOT(_q_showOrHideScrollBars()), Qt::QueuedConnection); |
270 | viewportFilter.reset(other: new QAbstractScrollAreaFilter(this)); |
271 | viewport->installEventFilter(filterObj: viewportFilter.data()); |
272 | viewport->setFocusProxy(q); |
273 | q->setFocusPolicy(Qt::StrongFocus); |
274 | q->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); |
275 | q->setSizePolicy(hor: QSizePolicy::Expanding, ver: QSizePolicy::Expanding); |
276 | layoutChildren(); |
277 | #ifndef Q_OS_MACOS |
278 | # ifndef QT_NO_GESTURES |
279 | viewport->grabGesture(type: Qt::PanGesture); |
280 | # endif |
281 | #endif |
282 | } |
283 | |
284 | void QAbstractScrollAreaPrivate::layoutChildren() |
285 | { |
286 | bool needH = false; |
287 | bool needV = false; |
288 | layoutChildren_helper(needHorizontalScrollbar: &needH, needVerticalScrollbar: &needV); |
289 | // Call a second time if one scrollbar was needed and not the other to |
290 | // check if it needs to readjust accordingly |
291 | if (needH != needV) |
292 | layoutChildren_helper(needHorizontalScrollbar: &needH, needVerticalScrollbar: &needV); |
293 | } |
294 | |
295 | void QAbstractScrollAreaPrivate::layoutChildren_helper(bool *needHorizontalScrollbar, bool *needVerticalScrollbar) |
296 | { |
297 | Q_Q(QAbstractScrollArea); |
298 | QStyleOptionSlider barOpt; |
299 | |
300 | hbar->initStyleOption(option: &barOpt); |
301 | bool htransient = hbar->style()->styleHint(stylehint: QStyle::SH_ScrollBar_Transient, opt: &barOpt, widget: hbar); |
302 | bool needh = *needHorizontalScrollbar || ((hbarpolicy != Qt::ScrollBarAlwaysOff) && ((hbarpolicy == Qt::ScrollBarAlwaysOn && !htransient) |
303 | || ((hbarpolicy == Qt::ScrollBarAsNeeded || htransient) |
304 | && hbar->minimum() < hbar->maximum() && !hbar->sizeHint().isEmpty()))); |
305 | const int hscrollOverlap = hbar->style()->pixelMetric(metric: QStyle::PM_ScrollView_ScrollBarOverlap, option: &barOpt, widget: hbar); |
306 | |
307 | vbar->initStyleOption(option: &barOpt); |
308 | bool vtransient = vbar->style()->styleHint(stylehint: QStyle::SH_ScrollBar_Transient, opt: &barOpt, widget: vbar); |
309 | bool needv = *needVerticalScrollbar || ((vbarpolicy != Qt::ScrollBarAlwaysOff) && ((vbarpolicy == Qt::ScrollBarAlwaysOn && !vtransient) |
310 | || ((vbarpolicy == Qt::ScrollBarAsNeeded || vtransient) |
311 | && vbar->minimum() < vbar->maximum() && !vbar->sizeHint().isEmpty()))); |
312 | const int vscrollOverlap = vbar->style()->pixelMetric(metric: QStyle::PM_ScrollView_ScrollBarOverlap, option: &barOpt, widget: vbar); |
313 | |
314 | QStyleOption opt(0); |
315 | opt.initFrom(w: q); |
316 | |
317 | const int hsbExt = hbar->sizeHint().height(); |
318 | const int vsbExt = vbar->sizeHint().width(); |
319 | const QPoint extPoint(vsbExt, hsbExt); |
320 | const QSize extSize(vsbExt, hsbExt); |
321 | |
322 | const QRect widgetRect = q->rect(); |
323 | |
324 | const bool hasCornerWidget = (cornerWidget != nullptr); |
325 | |
326 | QPoint cornerOffset((needv && vscrollOverlap == 0) ? vsbExt : 0, (needh && hscrollOverlap == 0) ? hsbExt : 0); |
327 | QRect controlsRect; |
328 | QRect viewportRect; |
329 | |
330 | // In FrameOnlyAroundContents mode the frame is drawn between the controls and |
331 | // the viewport, else the frame rect is equal to the widget rect. |
332 | if ((frameStyle != QFrame::NoFrame) && |
333 | q->style()->styleHint(stylehint: QStyle::SH_ScrollView_FrameOnlyAroundContents, opt: &opt, widget: q)) { |
334 | controlsRect = widgetRect; |
335 | const int spacing = q->style()->pixelMetric(metric: QStyle::PM_ScrollView_ScrollBarSpacing, option: &opt, widget: q); |
336 | const QPoint (needv ? spacing + vscrollOverlap : 0, needh ? spacing + hscrollOverlap : 0); |
337 | QRect frameRect = widgetRect; |
338 | frameRect.adjust(dx1: 0, dy1: 0, dx2: -cornerOffset.x() - cornerExtra.x(), dy2: -cornerOffset.y() - cornerExtra.y()); |
339 | q->setFrameRect(QStyle::visualRect(direction: opt.direction, boundingRect: opt.rect, logicalRect: frameRect)); |
340 | // The frame rect needs to be in logical coords, however we need to flip |
341 | // the contentsRect back before passing it on to the viewportRect |
342 | // since the viewportRect has its logical coords calculated later. |
343 | viewportRect = QStyle::visualRect(direction: opt.direction, boundingRect: opt.rect, logicalRect: q->contentsRect()); |
344 | } else { |
345 | q->setFrameRect(QStyle::visualRect(direction: opt.direction, boundingRect: opt.rect, logicalRect: widgetRect)); |
346 | controlsRect = q->contentsRect(); |
347 | viewportRect = QRect(controlsRect.topLeft(), controlsRect.bottomRight() - cornerOffset); |
348 | } |
349 | |
350 | cornerOffset = QPoint(needv ? vsbExt : 0, needh ? hsbExt : 0); |
351 | |
352 | // If we have a corner widget and are only showing one scroll bar, we need to move it |
353 | // to make room for the corner widget. |
354 | if (hasCornerWidget && ((needv && vscrollOverlap == 0) || (needh && hscrollOverlap == 0))) |
355 | cornerOffset = extPoint; |
356 | |
357 | // The corner point is where the scroll bar rects, the corner widget rect and the |
358 | // viewport rect meets. |
359 | const QPoint cornerPoint(controlsRect.bottomRight() + QPoint(1, 1) - cornerOffset); |
360 | |
361 | // Some styles paints the corner if both scorllbars are showing and there is |
362 | // no corner widget. |
363 | if (needv && needh && !hasCornerWidget && hscrollOverlap == 0 && vscrollOverlap == 0) |
364 | cornerPaintingRect = QStyle::visualRect(direction: opt.direction, boundingRect: opt.rect, logicalRect: QRect(cornerPoint, extSize)); |
365 | else |
366 | cornerPaintingRect = QRect(); |
367 | |
368 | // move the scrollbars away from top/left headers |
369 | int = 0; |
370 | int = 0; |
371 | #if QT_CONFIG(itemviews) |
372 | if ((vscrollOverlap > 0 && needv) || (hscrollOverlap > 0 && needh)) { |
373 | const QList<QHeaderView *> = q->findChildren<QHeaderView*>(); |
374 | if (headers.size() <= 2) { |
375 | for (const QHeaderView * : headers) { |
376 | const QRect geo = header->geometry(); |
377 | if (header->orientation() == Qt::Vertical && header->isVisible() && QStyle::visualRect(direction: opt.direction, boundingRect: opt.rect, logicalRect: geo).left() <= opt.rect.width() / 2) |
378 | vHeaderRight = QStyle::visualRect(direction: opt.direction, boundingRect: opt.rect, logicalRect: geo).right(); |
379 | else if (header->orientation() == Qt::Horizontal && header->isVisible() && geo.top() <= q->frameWidth()) |
380 | hHeaderBottom = geo.bottom(); |
381 | } |
382 | } |
383 | } |
384 | #endif // QT_CONFIG(itemviews) |
385 | if (needh) { |
386 | QRect horizontalScrollBarRect(QPoint(controlsRect.left() + vHeaderRight, cornerPoint.y()), QPoint(cornerPoint.x() - 1, controlsRect.bottom())); |
387 | |
388 | if (!hasCornerWidget && htransient) |
389 | horizontalScrollBarRect.adjust(dx1: 0, dy1: 0, dx2: cornerOffset.x(), dy2: 0); |
390 | scrollBarContainers[Qt::Horizontal]->setGeometry(QStyle::visualRect(direction: opt.direction, boundingRect: opt.rect, logicalRect: horizontalScrollBarRect)); |
391 | scrollBarContainers[Qt::Horizontal]->raise(); |
392 | } |
393 | |
394 | if (needv) { |
395 | QRect verticalScrollBarRect (QPoint(cornerPoint.x(), controlsRect.top() + hHeaderBottom), QPoint(controlsRect.right(), cornerPoint.y() - 1)); |
396 | if (!hasCornerWidget && vtransient) |
397 | verticalScrollBarRect.adjust(dx1: 0, dy1: 0, dx2: 0, dy2: cornerOffset.y()); |
398 | scrollBarContainers[Qt::Vertical]->setGeometry(QStyle::visualRect(direction: opt.direction, boundingRect: opt.rect, logicalRect: verticalScrollBarRect)); |
399 | scrollBarContainers[Qt::Vertical]->raise(); |
400 | } |
401 | |
402 | if (cornerWidget) { |
403 | const QRect cornerWidgetRect(cornerPoint, controlsRect.bottomRight()); |
404 | cornerWidget->setGeometry(QStyle::visualRect(direction: opt.direction, boundingRect: opt.rect, logicalRect: cornerWidgetRect)); |
405 | } |
406 | |
407 | scrollBarContainers[Qt::Horizontal]->setVisible(needh); |
408 | scrollBarContainers[Qt::Vertical]->setVisible(needv); |
409 | |
410 | if (q->isRightToLeft()) |
411 | viewportRect.adjust(dx1: right, dy1: top, dx2: -left, dy2: -bottom); |
412 | else |
413 | viewportRect.adjust(dx1: left, dy1: top, dx2: -right, dy2: -bottom); |
414 | viewportRect = QStyle::visualRect(direction: opt.direction, boundingRect: opt.rect, logicalRect: viewportRect); |
415 | viewportRect.translate(p: -overshoot); |
416 | viewport->setGeometry(viewportRect); // resize the viewport last |
417 | |
418 | *needHorizontalScrollbar = needh; |
419 | *needVerticalScrollbar = needv; |
420 | } |
421 | |
422 | /*! |
423 | \enum QAbstractScrollArea::SizeAdjustPolicy |
424 | \since 5.2 |
425 | |
426 | This enum specifies how the size hint of the QAbstractScrollArea should |
427 | adjust when the size of the viewport changes. |
428 | |
429 | \value AdjustIgnored The scroll area will behave like before - and not do any adjust. |
430 | \value AdjustToContents The scroll area will always adjust to the viewport |
431 | \value AdjustToContentsOnFirstShow The scroll area will adjust to its viewport the first time it is shown. |
432 | */ |
433 | |
434 | |
435 | /*! |
436 | \internal |
437 | |
438 | Creates a new QAbstractScrollAreaPrivate, \a dd with the given \a parent. |
439 | */ |
440 | QAbstractScrollArea::QAbstractScrollArea(QAbstractScrollAreaPrivate &dd, QWidget *parent) |
441 | :QFrame(dd, parent) |
442 | { |
443 | Q_D(QAbstractScrollArea); |
444 | QT_TRY { |
445 | d->init(); |
446 | } QT_CATCH(...) { |
447 | d->viewportFilter.reset(); |
448 | QT_RETHROW; |
449 | } |
450 | } |
451 | |
452 | /*! |
453 | Constructs a viewport. |
454 | |
455 | The \a parent argument is sent to the QWidget constructor. |
456 | */ |
457 | QAbstractScrollArea::QAbstractScrollArea(QWidget *parent) |
458 | :QFrame(*new QAbstractScrollAreaPrivate, parent) |
459 | { |
460 | Q_D(QAbstractScrollArea); |
461 | QT_TRY { |
462 | d->init(); |
463 | } QT_CATCH(...) { |
464 | d->viewportFilter.reset(); |
465 | QT_RETHROW; |
466 | } |
467 | } |
468 | |
469 | |
470 | /*! |
471 | Destroys the viewport. |
472 | */ |
473 | QAbstractScrollArea::~QAbstractScrollArea() |
474 | { |
475 | Q_D(QAbstractScrollArea); |
476 | // reset it here, otherwise we'll have a dangling pointer in ~QWidget |
477 | d->viewportFilter.reset(); |
478 | } |
479 | |
480 | |
481 | /*! |
482 | \since 4.2 |
483 | Sets the viewport to be the given \a widget. |
484 | The QAbstractScrollArea will take ownership of the given \a widget. |
485 | |
486 | If \a widget is \nullptr, QAbstractScrollArea will assign a new QWidget |
487 | instance for the viewport. |
488 | |
489 | \sa viewport() |
490 | */ |
491 | void QAbstractScrollArea::setViewport(QWidget *widget) |
492 | { |
493 | Q_D(QAbstractScrollArea); |
494 | if (widget != d->viewport) { |
495 | QWidget *oldViewport = d->viewport; |
496 | if (!widget) |
497 | widget = new QWidget; |
498 | d->viewport = widget; |
499 | d->viewport->setParent(this); |
500 | d->viewport->setFocusProxy(this); |
501 | d->viewport->installEventFilter(filterObj: d->viewportFilter.data()); |
502 | #ifndef QT_NO_GESTURES |
503 | d->viewport->grabGesture(type: Qt::PanGesture); |
504 | #endif |
505 | d->layoutChildren(); |
506 | #ifndef QT_NO_OPENGL |
507 | QWidgetPrivate::get(w: d->viewport)->initializeViewportFramebuffer(); |
508 | #endif |
509 | if (isVisible()) |
510 | d->viewport->show(); |
511 | setupViewport(widget); |
512 | delete oldViewport; |
513 | } |
514 | } |
515 | |
516 | /*! |
517 | Returns the viewport widget. |
518 | |
519 | Use the QScrollArea::widget() function to retrieve the contents of |
520 | the viewport widget. |
521 | |
522 | \sa QScrollArea::widget() |
523 | */ |
524 | QWidget *QAbstractScrollArea::viewport() const |
525 | { |
526 | Q_D(const QAbstractScrollArea); |
527 | return d->viewport; |
528 | } |
529 | |
530 | |
531 | /*! |
532 | Returns the size of the viewport as if the scroll bars had no valid |
533 | scrolling range. |
534 | */ |
535 | QSize QAbstractScrollArea::maximumViewportSize() const |
536 | { |
537 | Q_D(const QAbstractScrollArea); |
538 | int f = 2 * d->frameWidth; |
539 | QSize max = size() - QSize(f + d->left + d->right, f + d->top + d->bottom); |
540 | // Count the sizeHint of the bar only if it is displayed. |
541 | if (d->vbarpolicy == Qt::ScrollBarAlwaysOn) |
542 | max.rwidth() -= d->vbar->sizeHint().width(); |
543 | if (d->hbarpolicy == Qt::ScrollBarAlwaysOn) |
544 | max.rheight() -= d->hbar->sizeHint().height(); |
545 | return max; |
546 | } |
547 | |
548 | /*! |
549 | \property QAbstractScrollArea::verticalScrollBarPolicy |
550 | \brief the policy for the vertical scroll bar |
551 | |
552 | The default policy is Qt::ScrollBarAsNeeded. |
553 | |
554 | \sa horizontalScrollBarPolicy |
555 | */ |
556 | |
557 | Qt::ScrollBarPolicy QAbstractScrollArea::verticalScrollBarPolicy() const |
558 | { |
559 | Q_D(const QAbstractScrollArea); |
560 | return d->vbarpolicy; |
561 | } |
562 | |
563 | void QAbstractScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy) |
564 | { |
565 | Q_D(QAbstractScrollArea); |
566 | const Qt::ScrollBarPolicy oldPolicy = d->vbarpolicy; |
567 | d->vbarpolicy = policy; |
568 | if (isVisible()) |
569 | d->layoutChildren(); |
570 | if (oldPolicy != d->vbarpolicy) |
571 | d->scrollBarPolicyChanged(Qt::Vertical, d->vbarpolicy); |
572 | } |
573 | |
574 | |
575 | /*! |
576 | Returns the vertical scroll bar. |
577 | |
578 | \sa verticalScrollBarPolicy, horizontalScrollBar() |
579 | */ |
580 | QScrollBar *QAbstractScrollArea::verticalScrollBar() const |
581 | { |
582 | Q_D(const QAbstractScrollArea); |
583 | return d->vbar; |
584 | } |
585 | |
586 | /*! |
587 | \since 4.2 |
588 | Replaces the existing vertical scroll bar with \a scrollBar, and sets all |
589 | the former scroll bar's slider properties on the new scroll bar. The former |
590 | scroll bar is then deleted. |
591 | |
592 | QAbstractScrollArea already provides vertical and horizontal scroll bars by |
593 | default. You can call this function to replace the default vertical |
594 | scroll bar with your own custom scroll bar. |
595 | |
596 | \sa verticalScrollBar(), setHorizontalScrollBar() |
597 | */ |
598 | void QAbstractScrollArea::setVerticalScrollBar(QScrollBar *scrollBar) |
599 | { |
600 | Q_D(QAbstractScrollArea); |
601 | if (Q_UNLIKELY(!scrollBar)) { |
602 | qWarning(msg: "QAbstractScrollArea::setVerticalScrollBar: Cannot set a null scroll bar" ); |
603 | return; |
604 | } |
605 | |
606 | d->replaceScrollBar(scrollBar, orientation: Qt::Vertical); |
607 | } |
608 | |
609 | /*! |
610 | \property QAbstractScrollArea::horizontalScrollBarPolicy |
611 | \brief the policy for the horizontal scroll bar |
612 | |
613 | The default policy is Qt::ScrollBarAsNeeded. |
614 | |
615 | \sa verticalScrollBarPolicy |
616 | */ |
617 | |
618 | Qt::ScrollBarPolicy QAbstractScrollArea::horizontalScrollBarPolicy() const |
619 | { |
620 | Q_D(const QAbstractScrollArea); |
621 | return d->hbarpolicy; |
622 | } |
623 | |
624 | void QAbstractScrollArea::setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy) |
625 | { |
626 | Q_D(QAbstractScrollArea); |
627 | const Qt::ScrollBarPolicy oldPolicy = d->hbarpolicy; |
628 | d->hbarpolicy = policy; |
629 | if (isVisible()) |
630 | d->layoutChildren(); |
631 | if (oldPolicy != d->hbarpolicy) |
632 | d->scrollBarPolicyChanged(Qt::Horizontal, d->hbarpolicy); |
633 | } |
634 | |
635 | /*! |
636 | Returns the horizontal scroll bar. |
637 | |
638 | \sa horizontalScrollBarPolicy, verticalScrollBar() |
639 | */ |
640 | QScrollBar *QAbstractScrollArea::horizontalScrollBar() const |
641 | { |
642 | Q_D(const QAbstractScrollArea); |
643 | return d->hbar; |
644 | } |
645 | |
646 | /*! |
647 | \since 4.2 |
648 | |
649 | Replaces the existing horizontal scroll bar with \a scrollBar, and sets all |
650 | the former scroll bar's slider properties on the new scroll bar. The former |
651 | scroll bar is then deleted. |
652 | |
653 | QAbstractScrollArea already provides horizontal and vertical scroll bars by |
654 | default. You can call this function to replace the default horizontal |
655 | scroll bar with your own custom scroll bar. |
656 | |
657 | \sa horizontalScrollBar(), setVerticalScrollBar() |
658 | */ |
659 | void QAbstractScrollArea::setHorizontalScrollBar(QScrollBar *scrollBar) |
660 | { |
661 | Q_D(QAbstractScrollArea); |
662 | if (Q_UNLIKELY(!scrollBar)) { |
663 | qWarning(msg: "QAbstractScrollArea::setHorizontalScrollBar: Cannot set a null scroll bar" ); |
664 | return; |
665 | } |
666 | |
667 | d->replaceScrollBar(scrollBar, orientation: Qt::Horizontal); |
668 | } |
669 | |
670 | /*! |
671 | \since 4.2 |
672 | |
673 | Returns the widget in the corner between the two scroll bars. |
674 | |
675 | By default, no corner widget is present. |
676 | */ |
677 | QWidget *QAbstractScrollArea::cornerWidget() const |
678 | { |
679 | Q_D(const QAbstractScrollArea); |
680 | return d->cornerWidget; |
681 | } |
682 | |
683 | /*! |
684 | \since 4.2 |
685 | |
686 | Sets the widget in the corner between the two scroll bars to be |
687 | \a widget. |
688 | |
689 | You will probably also want to set at least one of the scroll bar |
690 | modes to \c AlwaysOn. |
691 | |
692 | Passing \nullptr shows no widget in the corner. |
693 | |
694 | Any previous corner widget is hidden. |
695 | |
696 | You may call setCornerWidget() with the same widget at different |
697 | times. |
698 | |
699 | All widgets set here will be deleted by the scroll area when it is |
700 | destroyed unless you separately reparent the widget after setting |
701 | some other corner widget (or \nullptr). |
702 | |
703 | Any \e newly set widget should have no current parent. |
704 | |
705 | By default, no corner widget is present. |
706 | |
707 | \sa horizontalScrollBarPolicy, horizontalScrollBarPolicy |
708 | */ |
709 | void QAbstractScrollArea::setCornerWidget(QWidget *widget) |
710 | { |
711 | Q_D(QAbstractScrollArea); |
712 | QWidget* oldWidget = d->cornerWidget; |
713 | if (oldWidget != widget) { |
714 | if (oldWidget) |
715 | oldWidget->hide(); |
716 | d->cornerWidget = widget; |
717 | |
718 | if (widget && widget->parentWidget() != this) |
719 | widget->setParent(this); |
720 | |
721 | d->layoutChildren(); |
722 | if (widget) |
723 | widget->show(); |
724 | } else { |
725 | d->cornerWidget = widget; |
726 | d->layoutChildren(); |
727 | } |
728 | } |
729 | |
730 | /*! |
731 | \since 4.2 |
732 | Adds \a widget as a scroll bar widget in the location specified |
733 | by \a alignment. |
734 | |
735 | Scroll bar widgets are shown next to the horizontal or vertical |
736 | scroll bar, and can be placed on either side of it. If you want |
737 | the scroll bar widgets to be always visible, set the |
738 | scrollBarPolicy for the corresponding scroll bar to \c AlwaysOn. |
739 | |
740 | \a alignment must be one of Qt::Alignleft and Qt::AlignRight, |
741 | which maps to the horizontal scroll bar, or Qt::AlignTop and |
742 | Qt::AlignBottom, which maps to the vertical scroll bar. |
743 | |
744 | A scroll bar widget can be removed by either re-parenting the |
745 | widget or deleting it. It's also possible to hide a widget with |
746 | QWidget::hide() |
747 | |
748 | The scroll bar widget will be resized to fit the scroll bar |
749 | geometry for the current style. The following describes the case |
750 | for scroll bar widgets on the horizontal scroll bar: |
751 | |
752 | The height of the widget will be set to match the height of the |
753 | scroll bar. To control the width of the widget, use |
754 | QWidget::setMinimumWidth and QWidget::setMaximumWidth, or |
755 | implement QWidget::sizeHint() and set a horizontal size policy. |
756 | If you want a square widget, call |
757 | QStyle::pixelMetric(QStyle::PM_ScrollBarExtent) and set the |
758 | width to this value. |
759 | |
760 | \sa scrollBarWidgets() |
761 | */ |
762 | void QAbstractScrollArea::addScrollBarWidget(QWidget *widget, Qt::Alignment alignment) |
763 | { |
764 | Q_D(QAbstractScrollArea); |
765 | |
766 | if (widget == nullptr) |
767 | return; |
768 | |
769 | const Qt::Orientation scrollBarOrientation |
770 | = ((alignment & Qt::AlignLeft) || (alignment & Qt::AlignRight)) ? Qt::Horizontal : Qt::Vertical; |
771 | const QAbstractScrollAreaScrollBarContainer::LogicalPosition position |
772 | = ((alignment & Qt::AlignRight) || (alignment & Qt::AlignBottom)) |
773 | ? QAbstractScrollAreaScrollBarContainer::LogicalRight : QAbstractScrollAreaScrollBarContainer::LogicalLeft; |
774 | d->scrollBarContainers[scrollBarOrientation]->addWidget(widget, position); |
775 | d->layoutChildren(); |
776 | if (isHidden() == false) |
777 | widget->show(); |
778 | } |
779 | |
780 | /*! |
781 | \since 4.2 |
782 | Returns a list of the currently set scroll bar widgets. \a alignment |
783 | can be any combination of the four location flags. |
784 | |
785 | \sa addScrollBarWidget() |
786 | */ |
787 | QWidgetList QAbstractScrollArea::scrollBarWidgets(Qt::Alignment alignment) |
788 | { |
789 | Q_D(QAbstractScrollArea); |
790 | |
791 | QWidgetList list; |
792 | |
793 | if (alignment & Qt::AlignLeft) |
794 | list += d->scrollBarContainers[Qt::Horizontal]->widgets(position: QAbstractScrollAreaScrollBarContainer::LogicalLeft); |
795 | if (alignment & Qt::AlignRight) |
796 | list += d->scrollBarContainers[Qt::Horizontal]->widgets(position: QAbstractScrollAreaScrollBarContainer::LogicalRight); |
797 | if (alignment & Qt::AlignTop) |
798 | list += d->scrollBarContainers[Qt::Vertical]->widgets(position: QAbstractScrollAreaScrollBarContainer::LogicalLeft); |
799 | if (alignment & Qt::AlignBottom) |
800 | list += d->scrollBarContainers[Qt::Vertical]->widgets(position: QAbstractScrollAreaScrollBarContainer::LogicalRight); |
801 | |
802 | return list; |
803 | } |
804 | |
805 | /*! |
806 | Sets the margins around the scrolling area to \a left, \a top, \a |
807 | right and \a bottom. This is useful for applications such as |
808 | spreadsheets with "locked" rows and columns. The marginal space |
809 | is left blank; put widgets in the unused area. |
810 | |
811 | Note that this function is frequently called by QTreeView and |
812 | QTableView, so margins must be implemented by QAbstractScrollArea |
813 | subclasses. Also, if the subclasses are to be used in item views, |
814 | they should not call this function. |
815 | |
816 | By default all margins are zero. |
817 | \sa viewportMargins() |
818 | */ |
819 | void QAbstractScrollArea::setViewportMargins(int left, int top, int right, int bottom) |
820 | { |
821 | Q_D(QAbstractScrollArea); |
822 | d->left = left; |
823 | d->top = top; |
824 | d->right = right; |
825 | d->bottom = bottom; |
826 | d->layoutChildren(); |
827 | } |
828 | |
829 | /*! |
830 | \since 4.6 |
831 | Sets \a margins around the scrolling area. This is useful for |
832 | applications such as spreadsheets with "locked" rows and columns. |
833 | The marginal space is is left blank; put widgets in the unused |
834 | area. |
835 | |
836 | By default all margins are zero. |
837 | \sa viewportMargins() |
838 | */ |
839 | void QAbstractScrollArea::setViewportMargins(const QMargins &margins) |
840 | { |
841 | setViewportMargins(left: margins.left(), top: margins.top(), |
842 | right: margins.right(), bottom: margins.bottom()); |
843 | } |
844 | |
845 | /*! |
846 | \since 5.5 |
847 | Returns the margins around the scrolling area. |
848 | By default all the margins are zero. |
849 | |
850 | \sa setViewportMargins() |
851 | */ |
852 | QMargins QAbstractScrollArea::viewportMargins() const |
853 | { |
854 | Q_D(const QAbstractScrollArea); |
855 | return QMargins(d->left, d->top, d->right, d->bottom); |
856 | } |
857 | |
858 | /*! \internal */ |
859 | bool QAbstractScrollArea::eventFilter(QObject *o, QEvent *e) |
860 | { |
861 | Q_D(QAbstractScrollArea); |
862 | if ((o == d->hbar || o == d->vbar) && (e->type() == QEvent::HoverEnter || e->type() == QEvent::HoverLeave)) { |
863 | if (d->hbarpolicy == Qt::ScrollBarAsNeeded && d->vbarpolicy == Qt::ScrollBarAsNeeded) { |
864 | QScrollBar *sbar = static_cast<QScrollBar*>(o); |
865 | QScrollBar *sibling = sbar == d->hbar ? d->vbar : d->hbar; |
866 | if (sbar->style()->styleHint(stylehint: QStyle::SH_ScrollBar_Transient, opt: nullptr, widget: sbar) && |
867 | sibling->style()->styleHint(stylehint: QStyle::SH_ScrollBar_Transient, opt: nullptr, widget: sibling)) |
868 | d->setScrollBarTransient(scrollBar: sibling, transient: e->type() == QEvent::HoverLeave); |
869 | } |
870 | } |
871 | return QFrame::eventFilter(watched: o, event: e); |
872 | } |
873 | |
874 | /*! |
875 | \fn bool QAbstractScrollArea::event(QEvent *event) |
876 | |
877 | \reimp |
878 | |
879 | This is the main event handler for the QAbstractScrollArea widget (\e not |
880 | the scrolling area viewport()). The specified \a event is a general event |
881 | object that may need to be cast to the appropriate class depending on its |
882 | type. |
883 | |
884 | \sa QEvent::type() |
885 | */ |
886 | bool QAbstractScrollArea::event(QEvent *e) |
887 | { |
888 | Q_D(QAbstractScrollArea); |
889 | switch (e->type()) { |
890 | case QEvent::AcceptDropsChange: |
891 | // There was a chance that with accessibility client we get an |
892 | // event before the viewport was created. |
893 | // Also, in some cases we might get here from QWidget::event() virtual function which is (indirectly) called |
894 | // from the viewport constructor at the time when the d->viewport is not yet initialized even without any |
895 | // accessibility client. See qabstractscrollarea autotest for a test case. |
896 | if (d->viewport) |
897 | d->viewport->setAcceptDrops(acceptDrops()); |
898 | break; |
899 | case QEvent::MouseTrackingChange: |
900 | d->viewport->setMouseTracking(hasMouseTracking()); |
901 | break; |
902 | case QEvent::Resize: |
903 | if (!d->inResize) { |
904 | d->inResize = true; |
905 | d->layoutChildren(); |
906 | d->inResize = false; |
907 | } |
908 | break; |
909 | case QEvent::Show: |
910 | if (!d->shownOnce && d->sizeAdjustPolicy == QAbstractScrollArea::AdjustToContentsOnFirstShow) { |
911 | d->sizeHint = QSize(); |
912 | updateGeometry(); |
913 | } |
914 | d->shownOnce = true; |
915 | return QFrame::event(e); |
916 | case QEvent::Paint: { |
917 | QStyleOption option; |
918 | option.initFrom(w: this); |
919 | if (d->cornerPaintingRect.isValid()) { |
920 | option.rect = d->cornerPaintingRect; |
921 | QPainter p(this); |
922 | style()->drawPrimitive(pe: QStyle::PE_PanelScrollAreaCorner, opt: &option, p: &p, w: this); |
923 | } |
924 | } |
925 | QFrame::paintEvent((QPaintEvent*)e); |
926 | break; |
927 | #ifndef QT_NO_CONTEXTMENU |
928 | case QEvent::ContextMenu: |
929 | if (static_cast<QContextMenuEvent *>(e)->reason() == QContextMenuEvent::Keyboard) |
930 | return QFrame::event(e); |
931 | e->ignore(); |
932 | break; |
933 | #endif // QT_NO_CONTEXTMENU |
934 | case QEvent::MouseButtonPress: |
935 | case QEvent::MouseButtonRelease: |
936 | case QEvent::MouseButtonDblClick: |
937 | case QEvent::MouseMove: |
938 | case QEvent::Wheel: |
939 | #if QT_CONFIG(draganddrop) |
940 | case QEvent::Drop: |
941 | case QEvent::DragEnter: |
942 | case QEvent::DragMove: |
943 | case QEvent::DragLeave: |
944 | #endif |
945 | // ignore touch events in case they have been propagated from the viewport |
946 | case QEvent::TouchBegin: |
947 | case QEvent::TouchUpdate: |
948 | case QEvent::TouchEnd: |
949 | return false; |
950 | #ifndef QT_NO_GESTURES |
951 | case QEvent::Gesture: |
952 | { |
953 | QGestureEvent *ge = static_cast<QGestureEvent *>(e); |
954 | QPanGesture *g = static_cast<QPanGesture *>(ge->gesture(type: Qt::PanGesture)); |
955 | if (g) { |
956 | QScrollBar *hBar = horizontalScrollBar(); |
957 | QScrollBar *vBar = verticalScrollBar(); |
958 | QPointF delta = g->delta(); |
959 | if (!delta.isNull()) { |
960 | if (QGuiApplication::isRightToLeft()) |
961 | delta.rx() *= -1; |
962 | int newX = hBar->value() - delta.x(); |
963 | int newY = vBar->value() - delta.y(); |
964 | hBar->setValue(newX); |
965 | vBar->setValue(newY); |
966 | } |
967 | return true; |
968 | } |
969 | return false; |
970 | } |
971 | #endif // QT_NO_GESTURES |
972 | case QEvent::ScrollPrepare: |
973 | { |
974 | QScrollPrepareEvent *se = static_cast<QScrollPrepareEvent *>(e); |
975 | if (d->canStartScrollingAt(startPos: se->startPos().toPoint())) { |
976 | QScrollBar *hBar = horizontalScrollBar(); |
977 | QScrollBar *vBar = verticalScrollBar(); |
978 | |
979 | se->setViewportSize(QSizeF(viewport()->size())); |
980 | se->setContentPosRange(QRectF(0, 0, hBar->maximum(), vBar->maximum())); |
981 | se->setContentPos(QPointF(hBar->value(), vBar->value())); |
982 | se->accept(); |
983 | return true; |
984 | } |
985 | return false; |
986 | } |
987 | case QEvent::Scroll: |
988 | { |
989 | QScrollEvent *se = static_cast<QScrollEvent *>(e); |
990 | |
991 | QScrollBar *hBar = horizontalScrollBar(); |
992 | QScrollBar *vBar = verticalScrollBar(); |
993 | hBar->setValue(se->contentPos().x()); |
994 | vBar->setValue(se->contentPos().y()); |
995 | |
996 | QPoint delta = d->overshoot - se->overshootDistance().toPoint(); |
997 | if (!delta.isNull()) |
998 | viewport()->move(viewport()->pos() + delta); |
999 | |
1000 | d->overshoot = se->overshootDistance().toPoint(); |
1001 | |
1002 | return true; |
1003 | } |
1004 | case QEvent::StyleChange: |
1005 | case QEvent::LayoutDirectionChange: |
1006 | case QEvent::ApplicationLayoutDirectionChange: |
1007 | case QEvent::LayoutRequest: |
1008 | d->layoutChildren(); |
1009 | Q_FALLTHROUGH(); |
1010 | default: |
1011 | return QFrame::event(e); |
1012 | } |
1013 | return true; |
1014 | } |
1015 | |
1016 | /*! |
1017 | \fn bool QAbstractScrollArea::viewportEvent(QEvent *event) |
1018 | |
1019 | The main event handler for the scrolling area (the viewport() widget). |
1020 | It handles the \a event specified, and can be called by subclasses to |
1021 | provide reasonable default behavior. |
1022 | |
1023 | Returns \c true to indicate to the event system that the event has been |
1024 | handled, and needs no further processing; otherwise returns \c false to |
1025 | indicate that the event should be propagated further. |
1026 | |
1027 | You can reimplement this function in a subclass, but we recommend |
1028 | using one of the specialized event handlers instead. |
1029 | |
1030 | Specialized handlers for viewport events are: paintEvent(), |
1031 | mousePressEvent(), mouseReleaseEvent(), mouseDoubleClickEvent(), |
1032 | mouseMoveEvent(), wheelEvent(), dragEnterEvent(), dragMoveEvent(), |
1033 | dragLeaveEvent(), dropEvent(), contextMenuEvent(), and |
1034 | resizeEvent(). |
1035 | */ |
1036 | bool QAbstractScrollArea::viewportEvent(QEvent *e) |
1037 | { |
1038 | switch (e->type()) { |
1039 | case QEvent::Resize: |
1040 | case QEvent::Paint: |
1041 | case QEvent::MouseButtonPress: |
1042 | case QEvent::MouseButtonRelease: |
1043 | case QEvent::MouseButtonDblClick: |
1044 | case QEvent::TouchBegin: |
1045 | case QEvent::TouchUpdate: |
1046 | case QEvent::TouchEnd: |
1047 | case QEvent::MouseMove: |
1048 | case QEvent::ContextMenu: |
1049 | #if QT_CONFIG(wheelevent) |
1050 | case QEvent::Wheel: |
1051 | #endif |
1052 | #if QT_CONFIG(draganddrop) |
1053 | case QEvent::Drop: |
1054 | case QEvent::DragEnter: |
1055 | case QEvent::DragMove: |
1056 | case QEvent::DragLeave: |
1057 | #endif |
1058 | #ifndef QT_NO_OPENGL |
1059 | // QOpenGLWidget needs special support because it has to know |
1060 | // its size has changed, so that it can resize its fbo. |
1061 | if (e->type() == QEvent::Resize) |
1062 | QWidgetPrivate::get(w: viewport())->resizeViewportFramebuffer(); |
1063 | #endif |
1064 | return QFrame::event(e); |
1065 | case QEvent::LayoutRequest: |
1066 | #ifndef QT_NO_GESTURES |
1067 | case QEvent::Gesture: |
1068 | case QEvent::GestureOverride: |
1069 | return event(e); |
1070 | #endif |
1071 | case QEvent::ScrollPrepare: |
1072 | case QEvent::Scroll: |
1073 | return event(e); |
1074 | default: |
1075 | break; |
1076 | } |
1077 | return false; // let the viewport widget handle the event |
1078 | } |
1079 | |
1080 | /*! |
1081 | \fn void QAbstractScrollArea::resizeEvent(QResizeEvent *event) |
1082 | |
1083 | This event handler can be reimplemented in a subclass to receive |
1084 | resize events (passed in \a event), for the viewport() widget. |
1085 | |
1086 | When resizeEvent() is called, the viewport already has its new |
1087 | geometry: Its new size is accessible through the |
1088 | QResizeEvent::size() function, and the old size through |
1089 | QResizeEvent::oldSize(). |
1090 | |
1091 | \sa QWidget::resizeEvent() |
1092 | */ |
1093 | void QAbstractScrollArea::resizeEvent(QResizeEvent *) |
1094 | { |
1095 | } |
1096 | |
1097 | /*! |
1098 | \fn void QAbstractScrollArea::paintEvent(QPaintEvent *event) |
1099 | |
1100 | This event handler can be reimplemented in a subclass to receive |
1101 | paint events (passed in \a event), for the viewport() widget. |
1102 | |
1103 | \note If you create a QPainter, it must operate on the viewport(). |
1104 | |
1105 | \sa QWidget::paintEvent() |
1106 | */ |
1107 | void QAbstractScrollArea::paintEvent(QPaintEvent*) |
1108 | { |
1109 | } |
1110 | |
1111 | /*! |
1112 | This event handler can be reimplemented in a subclass to receive |
1113 | mouse press events for the viewport() widget. The event is passed |
1114 | in \a e. |
1115 | |
1116 | The default implementation calls QWidget::mousePressEvent() for |
1117 | default popup handling. |
1118 | |
1119 | \sa QWidget::mousePressEvent() |
1120 | */ |
1121 | void QAbstractScrollArea::mousePressEvent(QMouseEvent *e) |
1122 | { |
1123 | QWidget::mousePressEvent(event: e); |
1124 | } |
1125 | |
1126 | /*! |
1127 | This event handler can be reimplemented in a subclass to receive |
1128 | mouse release events for the viewport() widget. The event is |
1129 | passed in \a e. |
1130 | |
1131 | \sa QWidget::mouseReleaseEvent() |
1132 | */ |
1133 | void QAbstractScrollArea::mouseReleaseEvent(QMouseEvent *e) |
1134 | { |
1135 | e->ignore(); |
1136 | } |
1137 | |
1138 | /*! |
1139 | This event handler can be reimplemented in a subclass to receive |
1140 | mouse double click events for the viewport() widget. The event is |
1141 | passed in \a e. |
1142 | |
1143 | \sa QWidget::mouseDoubleClickEvent() |
1144 | */ |
1145 | void QAbstractScrollArea::mouseDoubleClickEvent(QMouseEvent *e) |
1146 | { |
1147 | e->ignore(); |
1148 | } |
1149 | |
1150 | /*! |
1151 | This event handler can be reimplemented in a subclass to receive |
1152 | mouse move events for the viewport() widget. The event is passed |
1153 | in \a e. |
1154 | |
1155 | \sa QWidget::mouseMoveEvent() |
1156 | */ |
1157 | void QAbstractScrollArea::mouseMoveEvent(QMouseEvent *e) |
1158 | { |
1159 | e->ignore(); |
1160 | } |
1161 | |
1162 | /*! |
1163 | This event handler can be reimplemented in a subclass to receive |
1164 | wheel events for the viewport() widget. The event is passed in \a |
1165 | e. |
1166 | |
1167 | \sa QWidget::wheelEvent() |
1168 | */ |
1169 | #if QT_CONFIG(wheelevent) |
1170 | void QAbstractScrollArea::wheelEvent(QWheelEvent *e) |
1171 | { |
1172 | Q_D(QAbstractScrollArea); |
1173 | if (qAbs(t: e->angleDelta().x()) > qAbs(t: e->angleDelta().y())) |
1174 | QCoreApplication::sendEvent(receiver: d->hbar, event: e); |
1175 | else |
1176 | QCoreApplication::sendEvent(receiver: d->vbar, event: e); |
1177 | } |
1178 | #endif |
1179 | |
1180 | #ifndef QT_NO_CONTEXTMENU |
1181 | /*! |
1182 | This event handler can be reimplemented in a subclass to receive |
1183 | context menu events for the viewport() widget. The event is passed |
1184 | in \a e. |
1185 | |
1186 | \sa QWidget::contextMenuEvent() |
1187 | */ |
1188 | void QAbstractScrollArea::(QContextMenuEvent *e) |
1189 | { |
1190 | e->ignore(); |
1191 | } |
1192 | #endif // QT_NO_CONTEXTMENU |
1193 | |
1194 | /*! |
1195 | This function is called with key event \a e when key presses |
1196 | occur. It handles PageUp, PageDown, Up, Down, Left, and Right, and |
1197 | ignores all other key presses. |
1198 | */ |
1199 | void QAbstractScrollArea::keyPressEvent(QKeyEvent * e) |
1200 | { |
1201 | Q_D(QAbstractScrollArea); |
1202 | if (false){ |
1203 | #ifndef QT_NO_SHORTCUT |
1204 | } else if (e == QKeySequence::MoveToPreviousPage) { |
1205 | d->vbar->triggerAction(action: QScrollBar::SliderPageStepSub); |
1206 | } else if (e == QKeySequence::MoveToNextPage) { |
1207 | d->vbar->triggerAction(action: QScrollBar::SliderPageStepAdd); |
1208 | #endif |
1209 | } else { |
1210 | #ifdef QT_KEYPAD_NAVIGATION |
1211 | if (QApplicationPrivate::keypadNavigationEnabled() && !hasEditFocus()) { |
1212 | e->ignore(); |
1213 | return; |
1214 | } |
1215 | #endif |
1216 | switch (e->key()) { |
1217 | case Qt::Key_Up: |
1218 | d->vbar->triggerAction(action: QScrollBar::SliderSingleStepSub); |
1219 | break; |
1220 | case Qt::Key_Down: |
1221 | d->vbar->triggerAction(action: QScrollBar::SliderSingleStepAdd); |
1222 | break; |
1223 | case Qt::Key_Left: |
1224 | #ifdef QT_KEYPAD_NAVIGATION |
1225 | if (QApplicationPrivate::keypadNavigationEnabled() && hasEditFocus() |
1226 | && (!d->hbar->isVisible() || d->hbar->value() == d->hbar->minimum())) { |
1227 | //if we aren't using the hbar or we are already at the leftmost point ignore |
1228 | e->ignore(); |
1229 | return; |
1230 | } |
1231 | #endif |
1232 | d->hbar->triggerAction( |
1233 | action: layoutDirection() == Qt::LeftToRight |
1234 | ? QScrollBar::SliderSingleStepSub : QScrollBar::SliderSingleStepAdd); |
1235 | break; |
1236 | case Qt::Key_Right: |
1237 | #ifdef QT_KEYPAD_NAVIGATION |
1238 | if (QApplicationPrivate::keypadNavigationEnabled() && hasEditFocus() |
1239 | && (!d->hbar->isVisible() || d->hbar->value() == d->hbar->maximum())) { |
1240 | //if we aren't using the hbar or we are already at the rightmost point ignore |
1241 | e->ignore(); |
1242 | return; |
1243 | } |
1244 | #endif |
1245 | d->hbar->triggerAction( |
1246 | action: layoutDirection() == Qt::LeftToRight |
1247 | ? QScrollBar::SliderSingleStepAdd : QScrollBar::SliderSingleStepSub); |
1248 | break; |
1249 | default: |
1250 | e->ignore(); |
1251 | return; |
1252 | } |
1253 | } |
1254 | e->accept(); |
1255 | } |
1256 | |
1257 | |
1258 | #if QT_CONFIG(draganddrop) |
1259 | /*! |
1260 | \fn void QAbstractScrollArea::dragEnterEvent(QDragEnterEvent *event) |
1261 | |
1262 | This event handler can be reimplemented in a subclass to receive |
1263 | drag enter events (passed in \a event), for the viewport() widget. |
1264 | |
1265 | \sa QWidget::dragEnterEvent() |
1266 | */ |
1267 | void QAbstractScrollArea::dragEnterEvent(QDragEnterEvent *) |
1268 | { |
1269 | } |
1270 | |
1271 | /*! |
1272 | \fn void QAbstractScrollArea::dragMoveEvent(QDragMoveEvent *event) |
1273 | |
1274 | This event handler can be reimplemented in a subclass to receive |
1275 | drag move events (passed in \a event), for the viewport() widget. |
1276 | |
1277 | \sa QWidget::dragMoveEvent() |
1278 | */ |
1279 | void QAbstractScrollArea::dragMoveEvent(QDragMoveEvent *) |
1280 | { |
1281 | } |
1282 | |
1283 | /*! |
1284 | \fn void QAbstractScrollArea::dragLeaveEvent(QDragLeaveEvent *event) |
1285 | |
1286 | This event handler can be reimplemented in a subclass to receive |
1287 | drag leave events (passed in \a event), for the viewport() widget. |
1288 | |
1289 | \sa QWidget::dragLeaveEvent() |
1290 | */ |
1291 | void QAbstractScrollArea::dragLeaveEvent(QDragLeaveEvent *) |
1292 | { |
1293 | } |
1294 | |
1295 | /*! |
1296 | \fn void QAbstractScrollArea::dropEvent(QDropEvent *event) |
1297 | |
1298 | This event handler can be reimplemented in a subclass to receive |
1299 | drop events (passed in \a event), for the viewport() widget. |
1300 | |
1301 | \sa QWidget::dropEvent() |
1302 | */ |
1303 | void QAbstractScrollArea::dropEvent(QDropEvent *) |
1304 | { |
1305 | } |
1306 | |
1307 | |
1308 | #endif |
1309 | |
1310 | /*! |
1311 | This virtual handler is called when the scroll bars are moved by |
1312 | \a dx, \a dy, and consequently the viewport's contents should be |
1313 | scrolled accordingly. |
1314 | |
1315 | The default implementation simply calls update() on the entire |
1316 | viewport(), subclasses can reimplement this handler for |
1317 | optimization purposes, or - like QScrollArea - to move a contents |
1318 | widget. The parameters \a dx and \a dy are there for convenience, |
1319 | so that the class knows how much should be scrolled (useful |
1320 | e.g. when doing pixel-shifts). You may just as well ignore these |
1321 | values and scroll directly to the position the scroll bars |
1322 | indicate. |
1323 | |
1324 | Calling this function in order to scroll programmatically is an |
1325 | error, use the scroll bars instead (e.g. by calling |
1326 | QScrollBar::setValue() directly). |
1327 | */ |
1328 | void QAbstractScrollArea::scrollContentsBy(int, int) |
1329 | { |
1330 | viewport()->update(); |
1331 | } |
1332 | |
1333 | bool QAbstractScrollAreaPrivate::canStartScrollingAt(const QPoint &startPos) const |
1334 | { |
1335 | Q_Q(const QAbstractScrollArea); |
1336 | |
1337 | // don't start scrolling on a QAbstractSlider |
1338 | if (qobject_cast<QAbstractSlider *>(object: q->viewport()->childAt(p: startPos))) |
1339 | return false; |
1340 | |
1341 | return true; |
1342 | } |
1343 | |
1344 | void QAbstractScrollAreaPrivate::flashScrollBars() |
1345 | { |
1346 | QStyleOptionSlider opt; |
1347 | hbar->initStyleOption(option: &opt); |
1348 | |
1349 | bool htransient = hbar->style()->styleHint(stylehint: QStyle::SH_ScrollBar_Transient, opt: &opt, widget: hbar); |
1350 | if ((hbarpolicy != Qt::ScrollBarAlwaysOff) && (hbarpolicy == Qt::ScrollBarAsNeeded || htransient)) |
1351 | hbar->d_func()->flash(); |
1352 | vbar->initStyleOption(option: &opt); |
1353 | bool vtransient = vbar->style()->styleHint(stylehint: QStyle::SH_ScrollBar_Transient, opt: &opt, widget: vbar); |
1354 | if ((vbarpolicy != Qt::ScrollBarAlwaysOff) && (vbarpolicy == Qt::ScrollBarAsNeeded || vtransient)) |
1355 | vbar->d_func()->flash(); |
1356 | } |
1357 | |
1358 | void QAbstractScrollAreaPrivate::setScrollBarTransient(QScrollBar *scrollBar, bool transient) |
1359 | { |
1360 | scrollBar->d_func()->setTransient(transient); |
1361 | } |
1362 | |
1363 | void QAbstractScrollAreaPrivate::_q_hslide(int x) |
1364 | { |
1365 | Q_Q(QAbstractScrollArea); |
1366 | int dx = xoffset - x; |
1367 | xoffset = x; |
1368 | q->scrollContentsBy(dx, 0); |
1369 | flashScrollBars(); |
1370 | } |
1371 | |
1372 | void QAbstractScrollAreaPrivate::_q_vslide(int y) |
1373 | { |
1374 | Q_Q(QAbstractScrollArea); |
1375 | int dy = yoffset - y; |
1376 | yoffset = y; |
1377 | q->scrollContentsBy(0, dy); |
1378 | flashScrollBars(); |
1379 | } |
1380 | |
1381 | void QAbstractScrollAreaPrivate::_q_showOrHideScrollBars() |
1382 | { |
1383 | layoutChildren(); |
1384 | } |
1385 | |
1386 | QPoint QAbstractScrollAreaPrivate::contentsOffset() const |
1387 | { |
1388 | Q_Q(const QAbstractScrollArea); |
1389 | QPoint offset; |
1390 | if (vbar->isVisible()) |
1391 | offset.setY(vbar->value()); |
1392 | if (hbar->isVisible()) { |
1393 | if (q->isRightToLeft()) |
1394 | offset.setX(hbar->maximum() - hbar->value()); |
1395 | else |
1396 | offset.setX(hbar->value()); |
1397 | } |
1398 | return offset; |
1399 | } |
1400 | |
1401 | /*! |
1402 | \reimp |
1403 | |
1404 | */ |
1405 | QSize QAbstractScrollArea::minimumSizeHint() const |
1406 | { |
1407 | Q_D(const QAbstractScrollArea); |
1408 | int hsbExt = d->hbar->sizeHint().height(); |
1409 | int vsbExt = d->vbar->sizeHint().width(); |
1410 | int = 2 * d->frameWidth; |
1411 | QStyleOption opt; |
1412 | opt.initFrom(w: this); |
1413 | if ((d->frameStyle != QFrame::NoFrame) |
1414 | && style()->styleHint(stylehint: QStyle::SH_ScrollView_FrameOnlyAroundContents, opt: &opt, widget: this)) { |
1415 | extra += style()->pixelMetric(metric: QStyle::PM_ScrollView_ScrollBarSpacing, option: &opt, widget: this); |
1416 | } |
1417 | return QSize(d->scrollBarContainers[Qt::Horizontal]->sizeHint().width() + vsbExt + extra, |
1418 | d->scrollBarContainers[Qt::Vertical]->sizeHint().height() + hsbExt + extra); |
1419 | } |
1420 | |
1421 | /*! |
1422 | Returns the sizeHint property of the scroll area. The size is determined by using |
1423 | viewportSizeHint() plus some extra space for scroll bars, if needed. |
1424 | \reimp |
1425 | */ |
1426 | QSize QAbstractScrollArea::sizeHint() const |
1427 | { |
1428 | Q_D(const QAbstractScrollArea); |
1429 | if (d->sizeAdjustPolicy == QAbstractScrollArea::AdjustIgnored) |
1430 | return QSize(256, 192); |
1431 | |
1432 | if (!d->sizeHint.isValid() || d->sizeAdjustPolicy == QAbstractScrollArea::AdjustToContents) { |
1433 | const int f = 2 * d->frameWidth; |
1434 | const QSize frame(f, f); |
1435 | const bool vbarHidden = !d->vbar->isVisibleTo(this) || d->vbarpolicy == Qt::ScrollBarAlwaysOff; |
1436 | const bool hbarHidden = !d->vbar->isVisibleTo(this) || d->hbarpolicy == Qt::ScrollBarAlwaysOff; |
1437 | const QSize scrollbars(vbarHidden ? 0 : d->vbar->sizeHint().width(), |
1438 | hbarHidden ? 0 : d->hbar->sizeHint().height()); |
1439 | d->sizeHint = frame + scrollbars + viewportSizeHint(); |
1440 | } |
1441 | return d->sizeHint; |
1442 | } |
1443 | |
1444 | /*! |
1445 | \since 5.2 |
1446 | Returns the recommended size for the viewport. |
1447 | The default implementation returns viewport()->sizeHint(). |
1448 | Note that the size is just the viewport's size, without any scroll bars visible. |
1449 | */ |
1450 | QSize QAbstractScrollArea::viewportSizeHint() const |
1451 | { |
1452 | Q_D(const QAbstractScrollArea); |
1453 | if (d->viewport) { |
1454 | const QSize sh = d->viewport->sizeHint(); |
1455 | if (sh.isValid()) { |
1456 | return sh; |
1457 | } |
1458 | } |
1459 | const int h = qMax(a: 10, b: fontMetrics().height()); |
1460 | return QSize(6 * h, 4 * h); |
1461 | } |
1462 | |
1463 | /*! |
1464 | \since 5.2 |
1465 | \property QAbstractScrollArea::sizeAdjustPolicy |
1466 | \brief the policy describing how the size of the scroll area changes when the |
1467 | size of the viewport changes. |
1468 | |
1469 | The default policy is QAbstractScrollArea::AdjustIgnored. |
1470 | Changing this property might actually resize the scrollarea. |
1471 | */ |
1472 | |
1473 | QAbstractScrollArea::SizeAdjustPolicy QAbstractScrollArea::sizeAdjustPolicy() const |
1474 | { |
1475 | Q_D(const QAbstractScrollArea); |
1476 | return d->sizeAdjustPolicy; |
1477 | } |
1478 | |
1479 | void QAbstractScrollArea::setSizeAdjustPolicy(SizeAdjustPolicy policy) |
1480 | { |
1481 | Q_D(QAbstractScrollArea); |
1482 | if (d->sizeAdjustPolicy == policy) |
1483 | return; |
1484 | |
1485 | d->sizeAdjustPolicy = policy; |
1486 | d->sizeHint = QSize(); |
1487 | updateGeometry(); |
1488 | } |
1489 | |
1490 | /*! |
1491 | This slot is called by QAbstractScrollArea after setViewport(\a |
1492 | viewport) has been called. Reimplement this function in a |
1493 | subclass of QAbstractScrollArea to initialize the new \a viewport |
1494 | before it is used. |
1495 | |
1496 | \sa setViewport() |
1497 | */ |
1498 | void QAbstractScrollArea::setupViewport(QWidget *viewport) |
1499 | { |
1500 | Q_UNUSED(viewport); |
1501 | } |
1502 | |
1503 | QT_END_NAMESPACE |
1504 | |
1505 | #include "moc_qabstractscrollarea.cpp" |
1506 | #include "moc_qabstractscrollarea_p.cpp" |
1507 | |
1508 | #endif // QT_CONFIG(scrollarea) |
1509 | |