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
33
34#include <qapplication.h>
35#include <qpainter.h>
36#include <qstyleoption.h>
37#include <qkeysequence.h>
38#include <qevent.h>
39#include <qgridlayout.h>
40#include <qabstractbutton.h>
41
42#include <private/qguiapplication_p.h>
43#include <qpa/qplatformintegration.h>
44
45class tst_QAbstractButton : public QObject
46{
47 Q_OBJECT
48
49private slots:
50 void initTestCase();
51 void cleanupTestCase();
52 void init();
53
54 void setAutoRepeat_data();
55 void setAutoRepeat();
56
57 void pressed();
58 void released();
59 void setText();
60 void setIcon();
61
62 void setShortcut();
63
64 void animateClick();
65
66 void isCheckable();
67 void setDown();
68 void isChecked();
69 void toggled();
70 void setEnabled();
71 void shortcutEvents();
72 void stopRepeatTimer();
73
74 void mouseReleased(); // QTBUG-53244
75#ifdef QT_KEYPAD_NAVIGATION
76 void keyNavigation();
77#endif
78
79protected slots:
80 void onClicked();
81 void onToggled( bool on );
82 void onPressed();
83 void onReleased();
84 void resetValues();
85
86private:
87 uint click_count;
88 uint toggle_count;
89 uint press_count;
90 uint release_count;
91
92 QAbstractButton *testWidget;
93};
94
95// QAbstractButton is an abstract class in 4.0
96class MyButton : public QAbstractButton
97{
98public:
99 MyButton(QWidget *p = 0) : QAbstractButton(p) {}
100 void paintEvent(QPaintEvent *)
101 {
102 QPainter p(this);
103 QRect r = rect();
104 p.fillRect(r, c: isDown() ? Qt::black : (isChecked() ? Qt::lightGray : Qt::white));
105 p.setPen(isDown() ? Qt::white : Qt::black);
106 p.drawRect(r);
107 p.drawText(r, flags: Qt::AlignCenter | Qt::TextShowMnemonic, text: text());
108 if (hasFocus()) {
109 r.adjust(dx1: 2, dy1: 2, dx2: -2, dy2: -2);
110 QStyleOptionFocusRect opt;
111 opt.rect = r;
112 opt.palette = palette();
113 opt.state = QStyle::State_None;
114 style()->drawPrimitive(pe: QStyle::PE_FrameFocusRect, opt: &opt, p: &p, w: this);
115#ifdef Q_OS_MAC
116 p.setPen(Qt::red);
117 p.drawRect(r);
118#endif
119 }
120 }
121 QSize sizeHint() const
122 {
123 QSize sh(8, 8);
124 if (!text().isEmpty())
125 sh += fontMetrics().boundingRect(text: text()).size();
126 return sh;
127 }
128
129 void resetTimerEvents() { timerEvents = 0; }
130 int timerEventCount() const { return timerEvents; }
131
132private:
133
134 int timerEvents;
135
136 void timerEvent(QTimerEvent *event)
137 {
138 ++timerEvents;
139 QAbstractButton::timerEvent(e: event);
140 }
141};
142
143void tst_QAbstractButton::initTestCase()
144{
145 testWidget = new MyButton(0);
146 testWidget->setObjectName("testObject");
147 testWidget->resize( w: 200, h: 200 );
148 testWidget->show();
149
150 connect( sender: testWidget, SIGNAL(clicked()), receiver: this, SLOT(onClicked()) );
151 connect( sender: testWidget, SIGNAL(pressed()), receiver: this, SLOT(onPressed()) );
152 connect( sender: testWidget, SIGNAL(released()), receiver: this, SLOT(onReleased()) );
153 connect( sender: testWidget, SIGNAL(toggled(bool)), receiver: this, SLOT(onToggled(bool)) );
154}
155
156void tst_QAbstractButton::cleanupTestCase()
157{
158 delete testWidget;
159}
160
161void tst_QAbstractButton::init()
162{
163 testWidget->setText("Test");
164 testWidget->setEnabled( true );
165 testWidget->setDown( false );
166 testWidget->setAutoRepeat( false );
167 QKeySequence seq;
168 testWidget->setShortcut( seq );
169
170 toggle_count = 0;
171 press_count = 0;
172 release_count = 0;
173 click_count = 0;
174}
175
176void tst_QAbstractButton::resetValues()
177{
178 toggle_count = 0;
179 press_count = 0;
180 release_count = 0;
181 click_count = 0;
182}
183
184void tst_QAbstractButton::onClicked()
185{
186 click_count++;
187}
188
189void tst_QAbstractButton::onToggled( bool /*on*/ )
190{
191 toggle_count++;
192}
193
194void tst_QAbstractButton::onPressed()
195{
196 press_count++;
197}
198
199void tst_QAbstractButton::onReleased()
200{
201 release_count++;
202}
203
204void tst_QAbstractButton::setAutoRepeat_data()
205{
206 QTest::addColumn<int>(name: "mode");
207 QTest::newRow(dataTag: "mode 0") << 0;
208 QTest::newRow(dataTag: "mode 1") << 1;
209 QTest::newRow(dataTag: "mode 2") << 2;
210 QTest::newRow(dataTag: "mode 3") << 3;
211 QTest::newRow(dataTag: "mode 4") << 4;
212 QTest::newRow(dataTag: "mode 5") << 5;
213 QTest::newRow(dataTag: "mode 6") << 6;
214}
215
216#define REPEAT_DELAY 1000
217
218int test_count = 0;
219int last_mode = 0;
220
221void tst_QAbstractButton::setAutoRepeat()
222{
223 QFETCH( int, mode );
224
225 //FIXME: temp code to check that the test fails consistenly
226 //retest( 3 );
227
228 switch (mode)
229 {
230 case 0:
231 QVERIFY( !testWidget->isCheckable() );
232 break;
233 case 1:
234 // check if we can toggle the mode
235 testWidget->setAutoRepeat( true );
236 QVERIFY( testWidget->autoRepeat() );
237
238 testWidget->setAutoRepeat( false );
239 QVERIFY( !testWidget->autoRepeat() );
240 break;
241 case 2:
242 // check that the button is down if we press space and not in autorepeat
243 testWidget->setDown( false );
244 testWidget->setAutoRepeat( false );
245 QTest::keyPress( widget: testWidget, key: Qt::Key_Space );
246
247 QTest::qWait( REPEAT_DELAY );
248
249 QVERIFY( release_count == 0 );
250 QVERIFY( testWidget->isDown() );
251 QVERIFY( toggle_count == 0 );
252 QVERIFY( press_count == 1 );
253 QVERIFY( click_count == 0 );
254
255 QTest::keyRelease( widget: testWidget, key: Qt::Key_Space );
256 QVERIFY( click_count == 1 );
257 QVERIFY( release_count == 1 );
258 break;
259 case 3:
260 // check that the button is down if we press space while in autorepeat
261 testWidget->setDown(false);
262 testWidget->setAutoRepeat(true);
263 QTest::keyPress(widget: testWidget, key: Qt::Key_Space);
264 QTest::qWait(REPEAT_DELAY);
265 QVERIFY(testWidget->isDown());
266 QTest::keyRelease(widget: testWidget, key: Qt::Key_Space);
267 QCOMPARE(release_count, press_count);
268 QCOMPARE(toggle_count, uint(0));
269 QCOMPARE(press_count, click_count);
270 QVERIFY(click_count > 1);
271 break;
272 case 4:
273 // check that pressing ENTER has no effect when autorepeat is false
274 testWidget->setDown( false );
275 testWidget->setAutoRepeat( false );
276 QTest::keyPress( widget: testWidget, key: Qt::Key_Enter );
277
278 QTest::qWait( REPEAT_DELAY );
279
280 QVERIFY( !testWidget->isDown() );
281 QVERIFY( toggle_count == 0 );
282 QVERIFY( press_count == 0 );
283 QVERIFY( release_count == 0 );
284 QVERIFY( click_count == 0 );
285 QTest::keyRelease( widget: testWidget, key: Qt::Key_Enter );
286
287 QVERIFY( click_count == 0 );
288 break;
289 case 5:
290 // check that pressing ENTER has no effect when autorepeat is true
291 testWidget->setDown( false );
292 testWidget->setAutoRepeat( true );
293 QTest::keyPress( widget: testWidget, key: Qt::Key_Enter );
294
295 QTest::qWait( REPEAT_DELAY );
296
297 QVERIFY( !testWidget->isDown() );
298 QVERIFY( toggle_count == 0 );
299 QVERIFY( press_count == 0 );
300 QVERIFY( release_count == 0 );
301 QVERIFY( click_count == 0 );
302
303 QTest::keyRelease( widget: testWidget, key: Qt::Key_Enter );
304
305 QVERIFY( click_count == 0 );
306 break;
307 case 6:
308 // verify autorepeat is off by default.
309 MyButton tmp( 0);
310 tmp.setObjectName("tmp" );
311 QVERIFY( !tmp.autoRepeat() );
312 break;
313 }
314}
315
316void tst_QAbstractButton::pressed()
317{
318 // pressed/released signals expected for a QAbstractButton
319 QTest::keyPress( widget: testWidget, key: ' ' );
320 QCOMPARE( press_count, (uint)1 );
321}
322
323void tst_QAbstractButton::released()
324{
325 // pressed/released signals expected for a QAbstractButton
326 QTest::keyPress( widget: testWidget, key: ' ' );
327 QTest::keyRelease( widget: testWidget, key: ' ' );
328 QCOMPARE( release_count, (uint)1 );
329}
330
331void tst_QAbstractButton::setText()
332{
333 testWidget->setText("");
334 QCOMPARE( testWidget->text(), QString("") );
335 testWidget->setText("simple");
336 QCOMPARE( testWidget->text(), QString("simple") );
337 testWidget->setText("&ampersand");
338 QCOMPARE( testWidget->text(), QString("&ampersand") );
339#ifndef Q_OS_MAC // no mneonics on Mac.
340 QCOMPARE( testWidget->shortcut(), QKeySequence("ALT+A"));
341#endif
342 testWidget->setText("te&st");
343 QCOMPARE( testWidget->text(), QString("te&st") );
344#ifndef Q_OS_MAC // no mneonics on Mac.
345 QCOMPARE( testWidget->shortcut(), QKeySequence("ALT+S"));
346#endif
347 testWidget->setText("foo");
348 QCOMPARE( testWidget->text(), QString("foo") );
349#ifndef Q_OS_MAC // no mneonics on Mac.
350 QCOMPARE( testWidget->shortcut(), QKeySequence());
351#endif
352}
353
354void tst_QAbstractButton::setIcon()
355{
356 const char *test1_xpm[] = {
357 "12 8 2 1",
358 ". c None",
359 "c c #ff0000",
360 ".........ccc",
361 "........ccc.",
362 ".......ccc..",
363 "ccc...ccc...",
364 ".ccc.ccc....",
365 "..ccccc.....",
366 "...ccc......",
367 "....c.......",
368 };
369
370 QPixmap p(test1_xpm);
371 testWidget->setIcon( p );
372 QCOMPARE( testWidget->icon().pixmap(12, 8), p );
373
374 // Test for #14793
375
376 const char *test2_xpm[] = {
377 "12 8 2 1",
378 ". c None",
379 "c c #ff0000",
380 "ccc......ccc",
381 ".ccc....ccc.",
382 "..ccc..ccc..",
383 "....cc.cc...",
384 ".....ccc....",
385 "....cc.cc...",
386 "...ccc.ccc..",
387 "..ccc...ccc.",
388 };
389
390 int currentHeight = testWidget->height();
391 int currentWidth = testWidget->width();
392
393 QPixmap p2( test2_xpm );
394 for ( int a = 0; a<5; a++ )
395 testWidget->setIcon( p2 );
396
397 QCOMPARE( testWidget->icon().pixmap(12, 8), p2 );
398
399 QCOMPARE( testWidget->height(), currentHeight );
400 QCOMPARE( testWidget->width(), currentWidth );
401}
402
403void tst_QAbstractButton::setEnabled()
404{
405 testWidget->setEnabled( false );
406 QVERIFY( !testWidget->isEnabled() );
407// QTEST( testWidget, "disabled" );
408
409 testWidget->setEnabled( true );
410 QVERIFY( testWidget->isEnabled() );
411// QTEST( testWidget, "enabled" );
412}
413
414void tst_QAbstractButton::isCheckable()
415{
416 QVERIFY( !testWidget->isCheckable() );
417}
418
419void tst_QAbstractButton::setDown()
420{
421 testWidget->setDown( false );
422 QVERIFY( !testWidget->isDown() );
423
424 testWidget->setDown( true );
425 QTest::qWait(ms: 300);
426 QVERIFY( testWidget->isDown() );
427
428 testWidget->setDown( true );
429
430 // add some debugging stuff
431 QWidget *grab = QWidget::keyboardGrabber();
432 if (grab != 0 && grab != testWidget)
433 qDebug( msg: "testWidget != keyboardGrabber" );
434 grab = qApp->focusWidget();
435 if (grab != 0 && grab != testWidget)
436 qDebug( msg: "testWidget != focusWidget" );
437
438 QTest::keyClick( widget: testWidget, key: Qt::Key_Escape );
439 QVERIFY( !testWidget->isDown() );
440}
441
442void tst_QAbstractButton::isChecked()
443{
444 testWidget->setDown( false );
445 QVERIFY( !testWidget->isChecked() );
446
447 testWidget->setDown( true );
448 QVERIFY( !testWidget->isChecked() );
449
450 testWidget->setDown( false );
451 testWidget->toggle();
452 QVERIFY( testWidget->isChecked() == testWidget->isCheckable() );
453}
454
455void tst_QAbstractButton::toggled()
456{
457 testWidget->toggle();
458 QVERIFY( toggle_count == 0 );
459
460 QTest::mousePress( widget: testWidget, button: Qt::LeftButton );
461 QVERIFY( toggle_count == 0 );
462 QVERIFY( click_count == 0 );
463
464 QTest::mouseRelease( widget: testWidget, button: Qt::LeftButton );
465 QVERIFY( click_count == 1 );
466
467 testWidget->setCheckable(true);
468 testWidget->toggle();
469 testWidget->toggle();
470 QCOMPARE(int(toggle_count), 2);
471 testWidget->setCheckable(false);
472}
473
474void tst_QAbstractButton::setShortcut()
475{
476 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
477 QSKIP("Window activation is not supported");
478
479 QKeySequence seq( Qt::Key_A );
480 testWidget->setShortcut( seq );
481 QApplication::setActiveWindow(testWidget);
482 testWidget->activateWindow();
483 // must be active to get shortcuts
484 QVERIFY(QTest::qWaitForWindowActive(testWidget));
485
486 QTest::keyClick( widget: testWidget, key: 'A' );
487 QTest::qWait(ms: 300); // Animate click takes time
488 QCOMPARE(click_count, (uint)1);
489 QCOMPARE(press_count, (uint)1); // Press is part of a click
490 QCOMPARE(release_count,(uint)1); // Release is part of a click
491
492 QVERIFY( toggle_count == 0 );
493
494// resetValues();
495// QTest::keyPress( testWidget, 'A' );
496// QTest::qWait(10000);
497// QTest::keyRelease( testWidget, 'A' );
498// QCOMPARE(click_count, (uint)1);
499// QCOMPARE(press_count, (uint)1);
500// QCOMPARE(release_count,(uint)1);
501
502// qDebug() << click_count;
503
504}
505
506void tst_QAbstractButton::animateClick()
507{
508 testWidget->animateClick();
509 QVERIFY( testWidget->isDown() );
510 qApp->processEvents();
511 QVERIFY( testWidget->isDown() );
512 QTRY_VERIFY( !testWidget->isDown() );
513}
514
515void tst_QAbstractButton::shortcutEvents()
516{
517 MyButton button;
518 QSignalSpy pressedSpy(&button, SIGNAL(pressed()));
519 QSignalSpy releasedSpy(&button, SIGNAL(released()));
520 QSignalSpy clickedSpy(&button, SIGNAL(clicked(bool)));
521
522 for (int i = 0; i < 4; ++i) {
523 QKeySequence sequence;
524 // Default shortcutId for QAbstractButton is 0, so the shortcut event will work.
525 QShortcutEvent event(sequence, /*shortcutId*/ 0);
526 QApplication::sendEvent(receiver: &button, event: &event);
527 if (i < 2)
528 QTest::qWait(ms: 500);
529 }
530
531 QTest::qWait(ms: 1000); // ensure animate timer is expired
532
533 QCOMPARE(pressedSpy.count(), 3);
534 QCOMPARE(releasedSpy.count(), 3);
535 QCOMPARE(clickedSpy.count(), 3);
536}
537
538void tst_QAbstractButton::stopRepeatTimer()
539{
540 MyButton button;
541 button.setAutoRepeat(true);
542
543 // Mouse trigger case:
544 button.resetTimerEvents();
545 QTest::mousePress(widget: &button, button: Qt::LeftButton);
546 QTest::qWait(ms: 1000);
547 QVERIFY(button.timerEventCount() > 0);
548
549 QTest::mouseRelease(widget: &button, button: Qt::LeftButton);
550 button.resetTimerEvents();
551 QTest::qWait(ms: 1000);
552 QCOMPARE(button.timerEventCount(), 0);
553
554 // Key trigger case:
555 button.resetTimerEvents();
556 QTest::keyPress(widget: &button, key: Qt::Key_Space);
557 QTest::qWait(ms: 1000);
558 QVERIFY(button.timerEventCount() > 0);
559
560 QTest::keyRelease(widget: &button, key: Qt::Key_Space);
561 button.resetTimerEvents();
562 QTest::qWait(ms: 1000);
563 QCOMPARE(button.timerEventCount(), 0);
564}
565
566void tst_QAbstractButton::mouseReleased() // QTBUG-53244
567{
568 MyButton button(nullptr);
569 button.setObjectName("button");
570 button.setGeometry(ax: 0, ay: 0, aw: 20, ah: 20);
571 QSignalSpy spyPress(&button, &QAbstractButton::pressed);
572 QSignalSpy spyRelease(&button, &QAbstractButton::released);
573
574 QTest::mousePress(widget: &button, button: Qt::LeftButton);
575 QCOMPARE(spyPress.count(), 1);
576 QCOMPARE(button.isDown(), true);
577 QCOMPARE(spyRelease.count(), 0);
578
579 QTest::mouseClick(widget: &button, button: Qt::RightButton);
580 QCOMPARE(spyPress.count(), 1);
581 QCOMPARE(button.isDown(), true);
582 QCOMPARE(spyRelease.count(), 0);
583
584 QPointF posOutOfWidget = QPointF(30, 30);
585 QMouseEvent me(QEvent::MouseMove,
586 posOutOfWidget, Qt::NoButton,
587 Qt::MouseButtons(Qt::LeftButton),
588 Qt::NoModifier); // mouse press and move
589
590 qApp->sendEvent(receiver: &button, event: &me);
591 // should emit released signal once mouse is dragging out of boundary
592 QCOMPARE(spyPress.count(), 1);
593 QCOMPARE(button.isDown(), false);
594 QCOMPARE(spyRelease.count(), 1);
595}
596
597#ifdef QT_KEYPAD_NAVIGATION
598void tst_QAbstractButton::keyNavigation()
599{
600 QApplication::setNavigationMode(Qt::NavigationModeKeypadDirectional);
601
602 QWidget widget;
603 QGridLayout *layout = new QGridLayout(&widget);
604 QAbstractButton *buttons[3][3];
605 for(int y = 0; y < 3; y++) {
606 for(int x = 0; x < 3; x++) {
607 buttons[y][x] = new MyButton(&widget);
608 buttons[y][x]->setFocusPolicy(Qt::StrongFocus);
609 layout->addWidget(buttons[y][x], y, x);
610 }
611 }
612
613 widget.show();
614 qApp->setActiveWindow(&widget);
615 widget.activateWindow();
616 QVERIFY(QTest::qWaitForWindowActive(&widget));
617
618 buttons[1][1]->setFocus();
619 QTest::qWait(400);
620 QVERIFY(buttons[1][1]->hasFocus());
621 QTest::keyPress(buttons[1][1], Qt::Key_Up);
622 QTest::qWait(100);
623 QVERIFY(buttons[0][1]->hasFocus());
624 QTest::keyPress(buttons[0][1], Qt::Key_Down);
625 QTest::qWait(100);
626 QVERIFY(buttons[1][1]->hasFocus());
627 QTest::keyPress(buttons[1][1], Qt::Key_Left);
628 QTest::qWait(100);
629 QVERIFY(buttons[1][0]->hasFocus());
630 QTest::keyPress(buttons[1][0], Qt::Key_Down);
631 QTest::qWait(100);
632 QVERIFY(buttons[2][0]->hasFocus());
633 QTest::keyPress(buttons[2][0], Qt::Key_Right);
634 QTest::qWait(100);
635 QVERIFY(buttons[2][1]->hasFocus());
636 QTest::keyPress(buttons[2][1], Qt::Key_Right);
637 QTest::qWait(100);
638 QVERIFY(buttons[2][2]->hasFocus());
639 QTest::keyPress(buttons[2][2], Qt::Key_Up);
640 QTest::qWait(100);
641 QVERIFY(buttons[1][2]->hasFocus());
642 QTest::keyPress(buttons[1][2], Qt::Key_Up);
643 QTest::qWait(100);
644 QVERIFY(buttons[0][2]->hasFocus());
645 buttons[0][1]->hide();
646 QTest::keyPress(buttons[0][2], Qt::Key_Left);
647 QTest::qWait(100);
648 QTest::keyPress(buttons[0][2], Qt::Key_Left);
649 QEXPECT_FAIL("", "QTBUG-22286" ,Abort);
650 QVERIFY(buttons[0][0]->hasFocus());
651}
652#endif
653
654QTEST_MAIN(tst_QAbstractButton)
655#include "tst_qabstractbutton.moc"
656

source code of qtbase/tests/auto/widgets/widgets/qabstractbutton/tst_qabstractbutton.cpp