kde-workspace/kstyles/oxygen/demo/oxygensimulator.cpp
Ivailo Monev 9d5cc8fed6 generic: remove redundant Q_OS_UNIX definition checks
always defined now

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2024-03-15 09:23:17 +02:00

827 lines
29 KiB
C++

// krazy:excludeall=qclasses
//////////////////////////////////////////////////////////////////////////////
// oxygensimulator.cpp
// simulates event chain passed to the application
// -------------------
//
// Copyright (c) 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//////////////////////////////////////////////////////////////////////////////
#include "oxygensimulator.h"
#include "moc_oxygensimulator.cpp"
#include <QtGui/QAbstractItemView>
#include <QtGui/QApplication>
#include <QtGui/QCheckBox>
#include <QtGui/QComboBox>
#include <QtGui/QCursor>
#include <QtGui/qevent.h>
#include <QtGui/QMenu>
#include <QtGui/qevent.h>
#include <QtGui/QPushButton>
#include <QtGui/QRadioButton>
#include <QtGui/QScrollBar>
#include <QtGui/QSlider>
#include <QtGui/QStyle>
#include <QtGui/qstyleoption.h>
#include <QtGui/QToolButton>
#include <ctime>
namespace Oxygen
{
//_______________________________________________________________________
bool Simulator::_grabMouse = true;
int Simulator::_defaultDelay = 250;
//_______________________________________________________________________
Simulator::~Simulator( void )
{}
//_______________________________________________________________________
void Simulator::wait( int delay )
{ _events.push_back( Event( Event::Wait, 0, delay ) ); }
//_______________________________________________________________________
void Simulator::click( QWidget* receiver, int delay )
{
QPoint position;
if( QCheckBox* checkbox = qobject_cast<QCheckBox*>( receiver ) )
{
QStyleOptionButton option;
option.initFrom( checkbox );
position = checkbox->style()->subElementRect(
QStyle::SE_CheckBoxIndicator,
&option,
checkbox).center();
} else if( QRadioButton* radiobutton = qobject_cast<QRadioButton*>( receiver ) ) {
QStyleOptionButton option;
option.initFrom( radiobutton );
position = radiobutton->style()->subElementRect(
QStyle::SE_RadioButtonIndicator,
&option,
radiobutton).center();
} else {
position = receiver->rect().center();
}
click( receiver, position, delay );
}
//_______________________________________________________________________
void Simulator::click( QWidget* receiver, const QPoint& position, int delay )
{
Event event( Event::Click, receiver, delay );
event._position = position;
_events.push_back( event );
}
//_______________________________________________________________________
void Simulator::slide( QWidget* receiver, const QPoint& position, int delay )
{
Event event( Event::Slide, receiver, delay );
event._position = position;
_events.push_back( event );
}
//_______________________________________________________________________
void Simulator::selectItem( QWidget* receiver, int row, int column, int delay )
{
Event event( Event::SelectItem, receiver, delay );
event._position = QPoint( column, row );
_events.push_back( event );
}
//_______________________________________________________________________
void Simulator::selectComboBoxItem( QWidget* receiver, int index, int delay )
{
Event event( Event::SelectComboBoxItem, receiver, delay );
event._position.setX( index );
_events.push_back( event );
}
//_______________________________________________________________________
void Simulator::selectMenuItem( QWidget* receiver, int index, int delay )
{
Event event( Event::SelectMenuItem, receiver, delay );
event._position.setX( index );
_events.push_back( event );
}
//_______________________________________________________________________
void Simulator::selectTab( QTabWidget* tabwidget, int index, int delay )
{
foreach( QObject* child, tabwidget->children() )
{
if( QTabBar* tabbar = qobject_cast<QTabBar*>( child ) )
{
selectTab( tabbar, index, delay );
break;
}
}
}
//_______________________________________________________________________
void Simulator::selectTab( QTabBar* receiver, int index, int delay )
{
Event event( Event::SelectTab, receiver, delay );
event._position.setX( index );
_events.push_back( event );
}
//_______________________________________________________________________
void Simulator::writeText( QWidget* receiver, QString text, int delay )
{
Event event( Event::WriteText, receiver, delay );
event._text = text;
_events.push_back( event );
}
//_______________________________________________________________________
void Simulator::clearText( QWidget* receiver, int delay )
{ _events.push_back( Event( Event::ClearText, receiver, delay ) ); }
//_______________________________________________________________________
void Simulator::run( void )
{
if( _events.isEmpty() ) return;
// clear abort state
_aborted = false;
emit stateChanged( true );
foreach( const Event& event, _events )
{
if( _aborted )
{
_events.clear();
return;
}
processEvent( event );
}
// add last event to reset previousWidget and previousPosition
if( _previousWidget )
{
postEvent( _previousWidget.data(), QEvent::Leave );
if( _previousWidget.data()->testAttribute( Qt::WA_Hover ) )
{
const QPoint oldPosition( _previousWidget.data()->mapFromGlobal( _previousPosition ) );
const QPoint newPosition( _previousWidget.data()->mapFromGlobal( QPoint( -1, -1 ) ) );
postHoverEvent( _previousWidget.data(), QEvent::HoverLeave, newPosition, oldPosition );
}
_previousWidget.clear();
_previousPosition = QPoint(-1, -1 );
}
_events.clear();
emit stateChanged( false );
return;
}
//_______________________________________________________________________
void Simulator::abort( void )
{
_aborted = true;
emit stateChanged( true );
}
//_______________________________________________________________________
void Simulator::timerEvent( QTimerEvent* event )
{
if( event->timerId() == _timer.timerId() )
{
_timer.stop();
} else if( event->timerId() == _pendingEventsTimer.timerId() ) {
if( _aborted )
{
foreach( QEvent* event, _pendingEvents )
{ delete event; }
_pendingEvents.clear();
_pendingWidget.clear();
} else if( _pendingWidget && _pendingWidget.data()->isVisible() ) {
_pendingEventsTimer.stop();
foreach( QEvent* event, _pendingEvents )
{
if( event->type() == QEvent::MouseMove )
{
QPoint position( static_cast<QMouseEvent*>( event )->pos() );
moveCursor( _pendingWidget.data()->mapToGlobal( position ) );
}
postQEvent( _pendingWidget.data(), event );
postDelay( 150 );
}
_pendingEvents.clear();
_pendingWidget.clear();
}
} else return QObject::timerEvent( event );
}
//_______________________________________________________________________
void Simulator::processEvent( const Event& event )
{
if( _aborted ) return;
if( !event._receiver )
{
if( event._type == Event::Wait )
{ postDelay( event._delay ); }
return;
}
QWidget* receiver( event._receiver.data() );
switch( event._type )
{
// click event
case Event::Click:
{
// enter widget or move cursor to relevant position
if( !enter( receiver, event._position, event._delay ) )
{
moveCursor( receiver->mapToGlobal( event._position ) );
postMouseEvent( receiver, QEvent::MouseMove, Qt::NoButton, event._position );
}
postMouseClickEvent( receiver, Qt::LeftButton, event._position );
break;
}
// slide
case Event::Slide:
{
const QPoint& delta( event._position );
// calculate begin position depending on widget type
QPoint begin;
if( const QSlider* slider = qobject_cast<QSlider*>( receiver ) )
{
// this is copied from QSlider::initStyleOption
QStyleOptionSlider option;
option.initFrom( slider );
option.orientation = slider->orientation();
option.sliderPosition = slider->sliderPosition();
option.minimum = slider->minimum();
option.maximum = slider->maximum();
option.upsideDown = (slider->orientation() == Qt::Horizontal) ?
( slider->invertedAppearance() != (option.direction == Qt::RightToLeft))
: (!slider->invertedAppearance() );
QRect handleRect( slider->style()->subControlRect(
QStyle::CC_Slider, &option, QStyle::SC_SliderHandle,
slider ) );
if( !handleRect.isValid() ) break;
begin = handleRect.center();
} else if( const QScrollBar* scrollbar = qobject_cast<QScrollBar*>( receiver ) ) {
// this is copied from QSlider::initStyleOption
QStyleOptionSlider option;
option.initFrom( scrollbar );
option.orientation = scrollbar->orientation();
option.sliderPosition = scrollbar->sliderPosition();
option.minimum = scrollbar->minimum();
option.maximum = scrollbar->maximum();
option.upsideDown = scrollbar->invertedAppearance();
if( scrollbar->orientation() == Qt::Horizontal )
{ option.state |= QStyle::State_Horizontal; }
QRect handleRect( scrollbar->style()->subControlRect(
QStyle::CC_ScrollBar, &option, QStyle::SC_ScrollBarSlider,
scrollbar ) );
if( !handleRect.isValid() ) break;
begin = handleRect.center();
} else {
begin = receiver->rect().center();
}
// enter widget or move cursor to relevant position
if( !enter( receiver, begin, event._delay ) )
{
moveCursor( receiver->mapToGlobal( begin ) );
postMouseEvent( receiver, QEvent::MouseMove, Qt::NoButton, begin );
}
const QPoint end( begin + delta );
postMouseEvent( receiver, QEvent::MouseButtonPress, Qt::LeftButton, begin, Qt::LeftButton );
setFocus( receiver );
postDelay( 50 );
const int steps = 10;
for( int i=0; i<steps; ++i )
{
QPoint current(
begin.x() + qreal((i+1)*( end.x()-begin.x() ))/steps,
begin.y() + qreal((i+1)*( end.y()-begin.y() ))/steps );
moveCursor( receiver->mapToGlobal( current ), 1 );
postMouseEvent( receiver, QEvent::MouseMove, Qt::NoButton, current, Qt::LeftButton, Qt::NoModifier );
postDelay( 20 );
}
postMouseEvent( receiver, QEvent::MouseButtonRelease, Qt::LeftButton, end );
break;
}
case Event::SelectItem:
{
const QAbstractItemView* view = qobject_cast<QAbstractItemView*>( receiver );
if( !( view && view->model() ) ) break;
const int column( event._position.x() );
const int row( event._position.y() );
// find index
const QModelIndex modelIndex( view->model()->index( row, column ) );
if( !modelIndex.isValid() ) break;
// get rect
QRect r( view->visualRect( modelIndex ) );
if( !r.isValid() ) break;
// enter widget or move cursor to relevant position
const QPoint position( r.center() );
if( !enter( view->viewport(), position, event._delay ) )
{
moveCursor( view->viewport()->mapToGlobal( position ) );
postMouseEvent( view->viewport(), QEvent::MouseMove, Qt::NoButton, position );
postDelay( event._delay );
}
postMouseClickEvent( view->viewport(), Qt::LeftButton, position );
break;
}
case Event::SelectComboBoxItem:
{
QComboBox* combobox = qobject_cast<QComboBox*>( receiver );
if( !combobox ) break;
// get arrow rect
QStyleOptionComboBox option;
option.initFrom( combobox );
QRect arrowRect( combobox->style()->subControlRect( QStyle::CC_ComboBox, &option, QStyle::SC_ComboBoxArrow, combobox ) );
// enter widget or move cursor to relevant position
QPoint position( arrowRect.center() );
if( !enter( combobox, position, event._delay ) )
{
moveCursor( combobox->mapToGlobal( position ) );
postMouseEvent( combobox, QEvent::MouseMove, Qt::NoButton, position );
postDelay( event._delay );
}
postMouseClickEvent( combobox, Qt::LeftButton, position );
// select item in view
QAbstractItemView* view = combobox->view();
const int row( event._position.x() );
const int column( 0 );
// find index
const QModelIndex modelIndex( view->model()->index( row, column ) );
if( !modelIndex.isValid() ) break;
// get rect
QRect r( view->visualRect( modelIndex ) );
if( !r.isValid() ) break;
// send event
position = QPoint( r.center() );
moveCursor( view->viewport()->mapToGlobal( position ) );
postMouseEvent( view->viewport(), QEvent::MouseMove, Qt::NoButton, position, Qt::NoButton, Qt::NoModifier );
postDelay(100);
postMouseClickEvent( view->viewport(), Qt::LeftButton, position );
break;
}
case Event::SelectMenuItem:
{
// retrieve menu
QMenu* menu( 0 );
if( const QToolButton* button = qobject_cast<QToolButton*>( receiver ) ) menu = button->menu();
else if( const QPushButton* button = qobject_cast<QPushButton*>( receiver ) ) menu = button->menu();
// abort if not found
if( !menu ) break;
// get action and geometry
const int row( event._position.x() );
QList<QAction*> actions( menu->actions() );
if( row >= actions.size() ) break;
menu->sizeHint();
QRect r( menu->actionGeometry( actions[row] ) );
if( !r.isValid() ) break;
/*!
HACK: As soon as leftMouseButton is pressed on a button with menu,
the menu is shown and code is interrupted until an action is selected in the menu.
As a consequence, one must first generate the events, execute them with a delay, and then
click on the button (before delay is expired). This way, the menu events will be executed
even if the menu is visible (and blocking further code execution).
*/
QPoint position( r.center() );
_pendingWidget = menu;
_pendingEvents.push_back( new QMouseEvent(
QEvent::MouseMove,
position,
Qt::NoButton,
Qt::NoButton,
Qt::NoModifier ) );
_pendingEvents.push_back( new QMouseEvent(
QEvent::MouseButtonPress,
position,
Qt::LeftButton,
Qt::NoButton,
Qt::NoModifier ) );
_pendingEvents.push_back( new QMouseEvent(
QEvent::MouseButtonRelease,
position,
Qt::LeftButton,
Qt::NoButton,
Qt::NoModifier ) );
_pendingEventsTimer.start( 10, this );
// enter widget or move cursor to relevant position
position = receiver->rect().center();
if( !enter( receiver, position, event._delay ) )
{
moveCursor( receiver->mapToGlobal( position ) );
postMouseEvent( receiver, QEvent::MouseMove, Qt::NoButton, position );
postDelay( event._delay );
}
// click
postMouseEvent( receiver, QEvent::MouseButtonPress, Qt::LeftButton, position, Qt::NoButton, Qt::NoModifier );
break;
}
case Event::SelectTab:
{
const QTabBar* tabbar = qobject_cast<QTabBar*>( receiver );
if( !tabbar ) break;
const int index( event._position.x() );
const QRect r( tabbar->tabRect( index ) );
if( !r.isValid() ) break;
// enter widget or move cursor to relevant position
const QPoint position( r.center() );
if( !enter( receiver, position, event._delay ) )
{
moveCursor( receiver->mapToGlobal( position ) );
postMouseEvent( receiver, QEvent::MouseMove, Qt::NoButton, position );
postDelay( event._delay );
}
postMouseClickEvent( receiver, Qt::LeftButton, position );
break;
}
case Event::WriteText:
{
enter( receiver, receiver->rect().center(), event._delay );
setFocus( receiver );
const QString& text( event._text );
for( int i=0; i < text.length(); ++i )
{
const Qt::Key key( toKey( text.at(i) ) );
const QString local( text.at(i) );
postKeyEvent( receiver, QEvent::KeyPress, key, local, Qt::NoModifier );
postKeyEvent( receiver, QEvent::KeyRelease, key, local, Qt::NoModifier );
postDelay( 20 );
}
break;
}
case Event::ClearText:
{
enter( receiver, receiver->rect().center(), event._delay );
setFocus( receiver );
postKeyEvent( receiver, QEvent::KeyPress, Qt::Key_A, "a", Qt::ControlModifier );
postKeyEvent( receiver, QEvent::KeyRelease, Qt::Key_A, "a", Qt::ControlModifier );
postDelay( 20 );
postKeyClickEvent( receiver, Qt::Key_Backspace, QString() );
}
default: break;
}
// delay
postDelay( event._delay );
return;
}
//_______________________________________________________________________
void Simulator::postEvent( QWidget* receiver, QEvent::Type type ) const
{ postQEvent( receiver, new QEvent( type ) ); }
//_______________________________________________________________________
void Simulator::postHoverEvent( QWidget* receiver, QEvent::Type type, const QPoint& newPosition, const QPoint& oldPosition ) const
{ postQEvent( receiver, new QHoverEvent( type, newPosition, oldPosition ) ); }
//_______________________________________________________________________
bool Simulator::enter( QWidget* receiver, const QPoint& position, int delay )
{
if( receiver == _previousWidget.data() ) return false;
// store position
moveCursor( receiver->mapToGlobal( position ) );
// leave previous widget
if( _previousWidget )
{
postEvent( _previousWidget.data(), QEvent::Leave );
if( _previousWidget.data()->testAttribute( Qt::WA_Hover ) )
{
const QPoint oldPosition( _previousWidget.data()->mapFromGlobal( _previousPosition ) );
const QPoint newPosition( _previousWidget.data()->mapFromGlobal( receiver->mapToGlobal( position ) ) );
postHoverEvent( _previousWidget.data(), QEvent::HoverLeave, newPosition, oldPosition );
}
}
// enter or move in current widget
if( !receiver->rect().contains( receiver->mapFromGlobal( _previousPosition ) ) )
{
// enter current widget if needed
postEvent( receiver, QEvent::Enter );
if( receiver->testAttribute( Qt::WA_Hover ) )
{
const QPoint oldPosition( receiver->mapFromGlobal( _previousPosition ) );
const QPoint newPosition( position );
postHoverEvent( receiver, QEvent::HoverEnter, newPosition, oldPosition );
}
} else if( receiver->mapFromGlobal( _previousPosition ) != position ) {
// move mouse if needed
postMouseEvent( receiver, QEvent::MouseMove, Qt::NoButton, position );
if( receiver->testAttribute( Qt::WA_Hover ) )
{
const QPoint oldPosition( receiver->mapFromGlobal( _previousPosition ) );
const QPoint newPosition( position );
postHoverEvent( receiver, QEvent::HoverMove, newPosition, oldPosition );
}
}
// update previous widget and position
_previousWidget = receiver;
_previousPosition = receiver->mapToGlobal( position );
postDelay( delay );
return true;
}
//_______________________________________________________________________
void Simulator::postMouseClickEvent( QWidget* receiver, Qt::MouseButton button, const QPoint& position )
{
// button press and button release
postMouseEvent( receiver, QEvent::MouseButtonPress, button, position, button );
setFocus( receiver );
postDelay(50);
postMouseEvent( receiver, QEvent::MouseButtonRelease, button, position, button );
}
//_______________________________________________________________________
void Simulator::postMouseEvent(
QWidget* receiver,
QEvent::Type type,
Qt::MouseButton button,
const QPoint& position,
Qt::MouseButtons buttons,
Qt::KeyboardModifiers modifiers ) const
{
postQEvent( receiver, new QMouseEvent(
type,
position,
receiver->mapToGlobal( position ),
button,
buttons,
modifiers ) );
}
//_______________________________________________________________________
void Simulator::postKeyClickEvent( QWidget* receiver, Qt::Key key, QString text, Qt::KeyboardModifiers modifiers ) const
{
postKeyModifiersEvent( receiver, QEvent::KeyPress, modifiers );
postKeyEvent( receiver, QEvent::KeyPress, key, text, modifiers );
postKeyEvent( receiver, QEvent::KeyRelease, key, text, modifiers );
postKeyModifiersEvent( receiver, QEvent::KeyRelease, modifiers );
}
//_______________________________________________________________________
void Simulator::postKeyModifiersEvent( QWidget* receiver, QEvent::Type type, Qt::KeyboardModifiers modifiers ) const
{
if( modifiers == Qt::NoModifier ) return;
switch( type )
{
case QEvent::KeyPress:
{
if( modifiers & Qt::ShiftModifier)
{ postKeyEvent( receiver, QEvent::KeyPress, Qt::Key_Shift, QString() ); }
if( modifiers & Qt::ControlModifier )
{ postKeyEvent( receiver, QEvent::KeyPress, Qt::Key_Control, QString(), modifiers & Qt::ShiftModifier ); }
if( modifiers & Qt::AltModifier )
{ postKeyEvent( receiver, QEvent::KeyPress, Qt::Key_Alt, QString(), modifiers & (Qt::ShiftModifier|Qt::ControlModifier) ); }
if( modifiers & Qt::MetaModifier )
{ postKeyEvent( receiver, QEvent::KeyPress, Qt::Key_Meta, QString(), modifiers & (Qt::ShiftModifier|Qt::ControlModifier|Qt::AltModifier) ); }
break;
}
case QEvent::KeyRelease:
{
if( modifiers & Qt::MetaModifier )
{ postKeyEvent( receiver, QEvent::KeyRelease, Qt::Key_Meta, QString() ); }
if( modifiers & Qt::AltModifier )
{ postKeyEvent( receiver, QEvent::KeyRelease, Qt::Key_Alt, QString(), modifiers & Qt::MetaModifier ); }
if( modifiers & Qt::ControlModifier )
{ postKeyEvent( receiver, QEvent::KeyRelease, Qt::Key_Control, QString(), modifiers & (Qt::MetaModifier|Qt::AltModifier) ); }
if( modifiers & Qt::ShiftModifier)
{ postKeyEvent( receiver, QEvent::KeyRelease, Qt::Key_Shift, QString(), modifiers & (Qt::MetaModifier|Qt::AltModifier|Qt::ControlModifier) ); }
}
default: break;
}
}
//_______________________________________________________________________
void Simulator::postKeyEvent( QWidget* receiver, QEvent::Type type, Qt::Key key, QString text, Qt::KeyboardModifiers modifiers ) const
{ postQEvent( receiver, new QKeyEvent( type, key, modifiers, text ) ); }
//_______________________________________________________________________
void Simulator::postDelay( int delay )
{
// check value
if( delay == -1 ) delay = _defaultDelay;
if( delay <= 0 ) return;
// this is largely inspired from qtestlib's qsleep implementation
_timer.start( delay, this );
while( _timer.isActive() )
{
// flush events in loop
QCoreApplication::processEvents(QEventLoop::AllEvents, delay);
int ms( 10 );
// sleep
struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
nanosleep(&ts, NULL);
}
}
//_______________________________________________________________________
void Simulator::moveCursor( const QPoint& position, int steps )
{
// do nothing if mouse grab is disabled
if( !_grabMouse ) return;
if( _aborted ) return;
const QPoint begin( QCursor::pos() );
const QPoint end( position );
if( begin == end ) return;
if( steps > 1 )
{
for( int i = 0; i<steps; ++i )
{
const QPoint current(
begin.x() + qreal((i+1)*( end.x()-begin.x() ))/steps,
begin.y() + qreal((i+1)*( end.y()-begin.y() ))/steps );
QCursor::setPos( current );
postDelay( 10 );
}
} else {
QCursor::setPos( end );
}
}
//_______________________________________________________________________
void Simulator::setFocus( QWidget* receiver )
{
if( receiver->focusPolicy() != Qt::NoFocus )
{ receiver->setFocus(); }
}
//_______________________________________________________________________
Qt::Key Simulator::toKey( QChar a ) const
{ return (Qt::Key) QKeySequence( a )[0]; }
//_______________________________________________________________________
void Simulator::postQEvent( QWidget* receiver, QEvent* event ) const
{
if( _aborted ) delete event;
else qApp->postEvent( receiver, event );
}
}