You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
450 lines
12 KiB
450 lines
12 KiB
/****************************************************************************
|
|
|
|
KHotKeys
|
|
|
|
Copyright (C) 1999-2002 Lubos Lunak <l.lunak@kde.org>
|
|
|
|
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*/, TQObject* 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, TQT_SIGNAL( timeout()), TQT_SLOT( stroke_timeout()));
|
|
connect( windows_handler, TQT_SIGNAL( active_window_changed( WId )),
|
|
TQT_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( TQObject* receiver_P, const char* slot_P )
|
|
{
|
|
if( handlers.tqcontains( receiver_P ))
|
|
return;
|
|
handlers[ receiver_P ] = true;
|
|
connect( this, TQT_SIGNAL( handle_gesture( const TQString&, WId )),
|
|
receiver_P, slot_P );
|
|
if( handlers.count() == 1 )
|
|
update_grab();
|
|
}
|
|
|
|
void Gesture::unregister_handler( TQObject* receiver_P, const char* slot_P )
|
|
{
|
|
if( !handlers.tqcontains( receiver_P ))
|
|
return;
|
|
handlers.remove( receiver_P );
|
|
disconnect( this, TQT_SIGNAL( handle_gesture( const TQString&, 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 );
|
|
TQString 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"
|