summaryrefslogtreecommitdiffstats
path: root/khotkeys/shared/gestures.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'khotkeys/shared/gestures.cpp')
-rw-r--r--khotkeys/shared/gestures.cpp449
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"