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 test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
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 General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | |
30 | #include <QtTest/QtTest> |
31 | |
32 | #include <qcoreapplication.h> |
33 | #include <qdebug.h> |
34 | #include <qabstractscrollarea.h> |
35 | #include <qscrollarea.h> |
36 | #include <qscrollbar.h> |
37 | #include <qlabel.h> |
38 | #include <qwidget.h> |
39 | #include <qdialog.h> |
40 | #include <qscroller.h> |
41 | |
42 | class tst_QAbstractScrollArea : public QObject |
43 | { |
44 | Q_OBJECT |
45 | |
46 | public: |
47 | tst_QAbstractScrollArea(); |
48 | virtual ~tst_QAbstractScrollArea(); |
49 | private slots: |
50 | void scrollBarWidgets(); |
51 | void setScrollBars(); |
52 | void setScrollBars2(); |
53 | void objectNaming(); |
54 | void patternBackground(); |
55 | |
56 | void viewportCrash(); |
57 | void task214488_layoutDirection_data(); |
58 | void task214488_layoutDirection(); |
59 | |
60 | void margins(); |
61 | void resizeWithOvershoot(); |
62 | }; |
63 | |
64 | tst_QAbstractScrollArea::tst_QAbstractScrollArea() |
65 | { |
66 | } |
67 | |
68 | tst_QAbstractScrollArea::~tst_QAbstractScrollArea() |
69 | { |
70 | } |
71 | |
72 | void tst_QAbstractScrollArea::scrollBarWidgets() |
73 | { |
74 | QWidget *w1 = new QWidget(0); |
75 | QWidget *w2 = new QWidget(0); |
76 | QWidget *w3 = new QWidget(0); |
77 | |
78 | Qt::Alignment all = Qt::AlignLeft | Qt::AlignRight | Qt::AlignTop | Qt::AlignBottom; |
79 | |
80 | QWidgetList w1List = QWidgetList() << w1; |
81 | QWidgetList w2List = QWidgetList() << w2; |
82 | QWidgetList w3List = QWidgetList() << w3; |
83 | |
84 | QWidgetList w1w2List = w1List + w2List; |
85 | QWidgetList allList = w1List + w2List + w3List; |
86 | |
87 | QAbstractScrollArea area; |
88 | area.show(); |
89 | QCOMPARE(area.scrollBarWidgets(all), QWidgetList()); |
90 | |
91 | area.addScrollBarWidget(widget: w1, alignment: Qt::AlignLeft); |
92 | QCOMPARE(area.scrollBarWidgets(all), w1List); |
93 | QCOMPARE(area.scrollBarWidgets(Qt::AlignLeft), w1List); |
94 | QCOMPARE(area.scrollBarWidgets(Qt::AlignRight), QWidgetList()); |
95 | QCOMPARE(area.scrollBarWidgets(Qt::AlignTop), QWidgetList()); |
96 | QCOMPARE(area.scrollBarWidgets(Qt::AlignBottom), QWidgetList()); |
97 | |
98 | area.addScrollBarWidget(widget: w2, alignment: Qt::AlignBottom); |
99 | QCOMPARE(area.scrollBarWidgets(all), w1w2List); |
100 | QCOMPARE(area.scrollBarWidgets(Qt::AlignLeft), w1List); |
101 | QCOMPARE(area.scrollBarWidgets(Qt::AlignRight), QWidgetList()); |
102 | QCOMPARE(area.scrollBarWidgets(Qt::AlignTop), QWidgetList()); |
103 | QCOMPARE(area.scrollBarWidgets(Qt::AlignBottom), w2List); |
104 | |
105 | // duplicate add |
106 | area.addScrollBarWidget(widget: w2, alignment: Qt::AlignBottom); |
107 | QCOMPARE(area.scrollBarWidgets(all), w1w2List); |
108 | QCOMPARE(area.scrollBarWidgets(Qt::AlignLeft), w1List); |
109 | QCOMPARE(area.scrollBarWidgets(Qt::AlignRight), QWidgetList()); |
110 | QCOMPARE(area.scrollBarWidgets(Qt::AlignTop), QWidgetList()); |
111 | QCOMPARE(area.scrollBarWidgets(Qt::AlignBottom), w2List); |
112 | |
113 | //reparent |
114 | w2->setParent(w1); |
115 | QCOMPARE(area.scrollBarWidgets(all), w1List); |
116 | QCOMPARE(area.scrollBarWidgets(Qt::AlignLeft), w1List); |
117 | QCOMPARE(area.scrollBarWidgets(Qt::AlignRight), QWidgetList()); |
118 | QCOMPARE(area.scrollBarWidgets(Qt::AlignTop), QWidgetList()); |
119 | QCOMPARE(area.scrollBarWidgets(Qt::AlignBottom), QWidgetList()); |
120 | |
121 | // add after reparent |
122 | area.addScrollBarWidget(widget: w2, alignment: Qt::AlignBottom); |
123 | QCOMPARE(area.scrollBarWidgets(all), w1w2List); |
124 | QCOMPARE(area.scrollBarWidgets(Qt::AlignLeft), w1List); |
125 | QCOMPARE(area.scrollBarWidgets(Qt::AlignRight), QWidgetList()); |
126 | QCOMPARE(area.scrollBarWidgets(Qt::AlignTop), QWidgetList()); |
127 | QCOMPARE(area.scrollBarWidgets(Qt::AlignBottom), w2List); |
128 | |
129 | auto sort = [](const QWidgetList l) { |
130 | QWidgetList list = l; |
131 | std::sort(first: list.begin(), last: list.end()); |
132 | return list; |
133 | }; |
134 | |
135 | // two widgets at Bottom. |
136 | area.addScrollBarWidget(widget: w3, alignment: Qt::AlignBottom); |
137 | QCOMPARE(sort(area.scrollBarWidgets(all)), sort(allList)); |
138 | QCOMPARE(area.scrollBarWidgets(Qt::AlignLeft), w1List); |
139 | QCOMPARE(area.scrollBarWidgets(Qt::AlignRight), QWidgetList()); |
140 | QCOMPARE(area.scrollBarWidgets(Qt::AlignTop), QWidgetList()); |
141 | QCOMPARE(sort(area.scrollBarWidgets(Qt::AlignBottom)), sort(w2List + w3List)); |
142 | |
143 | //delete |
144 | delete w1; |
145 | delete w2; |
146 | delete w3; |
147 | |
148 | QCOMPARE(area.scrollBarWidgets(all), QWidgetList()); |
149 | QCOMPARE(area.scrollBarWidgets(Qt::AlignLeft), QWidgetList()); |
150 | QCOMPARE(area.scrollBarWidgets(Qt::AlignRight), QWidgetList()); |
151 | QCOMPARE(area.scrollBarWidgets(Qt::AlignTop), QWidgetList()); |
152 | QCOMPARE(area.scrollBarWidgets(Qt::AlignBottom), QWidgetList()); |
153 | } |
154 | |
155 | void tst_QAbstractScrollArea::setScrollBars() |
156 | { |
157 | QScrollArea scrollArea; |
158 | scrollArea.resize(w: 300, h: 300); |
159 | scrollArea.show(); |
160 | |
161 | QPointer<QScrollBar> vbar = scrollArea.verticalScrollBar(); |
162 | QPointer<QScrollBar> hbar = scrollArea.horizontalScrollBar(); |
163 | |
164 | // Now set properties on the scroll bars |
165 | scrollArea.verticalScrollBar()->setInvertedAppearance(true); |
166 | scrollArea.verticalScrollBar()->setInvertedControls(true); |
167 | scrollArea.verticalScrollBar()->setTracking(true); |
168 | scrollArea.verticalScrollBar()->setRange(min: -100, max: 100); |
169 | scrollArea.verticalScrollBar()->setPageStep(42); |
170 | scrollArea.verticalScrollBar()->setSingleStep(3); |
171 | scrollArea.verticalScrollBar()->setValue(43); |
172 | scrollArea.horizontalScrollBar()->setInvertedAppearance(true); |
173 | scrollArea.horizontalScrollBar()->setInvertedControls(true); |
174 | scrollArea.horizontalScrollBar()->setTracking(true); |
175 | scrollArea.horizontalScrollBar()->setRange(min: -100, max: 100); |
176 | scrollArea.horizontalScrollBar()->setPageStep(42); |
177 | scrollArea.horizontalScrollBar()->setSingleStep(3); |
178 | scrollArea.horizontalScrollBar()->setValue(43); |
179 | |
180 | qApp->processEvents(); |
181 | |
182 | // Then replace the scroll bars |
183 | scrollArea.setVerticalScrollBar(new QScrollBar); |
184 | scrollArea.setHorizontalScrollBar(new QScrollBar); |
185 | |
186 | // Check that the old ones were deleted |
187 | QVERIFY(!vbar); |
188 | QVERIFY(!hbar); |
189 | |
190 | qApp->processEvents(); |
191 | |
192 | // Check that all properties have been populated |
193 | QVERIFY(scrollArea.verticalScrollBar()->invertedAppearance()); |
194 | QVERIFY(scrollArea.verticalScrollBar()->invertedControls()); |
195 | QVERIFY(scrollArea.verticalScrollBar()->hasTracking()); |
196 | QVERIFY(scrollArea.verticalScrollBar()->isVisible()); |
197 | QCOMPARE(scrollArea.verticalScrollBar()->minimum(), -100); |
198 | QCOMPARE(scrollArea.verticalScrollBar()->maximum(), 100); |
199 | QCOMPARE(scrollArea.verticalScrollBar()->pageStep(), 42); |
200 | QCOMPARE(scrollArea.verticalScrollBar()->singleStep(), 3); |
201 | QCOMPARE(scrollArea.verticalScrollBar()->value(), 43); |
202 | QVERIFY(scrollArea.horizontalScrollBar()->invertedAppearance()); |
203 | QVERIFY(scrollArea.horizontalScrollBar()->invertedControls()); |
204 | QVERIFY(scrollArea.horizontalScrollBar()->hasTracking()); |
205 | QVERIFY(scrollArea.horizontalScrollBar()->isVisible()); |
206 | QCOMPARE(scrollArea.horizontalScrollBar()->minimum(), -100); |
207 | QCOMPARE(scrollArea.horizontalScrollBar()->maximum(), 100); |
208 | QCOMPARE(scrollArea.horizontalScrollBar()->pageStep(), 42); |
209 | QCOMPARE(scrollArea.horizontalScrollBar()->singleStep(), 3); |
210 | QCOMPARE(scrollArea.horizontalScrollBar()->value(), 43); |
211 | } |
212 | |
213 | void tst_QAbstractScrollArea::setScrollBars2() |
214 | { |
215 | QAbstractScrollArea scrollArea; |
216 | scrollArea.resize(w: 300, h: 300); |
217 | |
218 | QScrollBar *hbar = new QScrollBar; |
219 | scrollArea.setHorizontalScrollBar(hbar); |
220 | qApp->processEvents(); |
221 | QCOMPARE(scrollArea.horizontalScrollBar(), hbar); |
222 | |
223 | QScrollBar *vbar = new QScrollBar; |
224 | scrollArea.setVerticalScrollBar(vbar); |
225 | qApp->processEvents(); |
226 | QCOMPARE(scrollArea.verticalScrollBar(), vbar); |
227 | |
228 | scrollArea.horizontalScrollBar()->setRange(min: 0, max: 100); |
229 | scrollArea.verticalScrollBar()->setRange(min: 0, max: 100); |
230 | scrollArea.show(); |
231 | |
232 | // Make sure scroll bars are not explicitly hidden by QAbstractScrollArea itself. |
233 | QVERIFY(hbar->isVisible()); |
234 | QVERIFY(vbar->isVisible()); |
235 | |
236 | // Hide the OLD scroll bar and ensure that the NEW one is hidden. |
237 | hbar->hide(); |
238 | hbar = new QScrollBar(&scrollArea); |
239 | scrollArea.setHorizontalScrollBar(hbar); |
240 | QVERIFY(!hbar->isVisibleTo(hbar->parentWidget())); |
241 | |
242 | vbar->hide(); |
243 | vbar = new QScrollBar(&scrollArea); |
244 | scrollArea.setVerticalScrollBar(vbar); |
245 | QVERIFY(!vbar->isVisibleTo(vbar->parentWidget())); |
246 | |
247 | vbar->show(); |
248 | hbar->show(); |
249 | |
250 | // Hide the NEW scroll bar and ensure that it's visible |
251 | // (because the OLD one is visible). |
252 | hbar = new QScrollBar; |
253 | hbar->hide(); |
254 | scrollArea.setHorizontalScrollBar(hbar); |
255 | qApp->processEvents(); |
256 | QVERIFY(hbar->isVisible()); |
257 | |
258 | vbar = new QScrollBar; |
259 | vbar->hide(); |
260 | scrollArea.setVerticalScrollBar(vbar); |
261 | qApp->processEvents(); |
262 | QVERIFY(vbar->isVisible()); |
263 | |
264 | vbar->setRange(min: 0, max: 0); |
265 | qApp->processEvents(); |
266 | QVERIFY(!vbar->isVisible()); |
267 | |
268 | hbar->setRange(min: 0, max: 0); |
269 | qApp->processEvents(); |
270 | QVERIFY(!hbar->isVisible()); |
271 | } |
272 | |
273 | // we need to make sure the viewport internal widget is named |
274 | // qt_scrollarea_viewport, otherwise we're going to confuse Squish |
275 | // and friends. |
276 | void tst_QAbstractScrollArea::objectNaming() |
277 | { |
278 | QScrollArea area; |
279 | QCOMPARE(area.viewport()->objectName(), QString("qt_scrollarea_viewport" )); |
280 | } |
281 | |
282 | class ViewportCrashWidget : public QDialog |
283 | { |
284 | public: |
285 | ViewportCrashWidget() |
286 | { |
287 | // temprorary set PaintOnScreen to set the nativeChildrenForced flag. |
288 | setAttribute(Qt::WA_PaintOnScreen, on: true); |
289 | setAttribute(Qt::WA_PaintOnScreen, on: false); |
290 | |
291 | setAttribute(Qt::WA_DropSiteRegistered, on: true); |
292 | |
293 | startTimer(interval: 2000); |
294 | } |
295 | |
296 | void timerEvent(QTimerEvent * /* event */) |
297 | { |
298 | // should not crash. |
299 | (void)new QScrollArea(this); |
300 | accept(); |
301 | } |
302 | }; |
303 | |
304 | void tst_QAbstractScrollArea::viewportCrash() |
305 | { |
306 | ViewportCrashWidget w; |
307 | // should not crash |
308 | w.exec(); |
309 | } |
310 | |
311 | Q_DECLARE_METATYPE(Qt::LayoutDirection); |
312 | Q_DECLARE_METATYPE(Qt::Key); |
313 | |
314 | void tst_QAbstractScrollArea::task214488_layoutDirection_data() |
315 | { |
316 | QTest::addColumn<Qt::LayoutDirection>(name: "dir" ); |
317 | QTest::addColumn<Qt::Key>(name: "key" ); |
318 | QTest::addColumn<bool>(name: "lessThan" ); |
319 | |
320 | QTest::newRow(dataTag: "LTR left" ) << Qt::LeftToRight << Qt::Key_Left << true; |
321 | QTest::newRow(dataTag: "LTR right" ) << Qt::LeftToRight << Qt::Key_Right << false; |
322 | QTest::newRow(dataTag: "RTL left" ) << Qt::RightToLeft << Qt::Key_Left << false; |
323 | QTest::newRow(dataTag: "RTL right" ) << Qt::RightToLeft << Qt::Key_Right << true; |
324 | } |
325 | |
326 | void tst_QAbstractScrollArea::task214488_layoutDirection() |
327 | { |
328 | QScrollArea scrollArea; |
329 | scrollArea.resize(w: 200, h: 200); |
330 | QWidget widget; |
331 | widget.resize(w: 600, h: 600); |
332 | scrollArea.setWidget(&widget); |
333 | scrollArea.show(); |
334 | QScrollBar *hbar = scrollArea.horizontalScrollBar(); |
335 | hbar->setValue((hbar->minimum() + hbar->maximum()) / 2); |
336 | |
337 | QFETCH(Qt::LayoutDirection, dir); |
338 | QFETCH(Qt::Key, key); |
339 | QFETCH(bool, lessThan); |
340 | |
341 | scrollArea.setLayoutDirection(dir); |
342 | |
343 | int refValue = hbar->value(); |
344 | qApp->sendEvent(receiver: &scrollArea, event: new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier)); |
345 | #ifdef Q_OS_WINRT |
346 | QEXPECT_FAIL("" , "WinRT: Scrollbar is not guaranteed to be visible, as QWidget::resize does not" |
347 | "work" , Abort); |
348 | #endif |
349 | QVERIFY(lessThan ? (hbar->value() < refValue) : (hbar->value() > refValue)); |
350 | } |
351 | |
352 | void tst_QAbstractScrollArea::patternBackground() |
353 | { |
354 | QWidget topLevel; |
355 | QScrollArea scrollArea(&topLevel); |
356 | scrollArea.resize(w: 200, h: 200); |
357 | QWidget widget; |
358 | widget.resize(w: 600, h: 600); |
359 | scrollArea.setWidget(&widget); |
360 | topLevel.show(); |
361 | QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); |
362 | |
363 | QLinearGradient linearGrad(QPointF(250, 250), QPointF(300, 300)); |
364 | linearGrad.setColorAt(pos: 0, color: Qt::yellow); |
365 | linearGrad.setColorAt(pos: 1, color: Qt::red); |
366 | QBrush bg(linearGrad); |
367 | scrollArea.viewport()->setPalette(QPalette(Qt::black, bg, bg, bg, bg, bg, bg, bg, bg)); |
368 | widget.setPalette(Qt::transparent); |
369 | |
370 | |
371 | QImage image(200, 200, QImage::Format_ARGB32); |
372 | scrollArea.render(target: &image); |
373 | |
374 | QCOMPARE(image.pixel(QPoint(20,20)) , QColor(Qt::yellow).rgb()); |
375 | |
376 | QScrollBar *hbar = scrollArea.horizontalScrollBar(); |
377 | hbar->setValue(hbar->maximum()); |
378 | QScrollBar *vbar = scrollArea.verticalScrollBar(); |
379 | vbar->setValue(vbar->maximum()); |
380 | |
381 | |
382 | scrollArea.render(target: &image); |
383 | QCOMPARE(image.pixel(QPoint(20,20)) , QColor(Qt::red).rgb()); |
384 | } |
385 | |
386 | class ScrollArea : public QAbstractScrollArea |
387 | { |
388 | public: |
389 | using QAbstractScrollArea::setViewportMargins; |
390 | using QAbstractScrollArea::viewportMargins; |
391 | }; |
392 | |
393 | void tst_QAbstractScrollArea::margins() |
394 | { |
395 | ScrollArea area; |
396 | QCOMPARE(area.viewportMargins(), QMargins()); |
397 | |
398 | QMargins margins(10, 20, 30, 40); |
399 | area.setViewportMargins(margins); |
400 | QCOMPARE(area.viewportMargins(), margins); |
401 | } |
402 | |
403 | void tst_QAbstractScrollArea::resizeWithOvershoot() |
404 | { |
405 | QWidget window; |
406 | |
407 | QScrollArea scrollArea(&window); |
408 | scrollArea.setWidget([]{ |
409 | QWidget *widget = new QWidget; |
410 | widget->setFixedSize(QSize(0, 200)); |
411 | return widget; |
412 | }()); |
413 | scrollArea.setGeometry(ax: 0, ay: 20, aw: 100, ah: 100); |
414 | |
415 | QScroller::grabGesture(target: &scrollArea, gestureType: QScroller::LeftMouseButtonGesture); |
416 | |
417 | window.show(); |
418 | QVERIFY(QTest::qWaitForWindowExposed(&window)); |
419 | |
420 | const QPoint originAtRest = scrollArea.viewport()->pos(); |
421 | |
422 | QPoint center = scrollArea.viewport()->mapToGlobal(scrollArea.viewport()->rect().center()); |
423 | center = window.windowHandle()->mapFromGlobal(pos: center); |
424 | QTest::mousePress(window: window.windowHandle(), button: Qt::LeftButton, stateKey: {}, pos: center); |
425 | QTest::mouseMove(window: window.windowHandle(), pos: center + QPoint(0, 50)); |
426 | QTRY_COMPARE(scrollArea.viewport()->pos(), originAtRest + QPoint(0, 25)); |
427 | QPoint overshootPosition = scrollArea.viewport()->pos(); |
428 | |
429 | // trigger a layout of the scroll area while there's overshoot |
430 | scrollArea.setGeometry(ax: 0, ay: 0, aw: 100, ah: 120); |
431 | QCOMPARE(scrollArea.viewport()->pos(), overshootPosition); |
432 | QTest::mouseRelease(window: window.windowHandle(), button: Qt::LeftButton, stateKey: {}, pos: center + QPoint(0, 50)); |
433 | QTRY_COMPARE(scrollArea.viewport()->pos(), originAtRest); |
434 | // Process a few more events and verify that the scroll area |
435 | // doesn't overcompensate for the overshoot. |
436 | QApplication::processEvents(); |
437 | QTRY_COMPARE(scrollArea.viewport()->pos(), originAtRest); |
438 | } |
439 | |
440 | QTEST_MAIN(tst_QAbstractScrollArea) |
441 | #include "tst_qabstractscrollarea.moc" |
442 | |