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