]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/gestures.py
Moved some of the linux builds to a new build host
[wxWidgets.git] / wxPython / wx / lib / gestures.py
CommitLineData
51c5e1f2
RD
1#Mouse Gestures
2
7a0c9b39 3#Version 0.0.1
51c5e1f2
RD
4
5#By Daniel Pozmanter
6#drpython@bluebottle.com
7
8#Released under the terms of the wxWindows License.
9
10#This is a class to add Mouse Gestures to a program.
11#It can be used in two ways:
51c5e1f2 12#
7a0c9b39
RD
13#1. Automatic:
14# Automatically runs mouse gestures.
15# You need to set the gestures, and their associated actions,
16# as well as the Mouse Button/Modifiers to use.
51c5e1f2
RD
17#
18#2. Manual:
7a0c9b39
RD
19# Same as above, but you do not need to set the mouse button/modifiers.
20# You can launch this from events as you wish.
51c5e1f2
RD
21#
22#An example is provided in the demo.
23#The parent window is where the mouse events will be recorded.
24#(So if you want to record them in a pop up window, use manual mode,
25#and set the pop up as the parent).
26#
27#Start() starts recording mouse movement.
28#End() stops the recording, compiles all the gestures into a list,
29#and looks through the registered gestures to find a match.
7a0c9b39 30#The first matchs associated action is then run.
51c5e1f2
RD
31
32#The marginoferror is how much to forgive when calculating movement:
33#If the margin is 25, then movement less than 25 pixels will not be detected.
34
35#Recognized: L, R, U, D, 1, 3, 7, 9
36
37#Styles: Manual (Automatic By Default), DisplayNumbersForDiagonals (Off By Default).
38#Not Yet Implemented
39
40#The criteria for a direction is as follows:
41#x in a row. (Where x is the WobbleTolerance).
42#So if the WobbleTolerance is 9
43# 'URUUUUUUUUUUUUUUURUURUUUU1' is Up.
44
45#The higher this number, the less sensitive this class is.
46#So the more likely something like 1L will translate to 1.
47
48#This is good, since the mouse does tend to wobble somewhat,
49#and a higher number allows for this.
50
51#To change this, use SetWobbleTolerance
52
53#Also, to help with recognition of a diagonal versus
54#a vey messy straight line, if the greater absolute value
55#is not greater than twice the lesser, only the grater value
56#is counted.
57
51c5e1f2 58
7a0c9b39
RD
59#In automatic mode, EVT_MOUSE_EVENTS is used.
60#This allows the user to change the mouse button/modifiers at runtime.
61
62###########################################
63
64'''
65Changelog:
660.0.1: Treats a mouse leaving event as mouse up.
67 (Bug Report, Thanks Peter Damoc).
68
69
700.0.0: Initial Release.
71'''
72
73###########################################
74#ToDo:
51c5e1f2 75
7a0c9b39 76#Fully Implement Manual Mode
51c5e1f2
RD
77
78#Add "Ends With": AddGestureEndsWith(self, gesture, action, args)
79#Add "Starts With": AddGestuteStartsWith(self, gesture, action, args)
80
7a0c9b39
RD
81#For better control of when the gesture starts and stops,
82#use manual mode.
83#At the moment, you need to Bind the OnMouseMotion event if you want to use
84#manual mode.
51c5e1f2
RD
85
86import wx
87
88class MouseGestures:
89 def __init__(self, parent, Auto=True, MouseButton=wx.MOUSE_BTN_MIDDLE):
90 self.parent = parent
7a0c9b39 91
51c5e1f2
RD
92 self.gestures = []
93 self.actions = []
94 self.actionarguments = []
95
96 self.mousebutton = MouseButton
7a0c9b39 97 self.modifiers = []
51c5e1f2
RD
98
99 self.recording = False
100
101 self.lastposition = (-1, -1)
102
103 self.pen = wx.Pen(wx.Colour(0, 144, 255), 5)
104
105 self.dc = wx.ScreenDC()
106 self.dc.SetPen(self.pen)
107
108 self.showgesture = False
109
110 self.wobbletolerance = 7
111
112 self.rawgesture = ''
113
114 self.SetAuto(Auto)
115
7a0c9b39
RD
116 def _check_modifiers(self, event):
117 '''Internal: Returns True if all needed modifiers are down
118 for the given event.'''
119 if len(self.modifiers) > 0:
120 good = True
121 if wx.WXK_CONTROL in self.modifiers:
122 good = good and event.ControlDown()
123 if wx.WXK_SHIFT in self.modifiers:
124 good = good and event.ShiftDown()
125 if wx.WXK_ALT in self.modifiers:
126 good = good and event.AltDown()
127 return good
128 return True
129
51c5e1f2 130 def AddGesture(self, gesture, action, *args):
7a0c9b39 131 '''Registers a gesture, and an associated function, with any arguments needed.'''
51c5e1f2
RD
132 #Make Sure not a duplicate:
133 self.RemoveGesture(gesture)
134
135 self.gestures.append(gesture)
136 self.actions.append(action)
137 self.actionarguments.append(args)
138
7a0c9b39
RD
139 def DoAction(self, gesture):
140 '''If the gesture is in the array of registered gestures, run the associated function.'''
141 if gesture in self.gestures:
142 i = self.gestures.index(gesture)
143 apply(self.actions[i], self.actionarguments[i])
144
51c5e1f2 145 def End(self):
7a0c9b39
RD
146 '''Stops recording the points to create the mouse gesture from,
147 and creates the mouse gesture, returns the result as a string.'''
51c5e1f2
RD
148 self.recording = False
149
150 #Figure out the gestures (Look for occurances of 5 in a row or more):
151
152 tempstring = '0'
7a0c9b39 153 possiblechange = '0'
51c5e1f2
RD
154
155 directions = ''
156
157 for g in self.rawgesture:
158 l = len(tempstring)
159 if g != tempstring[l - 1]:
160 if g == possiblechange:
161 tempstring = g + g
162 else:
7a0c9b39 163 possiblechange = g
51c5e1f2
RD
164 else:
165 tempstring += g
166 if len(tempstring) >= self.wobbletolerance:
167 ld = len(directions)
168 if ld > 0:
169 if directions[ld - 1] != g:
170 directions += g
171 else:
172 directions += g
173 tempstring = '0'
7a0c9b39
RD
174
175 if self.showgesture:
176 self.parent.Refresh()
51c5e1f2
RD
177
178 return directions
179
180 def GetDirection(self, point1, point2):
7a0c9b39 181 '''Gets the direction between two points.'''
51c5e1f2
RD
182 #point1 is the old point
183 #point2 is current
184
185 x1, y1 = point1
186 x2, y2 = point2
187
188 #(Negative = Left, Up)
189 #(Positive = Right, Down)
190
191 horizontal = x2 - x1
192 vertical = y2 - y1
193
194 horizontalchange = abs(horizontal) > 0
195 verticalchange = abs(vertical) > 0
196
197 if horizontalchange and verticalchange:
198 ah = abs(horizontal)
199 av = abs(vertical)
200 if ah > av:
201 if (ah / av) > 2:
202 vertical = 0
203 verticalchange = False
204 elif av > ah:
205 if (av / ah) > 2:
206 horizontal = 0
7a0c9b39 207 horizontalchange = False
51c5e1f2
RD
208
209 if horizontalchange and verticalchange:
210 #Diagonal
211 if (horizontal > 0) and (vertical > 0):
212 return '3'
213 elif (horizontal > 0) and (vertical < 0):
214 return '9'
215 elif (horizontal < 0) and (vertical > 0):
216 return '1'
217 else:
7a0c9b39 218 return '7'
51c5e1f2
RD
219 else:
220 #Straight Line
221 if horizontalchange:
222 if horizontal > 0:
223 return 'R'
224 else:
225 return 'L'
226 else:
227 if vertical > 0:
228 return 'D'
229 else:
230 return 'U'
51c5e1f2 231
7a0c9b39
RD
232 def GetRecording(self):
233 '''Returns whether or not Gesture Recording has started.'''
234 return self.recording
235
51c5e1f2 236 def OnMotion(self, event):
7a0c9b39 237 '''Internal. Used if Start() has been run'''
51c5e1f2
RD
238 if self.recording:
239 currentposition = event.GetPosition()
240 if self.lastposition != (-1, -1):
241 self.rawgesture += self.GetDirection(self.lastposition, currentposition)
242 if self.showgesture:
243 #Draw it!
244 px1, py1 = self.parent.ClientToScreen(self.lastposition)
245 px2, py2 = self.parent.ClientToScreen(currentposition)
7a0c9b39 246 self.dc.DrawLine(px1, py1, px2, py2)
51c5e1f2
RD
247
248 self.lastposition = currentposition
249
250 event.Skip()
251
7a0c9b39
RD
252 def OnMouseEvent(self, event):
253 '''Internal. Used in Auto Mode.'''
254 if event.ButtonDown(self.mousebutton) and self._check_modifiers(event):
255 self.Start()
256 elif (event.ButtonUp(self.mousebutton) or event.Leaving()) and self.GetRecording():
257 result = self.End()
258 self.DoAction(result)
51c5e1f2
RD
259 event.Skip()
260
261 def RemoveGesture(self, gesture):
7a0c9b39 262 '''Removes a gesture, and its associated action'''
51c5e1f2
RD
263 if gesture in self.gestures:
264 i = self.gestures.index(gesture)
7a0c9b39 265
51c5e1f2
RD
266 del self.gestures[i]
267 del self.actions[i]
268 del self.actionarguments[i]
269
270 def SetAuto(self, auto):
7a0c9b39 271 '''Warning: Once auto is set, it stays set, unless you manually use UnBind'''
51c5e1f2 272 if auto:
7a0c9b39 273 self.parent.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvent)
51c5e1f2
RD
274 self.parent.Bind(wx.EVT_MOTION, self.OnMotion)
275
7a0c9b39
RD
276 def SetGesturePen(self, pen):
277 '''Sets the wx pen used to visually represent each gesture'''
278 self.pen = pen
279 self.dc.SetPen(self.pen)
280
281 def SetGesturePen(self, colour, width):
282 '''Sets the colour and width of the line drawn to visually represent each gesture'''
283 self.pen = wx.Pen(colour, width)
284 self.dc.SetPen(self.pen)
285
51c5e1f2 286 def SetGesturesVisible(self, vis):
7a0c9b39 287 '''Sets whether a line is drawn to visually represent each gesture'''
51c5e1f2
RD
288 self.showgesture = vis
289
7a0c9b39
RD
290 def SetModifiers(self, modifiers=[]):
291 '''Takes an array of wx Key constants (Control, Shift, and/or Alt).
292 Leave empty to unset all modifiers.'''
51c5e1f2
RD
293 self.modifiers = modifiers
294
7a0c9b39
RD
295 def SetMouseButton(self, mousebutton):
296 '''Takes the wx constant for the target mousebutton'''
297 self.mousebutton = mousebutton
298
51c5e1f2 299 def SetWobbleTolerance(self, wobbletolerance):
7a0c9b39 300 '''Sets just how much wobble this class can take!'''
51c5e1f2
RD
301 self.WobbleTolerance = wobbletolerance
302
303 def Start(self):
7a0c9b39 304 '''Starts recording the points to create the mouse gesture from'''
51c5e1f2 305 self.recording = True
7a0c9b39 306 self.rawgesture = ''
51c5e1f2 307 self.lastposition = (-1, -1)
7a0c9b39 308 if self.showgesture:
51c5e1f2 309 self.parent.Refresh()