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.
tdebase/khotkeys/shared/gestures.cpp

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"