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