9f4cc34f37
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@39667 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
311 lines
9.9 KiB
Python
311 lines
9.9 KiB
Python
#Mouse Gestures
|
|
|
|
#Version 0.0.1
|
|
|
|
#By Daniel Pozmanter
|
|
#drpython@bluebottle.com
|
|
|
|
#Released under the terms of the wxWindows License.
|
|
|
|
"""
|
|
This is a class to add Mouse Gestures to a program.
|
|
It can be used in two ways:
|
|
|
|
1. Automatic:
|
|
Automatically runs mouse gestures.
|
|
You need to set the gestures, and their associated actions,
|
|
as well as the Mouse Button/Modifiers to use.
|
|
|
|
2. Manual:
|
|
Same as above, but you do not need to set the mouse button/modifiers.
|
|
You can launch this from events as you wish.
|
|
|
|
An example is provided in the demo.
|
|
The parent window is where the mouse events will be recorded.
|
|
(So if you want to record them in a pop up window, use manual mode,
|
|
and set the pop up as the parent).
|
|
|
|
Start() starts recording mouse movement.
|
|
End() stops the recording, compiles all the gestures into a list,
|
|
and looks through the registered gestures to find a match.
|
|
The first matchs associated action is then run.
|
|
|
|
The marginoferror is how much to forgive when calculating movement:
|
|
If the margin is 25, then movement less than 25 pixels will not be detected.
|
|
|
|
Recognized: L, R, U, D, 1, 3, 7, 9
|
|
|
|
Styles: Manual (Automatic By Default), DisplayNumbersForDiagonals (Off By Default).
|
|
Not Yet Implemented
|
|
|
|
The criteria for a direction is as follows:
|
|
x in a row. (Where x is the WobbleTolerance).
|
|
So if the WobbleTolerance is 9
|
|
'URUUUUUUUUUUUUUUURUURUUUU1' is Up.
|
|
|
|
The higher this number, the less sensitive this class is.
|
|
So the more likely something like 1L will translate to 1.
|
|
|
|
This is good, since the mouse does tend to wobble somewhat,
|
|
and a higher number allows for this.
|
|
|
|
To change this, use SetWobbleTolerance
|
|
|
|
Also, to help with recognition of a diagonal versus
|
|
a vey messy straight line, if the greater absolute value
|
|
is not greater than twice the lesser, only the grater value
|
|
is counted.
|
|
|
|
In automatic mode, EVT_MOUSE_EVENTS is used.
|
|
This allows the user to change the mouse button/modifiers at runtime.
|
|
"""
|
|
|
|
###########################################
|
|
|
|
'''
|
|
Changelog:
|
|
0.0.1: Treats a mouse leaving event as mouse up.
|
|
(Bug Report, Thanks Peter Damoc).
|
|
|
|
|
|
0.0.0: Initial Release.
|
|
'''
|
|
|
|
###########################################
|
|
#ToDo:
|
|
|
|
#Fully Implement Manual Mode
|
|
|
|
#Add "Ends With": AddGestureEndsWith(self, gesture, action, args)
|
|
#Add "Starts With": AddGestuteStartsWith(self, gesture, action, args)
|
|
|
|
#For better control of when the gesture starts and stops,
|
|
#use manual mode.
|
|
#At the moment, you need to Bind the OnMouseMotion event if you want to use
|
|
#manual mode.
|
|
|
|
import wx
|
|
|
|
class MouseGestures:
|
|
def __init__(self, parent, Auto=True, MouseButton=wx.MOUSE_BTN_MIDDLE):
|
|
self.parent = parent
|
|
|
|
self.gestures = []
|
|
self.actions = []
|
|
self.actionarguments = []
|
|
|
|
self.mousebutton = MouseButton
|
|
self.modifiers = []
|
|
|
|
self.recording = False
|
|
|
|
self.lastposition = (-1, -1)
|
|
|
|
self.pen = wx.Pen(wx.Colour(0, 144, 255), 5)
|
|
|
|
self.dc = wx.ScreenDC()
|
|
self.dc.SetPen(self.pen)
|
|
|
|
self.showgesture = False
|
|
|
|
self.wobbletolerance = 7
|
|
|
|
self.rawgesture = ''
|
|
|
|
self.SetAuto(Auto)
|
|
|
|
def _check_modifiers(self, event):
|
|
'''Internal: Returns True if all needed modifiers are down
|
|
for the given event.'''
|
|
if len(self.modifiers) > 0:
|
|
good = True
|
|
if wx.WXK_CONTROL in self.modifiers:
|
|
good = good and event.ControlDown()
|
|
if wx.WXK_SHIFT in self.modifiers:
|
|
good = good and event.ShiftDown()
|
|
if wx.WXK_ALT in self.modifiers:
|
|
good = good and event.AltDown()
|
|
return good
|
|
return True
|
|
|
|
def AddGesture(self, gesture, action, *args):
|
|
'''Registers a gesture, and an associated function, with any arguments needed.'''
|
|
#Make Sure not a duplicate:
|
|
self.RemoveGesture(gesture)
|
|
|
|
self.gestures.append(gesture)
|
|
self.actions.append(action)
|
|
self.actionarguments.append(args)
|
|
|
|
def DoAction(self, gesture):
|
|
'''If the gesture is in the array of registered gestures, run the associated function.'''
|
|
if gesture in self.gestures:
|
|
i = self.gestures.index(gesture)
|
|
apply(self.actions[i], self.actionarguments[i])
|
|
|
|
def End(self):
|
|
'''Stops recording the points to create the mouse gesture from,
|
|
and creates the mouse gesture, returns the result as a string.'''
|
|
self.recording = False
|
|
|
|
#Figure out the gestures (Look for occurances of 5 in a row or more):
|
|
|
|
tempstring = '0'
|
|
possiblechange = '0'
|
|
|
|
directions = ''
|
|
|
|
for g in self.rawgesture:
|
|
l = len(tempstring)
|
|
if g != tempstring[l - 1]:
|
|
if g == possiblechange:
|
|
tempstring = g + g
|
|
else:
|
|
possiblechange = g
|
|
else:
|
|
tempstring += g
|
|
if len(tempstring) >= self.wobbletolerance:
|
|
ld = len(directions)
|
|
if ld > 0:
|
|
if directions[ld - 1] != g:
|
|
directions += g
|
|
else:
|
|
directions += g
|
|
tempstring = '0'
|
|
|
|
if self.showgesture:
|
|
self.parent.Refresh()
|
|
|
|
return directions
|
|
|
|
def GetDirection(self, point1, point2):
|
|
'''Gets the direction between two points.'''
|
|
#point1 is the old point
|
|
#point2 is current
|
|
|
|
x1, y1 = point1
|
|
x2, y2 = point2
|
|
|
|
#(Negative = Left, Up)
|
|
#(Positive = Right, Down)
|
|
|
|
horizontal = x2 - x1
|
|
vertical = y2 - y1
|
|
|
|
horizontalchange = abs(horizontal) > 0
|
|
verticalchange = abs(vertical) > 0
|
|
|
|
if horizontalchange and verticalchange:
|
|
ah = abs(horizontal)
|
|
av = abs(vertical)
|
|
if ah > av:
|
|
if (ah / av) > 2:
|
|
vertical = 0
|
|
verticalchange = False
|
|
elif av > ah:
|
|
if (av / ah) > 2:
|
|
horizontal = 0
|
|
horizontalchange = False
|
|
|
|
if horizontalchange and verticalchange:
|
|
#Diagonal
|
|
if (horizontal > 0) and (vertical > 0):
|
|
return '3'
|
|
elif (horizontal > 0) and (vertical < 0):
|
|
return '9'
|
|
elif (horizontal < 0) and (vertical > 0):
|
|
return '1'
|
|
else:
|
|
return '7'
|
|
else:
|
|
#Straight Line
|
|
if horizontalchange:
|
|
if horizontal > 0:
|
|
return 'R'
|
|
else:
|
|
return 'L'
|
|
else:
|
|
if vertical > 0:
|
|
return 'D'
|
|
else:
|
|
return 'U'
|
|
|
|
def GetRecording(self):
|
|
'''Returns whether or not Gesture Recording has started.'''
|
|
return self.recording
|
|
|
|
def OnMotion(self, event):
|
|
'''Internal. Used if Start() has been run'''
|
|
if self.recording:
|
|
currentposition = event.GetPosition()
|
|
if self.lastposition != (-1, -1):
|
|
self.rawgesture += self.GetDirection(self.lastposition, currentposition)
|
|
if self.showgesture:
|
|
#Draw it!
|
|
px1, py1 = self.parent.ClientToScreen(self.lastposition)
|
|
px2, py2 = self.parent.ClientToScreen(currentposition)
|
|
self.dc.DrawLine(px1, py1, px2, py2)
|
|
|
|
self.lastposition = currentposition
|
|
|
|
event.Skip()
|
|
|
|
def OnMouseEvent(self, event):
|
|
'''Internal. Used in Auto Mode.'''
|
|
if event.ButtonDown(self.mousebutton) and self._check_modifiers(event):
|
|
self.Start()
|
|
elif (event.ButtonUp(self.mousebutton) or event.Leaving()) and self.GetRecording():
|
|
result = self.End()
|
|
self.DoAction(result)
|
|
event.Skip()
|
|
|
|
def RemoveGesture(self, gesture):
|
|
'''Removes a gesture, and its associated action'''
|
|
if gesture in self.gestures:
|
|
i = self.gestures.index(gesture)
|
|
|
|
del self.gestures[i]
|
|
del self.actions[i]
|
|
del self.actionarguments[i]
|
|
|
|
def SetAuto(self, auto):
|
|
'''Warning: Once auto is set, it stays set, unless you manually use UnBind'''
|
|
if auto:
|
|
self.parent.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvent)
|
|
self.parent.Bind(wx.EVT_MOTION, self.OnMotion)
|
|
|
|
def SetGesturePen(self, pen):
|
|
'''Sets the wx pen used to visually represent each gesture'''
|
|
self.pen = pen
|
|
self.dc.SetPen(self.pen)
|
|
|
|
def SetGesturePen(self, colour, width):
|
|
'''Sets the colour and width of the line drawn to visually represent each gesture'''
|
|
self.pen = wx.Pen(colour, width)
|
|
self.dc.SetPen(self.pen)
|
|
|
|
def SetGesturesVisible(self, vis):
|
|
'''Sets whether a line is drawn to visually represent each gesture'''
|
|
self.showgesture = vis
|
|
|
|
def SetModifiers(self, modifiers=[]):
|
|
'''Takes an array of wx Key constants (Control, Shift, and/or Alt).
|
|
Leave empty to unset all modifiers.'''
|
|
self.modifiers = modifiers
|
|
|
|
def SetMouseButton(self, mousebutton):
|
|
'''Takes the wx constant for the target mousebutton'''
|
|
self.mousebutton = mousebutton
|
|
|
|
def SetWobbleTolerance(self, wobbletolerance):
|
|
'''Sets just how much wobble this class can take!'''
|
|
self.WobbleTolerance = wobbletolerance
|
|
|
|
def Start(self):
|
|
'''Starts recording the points to create the mouse gesture from'''
|
|
self.recording = True
|
|
self.rawgesture = ''
|
|
self.lastposition = (-1, -1)
|
|
if self.showgesture:
|
|
self.parent.Refresh()
|