diff options
Diffstat (limited to 'khotkeys/shared/gestures.cpp')
-rw-r--r-- | khotkeys/shared/gestures.cpp | 449 |
1 files changed, 449 insertions, 0 deletions
diff --git a/khotkeys/shared/gestures.cpp b/khotkeys/shared/gestures.cpp new file mode 100644 index 000000000..11cc44e22 --- /dev/null +++ b/khotkeys/shared/gestures.cpp @@ -0,0 +1,449 @@ +/**************************************************************************** + + KHotKeys + + Copyright (C) 1999-2002 Lubos Lunak <[email protected]> + + Distributed under the terms of the GNU General Public License version 2. + + Based on LibStroke : + ( libstroke - an X11 stroke interface library + Copyright (c) 1996,1997,1998,1999 Mark F. Willey, ETLA Technical + There is a reference application available on the LibStroke Home Page: + http://www.etla.net/~willey/projects/libstroke/ ) + +****************************************************************************/ + +#define _GESTURES_CPP_ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "gestures.h" + +#include <stdlib.h> +#include <math.h> +#include <assert.h> + +#include <X11/Xlib.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kxerrorhandler.h> +#include <kkeynative.h> + +#include "input.h" +#include "windows.h" + +#include "voices.h" + +namespace KHotKeys +{ + +Gesture* gesture_handler; + +Gesture::Gesture( bool /*enabled_P*/, QObject* parent_P ) + : _enabled( false ), recording( false ), button( 0 ), exclude( NULL ) + { + (void) new DeleteObject( this, parent_P ); + assert( gesture_handler == NULL ); + gesture_handler = this; + connect( &nostroke_timer, SIGNAL( timeout()), SLOT( stroke_timeout())); + connect( windows_handler, SIGNAL( active_window_changed( WId )), + SLOT( active_window_changed( WId ))); + } + +Gesture::~Gesture() + { + enable( false ); + gesture_handler = NULL; + } + +void Gesture::enable( bool enabled_P ) + { + if( _enabled == enabled_P ) + return; + _enabled = enabled_P; + assert( button != 0 ); + update_grab(); + } + +void Gesture::set_exclude( Windowdef_list* windows_P ) + { + delete exclude; + // check for count() > 0 - empty exclude list means no window is excluded, + // but empty Windowdef_list matches everything + if( windows_P != NULL && windows_P->count() > 0 ) + exclude = windows_P->copy(); + else + exclude = NULL; + update_grab(); + } + +void Gesture::update_grab() + { + if( _enabled && handlers.count() > 0 + && ( exclude == NULL || !exclude->match( Window_data( windows_handler->active_window())))) + { + kapp->removeX11EventFilter( this ); // avoid being installed twice + kapp->installX11EventFilter( this ); + // CHECKME at se grabuje jen kdyz je alespon jedno gesto? + grab_mouse( true ); + } + else + { + grab_mouse( false ); + kapp->removeX11EventFilter( this ); + } + } + +void Gesture::active_window_changed( WId ) + { + update_grab(); + } + +void Gesture::register_handler( QObject* receiver_P, const char* slot_P ) + { + if( handlers.contains( receiver_P )) + return; + handlers[ receiver_P ] = true; + connect( this, SIGNAL( handle_gesture( const QString&, WId )), + receiver_P, slot_P ); + if( handlers.count() == 1 ) + update_grab(); + } + +void Gesture::unregister_handler( QObject* receiver_P, const char* slot_P ) + { + if( !handlers.contains( receiver_P )) + return; + handlers.remove( receiver_P ); + disconnect( this, SIGNAL( handle_gesture( const QString&, WId )), + receiver_P, slot_P ); + if( handlers.count() == 0 ) + update_grab(); + } + +bool Gesture::x11Event( XEvent* ev_P ) + { +/* kdDebug(1217) << k_funcinfo << " ( type = " << ev_P->type << " )" << KeyRelease << " " << KeyPress <<endl; + if( ev_P->type == XKeyPress || ev_P->type == XKeyRelease ) + { + return voice_handler->x11Event( ev_P ); + }*/ + + if( ev_P->type == ButtonPress && ev_P->xbutton.button == button ) + { + kdDebug( 1217 ) << "GESTURE: mouse press" << endl; + stroke.reset(); + stroke.record( ev_P->xbutton.x, ev_P->xbutton.y ); + nostroke_timer.start( timeout, true ); + recording = true; + start_x = ev_P->xbutton.x_root; + start_y = ev_P->xbutton.y_root; + return true; + } + else if( ev_P->type == ButtonRelease && ev_P->xbutton.button == button + && recording ) + { + recording = false; + nostroke_timer.stop(); + stroke.record( ev_P->xbutton.x, ev_P->xbutton.y ); + QString gesture( stroke.translate()); + if( gesture.isEmpty()) + { + kdDebug( 1217 ) << "GESTURE: replay" << endl; + XAllowEvents( qt_xdisplay(), AsyncPointer, CurrentTime ); + XUngrabPointer( qt_xdisplay(), CurrentTime ); + mouse_replay( true ); + return true; + } + kdDebug( 1217 ) << "GESTURE: got: " << gesture << endl; + emit handle_gesture( gesture, windows_handler->window_at_position( start_x, start_y )); + return true; + } + else if( ev_P->type == MotionNotify && recording ) + { // ignore small initial movement + if( nostroke_timer.isActive() + && abs( start_x - ev_P->xmotion.x_root ) < 10 + && abs( start_y - ev_P->xmotion.y_root ) < 10 ) + return true; + nostroke_timer.stop(); + stroke.record( ev_P->xmotion.x, ev_P->xmotion.y ); + } + return false; + } + +void Gesture::stroke_timeout() + { + kdDebug( 1217 ) << "GESTURE: timeout" << endl; + XAllowEvents( qt_xdisplay(), AsyncPointer, CurrentTime ); + XUngrabPointer( qt_xdisplay(), CurrentTime ); + mouse_replay( false ); + recording = false; + } + +void Gesture::mouse_replay( bool release_P ) + { + bool was_enabled = _enabled; + enable( false ); + Mouse::send_mouse_button( button, release_P ); + enable( was_enabled ); + } + +void Gesture::grab_mouse( bool grab_P ) + { + if( grab_P ) + { + KXErrorHandler handler; + static int mask[] = { 0, Button1MotionMask, Button2MotionMask, Button3MotionMask, + Button4MotionMask, Button5MotionMask, ButtonMotionMask, ButtonMotionMask, + ButtonMotionMask, ButtonMotionMask }; +#define XCapL KKeyNative::modXLock() +#define XNumL KKeyNative::modXNumLock() +#define XScrL KKeyNative::modXScrollLock() + unsigned int mods[ 8 ] = + { + 0, XCapL, XNumL, XNumL | XCapL, + XScrL, XScrL | XCapL, + XScrL | XNumL, XScrL | XNumL | XCapL + }; +#undef XCapL +#undef XNumL +#undef XScrL + for( int i = 0; + i < 8; + ++i ) + XGrabButton( qt_xdisplay(), button, mods[ i ], qt_xrootwin(), False, + ButtonPressMask | ButtonReleaseMask | mask[ button ], GrabModeAsync, GrabModeAsync, + None, None ); + bool err = handler.error( true ); + kdDebug( 1217 ) << "Gesture grab:" << err << endl; + } + else + { + kdDebug( 1217 ) << "Gesture ungrab" << endl; + XUngrabButton( qt_xdisplay(), button, AnyModifier, qt_xrootwin()); + } + } + +void Gesture::set_mouse_button( unsigned int button_P ) + { + if( button == button_P ) + return; + if( !_enabled ) + { + button = button_P; + return; + } + grab_mouse( false ); + button = button_P; + grab_mouse( true ); + } + +void Gesture::set_timeout( int timeout_P ) + { + timeout = timeout_P; + } + +Stroke::Stroke() + { + reset(); + points = new point[ MAX_POINTS ]; // CHECKME + } + +Stroke::~Stroke() + { + delete[] points; + } + +void Stroke::reset() + { + min_x = 10000; + min_y = 10000; + max_x = -1; + max_y = -1; + point_count = -1; + } + +bool Stroke::record( int x, int y ) + { + if( point_count >= MAX_POINTS ) + return false; + if( point_count == -1 ) + { + ++point_count; + points[ point_count ].x = x; + points[ point_count ].y = y; + min_x = max_x = x; + min_y = max_y = y; + } + else + { + // interpolate between last and current point + int delx = x - points[ point_count ].x; + int dely = y - points[ point_count ].y; + if( abs( delx ) > abs( dely )) // step by the greatest delta direction + { + float iy = points[ point_count ].y; + // go from the last point to the current, whatever direction it may be + for( int ix = points[ point_count ].x; + ( delx > 0 ) ? ( ix < x ) : ( ix > x ); + ( delx > 0 ) ? ++ix : --ix ) + { + // step the other axis by the correct increment + if( dely < 0 ) + iy -= fabs( dely / ( float ) delx ); + else + iy += fabs( dely / ( float ) delx ); + // add the interpolated point + ++point_count; + if( point_count >= MAX_POINTS ) + return false; + points[ point_count ].x = ix; + points[ point_count ].y = ( int )iy; + } + // add the last point + ++point_count; + if( point_count >= MAX_POINTS ) + return false; + points[ point_count ].x = x; + points[ point_count ].y = y; + // update metrics, it's ok to do it only for the last point + if( x < min_x ) + min_x = x; + if( x > max_x ) + max_x = x; + if( y < min_y ) + min_y = y; + if( y > max_y ) + max_y = y; + } + else + { // same thing, but for dely larger than delx case... + float ix = points[ point_count ].x; + // go from the last point to the current, whatever direction it may be + for( int iy = points[ point_count ].y; + ( dely > 0 ) ? ( iy < y ) : ( iy > y ); + ( dely > 0 ) ? ++iy : --iy ) + { + // step the other axis by the correct increment + if( delx < 0 ) + ix -= fabs( delx / ( float ) dely ); + else + ix += fabs( delx / ( float ) dely ); + // add the interpolated point + ++point_count; + if( point_count >= MAX_POINTS ) + return false; + points[ point_count ].x = ( int )ix; + points[ point_count ].y = iy; + } + // add the last point + ++point_count; + if( point_count >= MAX_POINTS ) + return false; + points[ point_count ].x = x; + points[ point_count ].y = y; + // update metrics, ts's ok to do it only for the last point + if( x < min_x ) + min_x = x; + if( x > max_x ) + max_x = x; + if( y < min_y ) + min_y = y; + if( y > max_y ) + max_y = y; + } + } + return true; + } + +char* Stroke::translate( int min_bin_points_percentage_P, int scale_ratio_P, int min_points_P ) + { + if( point_count < min_points_P ) + return NULL; + // determine size of grid + delta_x = max_x - min_x; + delta_y = max_y - min_y; + if( delta_x > scale_ratio_P * delta_y ) + { + int avg_y = ( max_y + min_y ) / 2; + min_y = avg_y - delta_x / 2; + max_y = avg_y + delta_x / 2; + delta_y = max_y - min_y; + } + else if( delta_y > scale_ratio_P * delta_x ) + { + int avg_x = ( max_x + min_x ) / 2; + min_x = avg_x - delta_y / 2; + max_x = avg_x + delta_y / 2; + delta_x = max_x - min_x; + } + // calculate bin boundary positions + bound_x_1 = min_x + delta_x / 3; + bound_x_2 = min_x + 2 * delta_x / 3; + bound_y_1 = min_y + delta_y / 3; + bound_y_2 = min_y + 2 * delta_y / 3; + + int sequence_count = 0; + // points-->sequence translation scratch variables + int prev_bin = 0; + int current_bin = 0; + int bin_count = 0; +// build string by placing points in bins, collapsing bins and discarding +// those with too few points... + for( int pos = 0; + pos <= point_count; + ++pos ) + { + // figure out which bin the point falls in + current_bin = bin( points[ pos ].x, points[ pos ].y ); + // if this is the first point, consider it the previous bin, too. + if( prev_bin == 0 ) + prev_bin = current_bin; + if( prev_bin == current_bin ) + bin_count++; + else + { // we are moving to a new bin -- consider adding to the sequence + // CHECKME tohle taky konfigurovatelne ? + if( bin_count >= ( min_bin_points_percentage_P * point_count / 100 ) + || sequence_count == 0 ) + { + if( sequence_count >= MAX_SEQUENCE ) + return NULL; + ret_val[ sequence_count++ ] = prev_bin + '0'; + } + // restart counting points in the new bin + bin_count=0; + prev_bin = current_bin; + } + } + + // add the last run of points to the sequence + if( sequence_count >= MAX_SEQUENCE - 1 ) + return NULL; + ret_val[ sequence_count++ ] = current_bin + '0'; + ret_val[ sequence_count ] = 0; // endmark + return ret_val; + } + +/* figure out which bin the point falls in */ +int Stroke::bin( int x, int y ) + { + int bin_num = 1; + if( x > bound_x_1 ) + ++bin_num; + if( x > bound_x_2 ) + ++bin_num; + if( y < bound_y_1 ) + bin_num += 3; + if( y < bound_y_2 ) + bin_num += 3; + return bin_num; + } + +} // namespace KHotKeys + +#include "gestures.moc" |