File indexing completed on 2024-05-19 05:35:24

0001 // krazy:excludeall=qclasses
0002 
0003 //////////////////////////////////////////////////////////////////////////////
0004 // oxygensimulator.cpp
0005 // simulates event chain passed to the application
0006 // -------------------
0007 //
0008 // SPDX-FileCopyrightText: 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr>
0009 //
0010 // SPDX-License-Identifier: MIT
0011 //////////////////////////////////////////////////////////////////////////////
0012 
0013 #include "oxygensimulator.h"
0014 
0015 #include <QAbstractItemView>
0016 #include <QApplication>
0017 #include <QCheckBox>
0018 #include <QComboBox>
0019 #include <QCursor>
0020 #include <QFocusEvent>
0021 #include <QHoverEvent>
0022 #include <QMdiSubWindow>
0023 #include <QMenu>
0024 #include <QMouseEvent>
0025 #include <QPushButton>
0026 #include <QRadioButton>
0027 #include <QScrollBar>
0028 #include <QSlider>
0029 #include <QStyle>
0030 #include <QStyleOptionComboBox>
0031 #include <QStyleOptionSlider>
0032 #include <QToolButton>
0033 
0034 #ifdef Q_OS_WIN
0035 /* need windows.h include for Sleep function*/
0036 #include <windows.h>
0037 #endif
0038 
0039 #ifdef Q_OS_UNIX
0040 #include <ctime>
0041 #endif
0042 
0043 namespace Oxygen
0044 {
0045 //_______________________________________________________________________
0046 bool Simulator::_grabMouse = true;
0047 int Simulator::_defaultDelay = 250;
0048 
0049 //_______________________________________________________________________
0050 void Simulator::wait(int delay)
0051 {
0052     _events.append(Event(Event::Wait, nullptr, delay));
0053 }
0054 
0055 //_______________________________________________________________________
0056 void Simulator::click(QWidget *receiver, int delay)
0057 {
0058     QPoint position;
0059     if (QCheckBox *checkbox = qobject_cast<QCheckBox *>(receiver)) {
0060         QStyleOptionButton option;
0061         option.initFrom(checkbox);
0062         position = checkbox->style()->subElementRect(QStyle::SE_CheckBoxIndicator, &option, checkbox).center();
0063 
0064     } else if (QRadioButton *radiobutton = qobject_cast<QRadioButton *>(receiver)) {
0065         QStyleOptionButton option;
0066         option.initFrom(radiobutton);
0067         position = radiobutton->style()->subElementRect(QStyle::SE_RadioButtonIndicator, &option, radiobutton).center();
0068 
0069     } else if (const QMdiSubWindow *window = qobject_cast<QMdiSubWindow *>(receiver)) {
0070         QStyleOptionTitleBar option;
0071         option.initFrom(window);
0072         int titleBarHeight(window->style()->pixelMetric(QStyle::PM_TitleBarHeight, &option, window));
0073         QRect titleBarRect(QPoint(0, 0), QSize(window->width(), titleBarHeight));
0074         if (!titleBarRect.isValid())
0075             return;
0076         position = titleBarRect.center();
0077 
0078     } else {
0079         position = receiver->rect().center();
0080     }
0081 
0082     click(receiver, position, delay);
0083 }
0084 
0085 //_______________________________________________________________________
0086 void Simulator::click(QWidget *receiver, const QPoint &position, int delay)
0087 {
0088     Event event(Event::Click, receiver, delay);
0089     event._position = position;
0090     _events.append(event);
0091 }
0092 
0093 //_______________________________________________________________________
0094 void Simulator::slide(QWidget *receiver, const QPoint &position, int delay)
0095 {
0096     Event event(Event::Slide, receiver, delay);
0097     event._position = position;
0098     _events.append(event);
0099 }
0100 
0101 //_______________________________________________________________________
0102 void Simulator::selectItem(QWidget *receiver, int row, int column, int delay)
0103 {
0104     Event event(Event::SelectItem, receiver, delay);
0105     event._position = QPoint(column, row);
0106     _events.append(event);
0107 }
0108 
0109 //_______________________________________________________________________
0110 void Simulator::selectComboBoxItem(QWidget *receiver, int index, int delay)
0111 {
0112     Event event(Event::SelectComboBoxItem, receiver, delay);
0113     event._position.setX(index);
0114     _events.append(event);
0115 }
0116 
0117 //_______________________________________________________________________
0118 void Simulator::selectMenuItem(QWidget *receiver, int index, int delay)
0119 {
0120     Event event(Event::SelectMenuItem, receiver, delay);
0121     event._position.setX(index);
0122     _events.append(event);
0123 }
0124 
0125 //_______________________________________________________________________
0126 void Simulator::selectTab(QTabWidget *tabwidget, int index, int delay)
0127 {
0128     const auto children = tabwidget->children();
0129     for (QObject *child : children) {
0130         if (QTabBar *tabbar = qobject_cast<QTabBar *>(child)) {
0131             selectTab(tabbar, index, delay);
0132             break;
0133         }
0134     }
0135 }
0136 
0137 //_______________________________________________________________________
0138 void Simulator::selectTab(QTabBar *receiver, int index, int delay)
0139 {
0140     Event event(Event::SelectTab, receiver, delay);
0141     event._position.setX(index);
0142     _events.append(event);
0143 }
0144 
0145 //_______________________________________________________________________
0146 void Simulator::writeText(QWidget *receiver, QString text, int delay)
0147 {
0148     Event event(Event::WriteText, receiver, delay);
0149     event._text = text;
0150     _events.append(event);
0151 }
0152 
0153 //_______________________________________________________________________
0154 void Simulator::clearText(QWidget *receiver, int delay)
0155 {
0156     _events.append(Event(Event::ClearText, receiver, delay));
0157 }
0158 
0159 //_______________________________________________________________________
0160 void Simulator::run(void)
0161 {
0162     if (_events.isEmpty())
0163         return;
0164 
0165     // clear abort state
0166     _aborted = false;
0167 
0168     emit stateChanged(true);
0169 
0170     for (const Event &event : std::as_const(_events)) {
0171         if (_aborted) {
0172             _events.clear();
0173             return;
0174         }
0175 
0176         processEvent(event);
0177     }
0178 
0179     // add last event to reset previousWidget and previousPosition
0180     if (_previousWidget) {
0181         postEvent(_previousWidget.data(), QEvent::Leave);
0182         if (_previousWidget.data()->testAttribute(Qt::WA_Hover)) {
0183             const QPoint oldPosition(_previousWidget.data()->mapFromGlobal(_previousPosition));
0184             const QPoint newPosition(_previousWidget.data()->mapFromGlobal(QPoint(-1, -1)));
0185             postHoverEvent(_previousWidget.data(), QEvent::HoverLeave, newPosition, oldPosition);
0186         }
0187 
0188         _previousWidget.clear();
0189         _previousPosition = QPoint(-1, -1);
0190     }
0191 
0192     _events.clear();
0193     emit stateChanged(false);
0194 
0195     return;
0196 }
0197 
0198 //_______________________________________________________________________
0199 void Simulator::abort(void)
0200 {
0201     _aborted = true;
0202     emit stateChanged(true);
0203 }
0204 
0205 //_______________________________________________________________________
0206 void Simulator::timerEvent(QTimerEvent *event)
0207 {
0208     if (event->timerId() == _timer.timerId()) {
0209         _timer.stop();
0210 
0211     } else if (event->timerId() == _pendingEventsTimer.timerId()) {
0212         if (_aborted) {
0213             qDeleteAll(_pendingEvents);
0214 
0215             _pendingEvents.clear();
0216             _pendingWidget.clear();
0217 
0218         } else if (_pendingWidget && _pendingWidget.data()->isVisible()) {
0219             _pendingEventsTimer.stop();
0220             for (QEvent *event : std::as_const(_pendingEvents)) {
0221                 if (event->type() == QEvent::MouseMove) {
0222                     QPoint position(static_cast<QMouseEvent *>(event)->pos());
0223                     moveCursor(_pendingWidget.data()->mapToGlobal(position));
0224                 }
0225 
0226                 postQEvent(_pendingWidget.data(), event);
0227                 postDelay(150);
0228             }
0229 
0230             _pendingEvents.clear();
0231             _pendingWidget.clear();
0232         }
0233 
0234     } else
0235         return QObject::timerEvent(event);
0236 }
0237 
0238 //_______________________________________________________________________
0239 void Simulator::processEvent(const Event &event)
0240 {
0241     if (_aborted)
0242         return;
0243     if (!event._receiver) {
0244         if (event._type == Event::Wait) {
0245             postDelay(event._delay);
0246         }
0247 
0248         return;
0249     }
0250 
0251     QWidget *receiver(event._receiver.data());
0252     switch (event._type) {
0253     // click event
0254     case Event::Click: {
0255         // enter widget or move cursor to relevant position
0256         if (!enter(receiver, event._position, event._delay)) {
0257             moveCursor(receiver->mapToGlobal(event._position));
0258             postMouseEvent(receiver, QEvent::MouseMove, Qt::NoButton, event._position);
0259         }
0260 
0261         postMouseClickEvent(receiver, Qt::LeftButton, event._position);
0262         break;
0263     }
0264 
0265     // slide
0266     case Event::Slide: {
0267         const QPoint &delta(event._position);
0268 
0269         // calculate begin position depending on widget type
0270         QPoint begin;
0271         if (const QSlider *slider = qobject_cast<QSlider *>(receiver)) {
0272             // this is copied from QSlider::initStyleOption
0273             QStyleOptionSlider option;
0274             option.initFrom(slider);
0275             option.orientation = slider->orientation();
0276             option.sliderPosition = slider->sliderPosition();
0277             option.minimum = slider->minimum();
0278             option.maximum = slider->maximum();
0279             option.upsideDown = (slider->orientation() == Qt::Horizontal) ? (slider->invertedAppearance() != (option.direction == Qt::RightToLeft))
0280                                                                           : (!slider->invertedAppearance());
0281 
0282             QRect handleRect(slider->style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, slider));
0283 
0284             if (!handleRect.isValid())
0285                 break;
0286             begin = handleRect.center();
0287 
0288         } else if (const QScrollBar *scrollbar = qobject_cast<QScrollBar *>(receiver)) {
0289             // this is copied from QSlider::initStyleOption
0290             QStyleOptionSlider option;
0291             option.initFrom(scrollbar);
0292             option.orientation = scrollbar->orientation();
0293             option.sliderPosition = scrollbar->sliderPosition();
0294             option.minimum = scrollbar->minimum();
0295             option.maximum = scrollbar->maximum();
0296             option.upsideDown = scrollbar->invertedAppearance();
0297             if (scrollbar->orientation() == Qt::Horizontal) {
0298                 option.state |= QStyle::State_Horizontal;
0299             }
0300 
0301             QRect handleRect(scrollbar->style()->subControlRect(QStyle::CC_ScrollBar, &option, QStyle::SC_ScrollBarSlider, scrollbar));
0302 
0303             if (!handleRect.isValid())
0304                 break;
0305             begin = handleRect.center();
0306 
0307         } else if (const QMdiSubWindow *window = qobject_cast<QMdiSubWindow *>(receiver)) {
0308             QStyleOptionTitleBar option;
0309             option.initFrom(window);
0310             int titleBarHeight(window->style()->pixelMetric(QStyle::PM_TitleBarHeight, &option, window));
0311             QRect titleBarRect(QPoint(0, 0), QSize(window->width(), titleBarHeight));
0312             if (!titleBarRect.isValid())
0313                 break;
0314             begin = titleBarRect.center();
0315 
0316         } else {
0317             begin = receiver->rect().center();
0318         }
0319 
0320         // enter widget or move cursor to relevant position
0321         if (!enter(receiver, begin, event._delay)) {
0322             moveCursor(receiver->mapToGlobal(begin));
0323             postMouseEvent(receiver, QEvent::MouseMove, Qt::NoButton, begin);
0324         }
0325 
0326         const QPoint end(begin + delta);
0327         postMouseEvent(receiver, QEvent::MouseButtonPress, Qt::LeftButton, begin, Qt::LeftButton);
0328         setFocus(receiver);
0329         postDelay(50);
0330         const int steps = 10;
0331         for (int i = 0; i < steps; ++i) {
0332             QPoint current(begin.x() + qreal((i + 1) * (end.x() - begin.x())) / steps, begin.y() + qreal((i + 1) * (end.y() - begin.y())) / steps);
0333             moveCursor(receiver->mapToGlobal(current), 1);
0334             postMouseEvent(receiver, QEvent::MouseMove, Qt::NoButton, current, Qt::LeftButton, Qt::NoModifier);
0335             postDelay(20);
0336         }
0337 
0338         postMouseEvent(receiver, QEvent::MouseButtonRelease, Qt::LeftButton, end);
0339         break;
0340     }
0341 
0342     case Event::SelectItem: {
0343         const QAbstractItemView *view = qobject_cast<QAbstractItemView *>(receiver);
0344         if (!(view && view->model()))
0345             break;
0346 
0347         const int column(event._position.x());
0348         const int row(event._position.y());
0349 
0350         // find index
0351         const QModelIndex modelIndex(view->model()->index(row, column));
0352         if (!modelIndex.isValid())
0353             break;
0354 
0355         // get rect
0356         QRect r(view->visualRect(modelIndex));
0357         if (!r.isValid())
0358             break;
0359 
0360         // enter widget or move cursor to relevant position
0361         const QPoint position(r.center());
0362         if (!enter(view->viewport(), position, event._delay)) {
0363             moveCursor(view->viewport()->mapToGlobal(position));
0364             postMouseEvent(view->viewport(), QEvent::MouseMove, Qt::NoButton, position);
0365             postDelay(event._delay);
0366         }
0367 
0368         postMouseClickEvent(view->viewport(), Qt::LeftButton, position);
0369         break;
0370     }
0371 
0372     case Event::SelectComboBoxItem: {
0373         QComboBox *combobox = qobject_cast<QComboBox *>(receiver);
0374         if (!combobox)
0375             break;
0376 
0377         // get arrow rect
0378         QStyleOptionComboBox option;
0379         option.initFrom(combobox);
0380         QRect arrowRect(combobox->style()->subControlRect(QStyle::CC_ComboBox, &option, QStyle::SC_ComboBoxArrow, combobox));
0381 
0382         // enter widget or move cursor to relevant position
0383         QPoint position(arrowRect.center());
0384         if (!enter(combobox, position, event._delay)) {
0385             moveCursor(combobox->mapToGlobal(position));
0386             postMouseEvent(combobox, QEvent::MouseMove, Qt::NoButton, position);
0387             postDelay(event._delay);
0388         }
0389 
0390         postMouseClickEvent(combobox, Qt::LeftButton, position);
0391 
0392         // select item in view
0393         QAbstractItemView *view = combobox->view();
0394         const int row(event._position.x());
0395         const int column(0);
0396 
0397         // find index
0398         const QModelIndex modelIndex(view->model()->index(row, column));
0399         if (!modelIndex.isValid())
0400             break;
0401 
0402         // get rect
0403         QRect r(view->visualRect(modelIndex));
0404         if (!r.isValid())
0405             break;
0406 
0407         // send event
0408         position = QPoint(r.center());
0409         moveCursor(view->viewport()->mapToGlobal(position));
0410         postMouseEvent(view->viewport(), QEvent::MouseMove, Qt::NoButton, position, Qt::NoButton, Qt::NoModifier);
0411         postDelay(100);
0412         postMouseClickEvent(view->viewport(), Qt::LeftButton, position);
0413         break;
0414     }
0415 
0416     case Event::SelectMenuItem: {
0417         // retrieve menu
0418         QMenu *menu(nullptr);
0419         if (const QToolButton *button = qobject_cast<QToolButton *>(receiver))
0420             menu = button->menu();
0421         else if (const QPushButton *button = qobject_cast<QPushButton *>(receiver))
0422             menu = button->menu();
0423 
0424         // abort if not found
0425         if (!menu)
0426             break;
0427 
0428         // get action and geometry
0429         const int row(event._position.x());
0430         QList<QAction *> actions(menu->actions());
0431         if (row >= actions.size())
0432             break;
0433 
0434         menu->sizeHint();
0435         QRect r(menu->actionGeometry(actions[row]));
0436         if (!r.isValid())
0437             break;
0438 
0439         /*!
0440         HACK: As soon as leftMouseButton is pressed on a button with menu,
0441         the menu is shown and code is interrupted until an action is selected in the menu.
0442         As a consequence, one must first generate the events, execute them with a delay, and then
0443         click on the button (before delay is expired). This way, the menu events will be executed
0444         even if the menu is visible (and blocking further code execution).
0445         */
0446         QPoint position(r.center());
0447         _pendingWidget = menu;
0448         _pendingEvents.append(new QMouseEvent(QEvent::MouseMove, position, Qt::NoButton, Qt::NoButton, Qt::NoModifier));
0449 
0450         _pendingEvents.append(new QMouseEvent(QEvent::MouseButtonPress, position, Qt::LeftButton, Qt::NoButton, Qt::NoModifier));
0451 
0452         _pendingEvents.append(new QMouseEvent(QEvent::MouseButtonRelease, position, Qt::LeftButton, Qt::NoButton, Qt::NoModifier));
0453 
0454         _pendingEventsTimer.start(10, this);
0455 
0456         // enter widget or move cursor to relevant position
0457         position = receiver->rect().center();
0458         if (!enter(receiver, position, event._delay)) {
0459             moveCursor(receiver->mapToGlobal(position));
0460             postMouseEvent(receiver, QEvent::MouseMove, Qt::NoButton, position);
0461             postDelay(event._delay);
0462         }
0463 
0464         // click
0465         postMouseEvent(receiver, QEvent::MouseButtonPress, Qt::LeftButton, position, Qt::NoButton, Qt::NoModifier);
0466         break;
0467     }
0468 
0469     case Event::SelectTab: {
0470         const QTabBar *tabbar = qobject_cast<QTabBar *>(receiver);
0471         if (!tabbar)
0472             break;
0473 
0474         const int index(event._position.x());
0475 
0476         const QRect r(tabbar->tabRect(index));
0477         if (!r.isValid())
0478             break;
0479 
0480         // enter widget or move cursor to relevant position
0481         const QPoint position(r.center());
0482         if (!enter(receiver, position, event._delay)) {
0483             moveCursor(receiver->mapToGlobal(position));
0484             postMouseEvent(receiver, QEvent::MouseMove, Qt::NoButton, position);
0485             postDelay(event._delay);
0486         }
0487 
0488         postMouseClickEvent(receiver, Qt::LeftButton, position);
0489         break;
0490     }
0491 
0492     case Event::WriteText: {
0493         enter(receiver, receiver->rect().center(), event._delay);
0494         setFocus(receiver);
0495         const QString &text(event._text);
0496         for (int i = 0; i < text.length(); ++i) {
0497             const Qt::Key key(toKey(text.at(i)));
0498             const QString local(text.at(i));
0499             postKeyEvent(receiver, QEvent::KeyPress, key, local, Qt::NoModifier);
0500             postKeyEvent(receiver, QEvent::KeyRelease, key, local, Qt::NoModifier);
0501             postDelay(20);
0502         }
0503         break;
0504     }
0505 
0506     case Event::ClearText: {
0507         enter(receiver, receiver->rect().center(), event._delay);
0508         setFocus(receiver);
0509         postKeyEvent(receiver, QEvent::KeyPress, Qt::Key_A, QStringLiteral("a"), Qt::ControlModifier);
0510         postKeyEvent(receiver, QEvent::KeyRelease, Qt::Key_A, QStringLiteral("a"), Qt::ControlModifier);
0511         postDelay(20);
0512         postKeyClickEvent(receiver, Qt::Key_Backspace, QString());
0513     }
0514 
0515     default:
0516         break;
0517     }
0518 
0519     // delay
0520     postDelay(event._delay);
0521 
0522     return;
0523 }
0524 
0525 //_______________________________________________________________________
0526 void Simulator::postEvent(QWidget *receiver, QEvent::Type type) const
0527 {
0528     postQEvent(receiver, new QEvent(type));
0529 }
0530 
0531 //_______________________________________________________________________
0532 void Simulator::postHoverEvent(QWidget *receiver, QEvent::Type type, const QPoint &newPosition, const QPoint &oldPosition) const
0533 {
0534     postQEvent(receiver, new QHoverEvent(type, newPosition, oldPosition));
0535 }
0536 
0537 //_______________________________________________________________________
0538 bool Simulator::enter(QWidget *receiver, const QPoint &position, int delay)
0539 {
0540     if (receiver == _previousWidget.data())
0541         return false;
0542 
0543     // store position
0544     moveCursor(receiver->mapToGlobal(position));
0545 
0546     // leave previous widget
0547     if (_previousWidget) {
0548         postEvent(_previousWidget.data(), QEvent::Leave);
0549         if (_previousWidget.data()->testAttribute(Qt::WA_Hover)) {
0550             const QPoint oldPosition(_previousWidget.data()->mapFromGlobal(_previousPosition));
0551             const QPoint newPosition(_previousWidget.data()->mapFromGlobal(receiver->mapToGlobal(position)));
0552             postHoverEvent(_previousWidget.data(), QEvent::HoverLeave, newPosition, oldPosition);
0553         }
0554     }
0555 
0556     // enter or move in current widget
0557     if (!receiver->rect().contains(receiver->mapFromGlobal(_previousPosition))) {
0558         // enter current widget if needed
0559         postEvent(receiver, QEvent::Enter);
0560         if (receiver->testAttribute(Qt::WA_Hover)) {
0561             const QPoint oldPosition(receiver->mapFromGlobal(_previousPosition));
0562             const QPoint newPosition(position);
0563             postHoverEvent(receiver, QEvent::HoverEnter, newPosition, oldPosition);
0564         }
0565 
0566     } else if (receiver->mapFromGlobal(_previousPosition) != position) {
0567         // move mouse if needed
0568         postMouseEvent(receiver, QEvent::MouseMove, Qt::NoButton, position);
0569         if (receiver->testAttribute(Qt::WA_Hover)) {
0570             const QPoint oldPosition(receiver->mapFromGlobal(_previousPosition));
0571             const QPoint newPosition(position);
0572             postHoverEvent(receiver, QEvent::HoverMove, newPosition, oldPosition);
0573         }
0574     }
0575 
0576     // update previous widget and position
0577     _previousWidget = receiver;
0578     _previousPosition = receiver->mapToGlobal(position);
0579     postDelay(delay);
0580 
0581     return true;
0582 }
0583 
0584 //_______________________________________________________________________
0585 void Simulator::postMouseClickEvent(QWidget *receiver, Qt::MouseButton button, const QPoint &position)
0586 {
0587     // button press and button release
0588     postMouseEvent(receiver, QEvent::MouseButtonPress, button, position, button);
0589     setFocus(receiver);
0590     postDelay(50);
0591     postMouseEvent(receiver, QEvent::MouseButtonRelease, button, position, button);
0592 }
0593 
0594 //_______________________________________________________________________
0595 void Simulator::postMouseEvent(QWidget *receiver,
0596                                QEvent::Type type,
0597                                Qt::MouseButton button,
0598                                const QPoint &position,
0599                                Qt::MouseButtons buttons,
0600                                Qt::KeyboardModifiers modifiers) const
0601 {
0602     postQEvent(receiver, new QMouseEvent(type, position, receiver->mapToGlobal(position), button, buttons, modifiers));
0603 }
0604 
0605 //_______________________________________________________________________
0606 void Simulator::postKeyClickEvent(QWidget *receiver, Qt::Key key, QString text, Qt::KeyboardModifiers modifiers) const
0607 {
0608     postKeyModifiersEvent(receiver, QEvent::KeyPress, modifiers);
0609     postKeyEvent(receiver, QEvent::KeyPress, key, text, modifiers);
0610     postKeyEvent(receiver, QEvent::KeyRelease, key, text, modifiers);
0611     postKeyModifiersEvent(receiver, QEvent::KeyRelease, modifiers);
0612 }
0613 
0614 //_______________________________________________________________________
0615 void Simulator::postKeyModifiersEvent(QWidget *receiver, QEvent::Type type, Qt::KeyboardModifiers modifiers) const
0616 {
0617     if (modifiers == Qt::NoModifier)
0618         return;
0619 
0620     switch (type) {
0621     case QEvent::KeyPress: {
0622         if (modifiers & Qt::ShiftModifier) {
0623             postKeyEvent(receiver, QEvent::KeyPress, Qt::Key_Shift, QString());
0624         }
0625 
0626         if (modifiers & Qt::ControlModifier) {
0627             postKeyEvent(receiver, QEvent::KeyPress, Qt::Key_Control, QString(), modifiers & Qt::ShiftModifier);
0628         }
0629 
0630         if (modifiers & Qt::AltModifier) {
0631             postKeyEvent(receiver, QEvent::KeyPress, Qt::Key_Alt, QString(), modifiers & (Qt::ShiftModifier | Qt::ControlModifier));
0632         }
0633 
0634         if (modifiers & Qt::MetaModifier) {
0635             postKeyEvent(receiver, QEvent::KeyPress, Qt::Key_Meta, QString(), modifiers & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier));
0636         }
0637 
0638         break;
0639     }
0640 
0641     case QEvent::KeyRelease: {
0642         if (modifiers & Qt::MetaModifier) {
0643             postKeyEvent(receiver, QEvent::KeyRelease, Qt::Key_Meta, QString());
0644         }
0645 
0646         if (modifiers & Qt::AltModifier) {
0647             postKeyEvent(receiver, QEvent::KeyRelease, Qt::Key_Alt, QString(), modifiers & Qt::MetaModifier);
0648         }
0649 
0650         if (modifiers & Qt::ControlModifier) {
0651             postKeyEvent(receiver, QEvent::KeyRelease, Qt::Key_Control, QString(), modifiers & (Qt::MetaModifier | Qt::AltModifier));
0652         }
0653 
0654         if (modifiers & Qt::ShiftModifier) {
0655             postKeyEvent(receiver, QEvent::KeyRelease, Qt::Key_Shift, QString(), modifiers & (Qt::MetaModifier | Qt::AltModifier | Qt::ControlModifier));
0656         }
0657     }
0658 
0659     default:
0660         break;
0661     }
0662 }
0663 
0664 //_______________________________________________________________________
0665 void Simulator::postKeyEvent(QWidget *receiver, QEvent::Type type, Qt::Key key, QString text, Qt::KeyboardModifiers modifiers) const
0666 {
0667     postQEvent(receiver, new QKeyEvent(type, key, modifiers, text));
0668 }
0669 
0670 //_______________________________________________________________________
0671 void Simulator::postDelay(int delay)
0672 {
0673     // check value
0674     if (delay == -1)
0675         delay = _defaultDelay;
0676     if (delay <= 0)
0677         return;
0678 
0679     // this is largely inspired from qtestlib's qsleep implementation
0680     _timer.start(delay, this);
0681     while (_timer.isActive()) {
0682         // flush events in loop
0683         QCoreApplication::processEvents(QEventLoop::AllEvents, delay);
0684         int ms(10);
0685 
0686 // sleep
0687 #ifdef Q_OS_WIN
0688         Sleep(uint(ms));
0689 #else
0690         struct timespec ts = {ms / 1000, (ms % 1000) * 1000 * 1000};
0691         nanosleep(&ts, nullptr);
0692 #endif
0693     }
0694 }
0695 
0696 //_______________________________________________________________________
0697 void Simulator::moveCursor(const QPoint &position, int steps)
0698 {
0699     // do nothing if mouse grab is disabled
0700     if (!_grabMouse)
0701         return;
0702     if (_aborted)
0703         return;
0704     const QPoint begin(QCursor::pos());
0705     const QPoint end(position);
0706     if (begin == end)
0707         return;
0708 
0709     if (steps > 1) {
0710         for (int i = 0; i < steps; ++i) {
0711             const QPoint current(begin.x() + qreal((i + 1) * (end.x() - begin.x())) / steps, begin.y() + qreal((i + 1) * (end.y() - begin.y())) / steps);
0712             QCursor::setPos(current);
0713             postDelay(10);
0714         }
0715 
0716     } else {
0717         QCursor::setPos(end);
0718     }
0719 }
0720 
0721 //_______________________________________________________________________
0722 void Simulator::setFocus(QWidget *receiver)
0723 {
0724     if (receiver->focusPolicy() != Qt::NoFocus) {
0725         receiver->setFocus();
0726     }
0727 }
0728 
0729 //_______________________________________________________________________
0730 Qt::Key Simulator::toKey(QChar a) const
0731 {
0732 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0733     return (Qt::Key)QKeySequence(a)[0];
0734 #else
0735     return QKeySequence(a)[0].key();
0736 #endif
0737 }
0738 
0739 //_______________________________________________________________________
0740 void Simulator::postQEvent(QWidget *receiver, QEvent *event) const
0741 {
0742     if (_aborted)
0743         delete event;
0744     else
0745         qApp->postEvent(receiver, event);
0746 }
0747 }