source: trunk/src/testing/ui/pointers.py @ 4

Revision 4, 17.1 KB checked in by ajaworski, 13 years ago (diff)

Added modified SAGE sources

RevLine 
[4]1############################################################################
2#
3# SAGE UI - A Graphical User Interface for SAGE
4# Copyright (C) 2005 Electronic Visualization Laboratory,
5# University of Illinois at Chicago
6#
7# All rights reserved.
8#
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions are met:
11#
12#  * Redistributions of source code must retain the above copyright
13#    notice, this list of conditions and the following disclaimer.
14#  * Redistributions in binary form must reproduce the above
15#    copyright notice, this list of conditions and the following disclaimer
16#    in the documentation and/or other materials provided with the distribution.
17#  * Neither the name of the University of Illinois at Chicago nor
18#    the names of its contributors may be used to endorse or promote
19#    products derived from this software without specific prior written permission.
20#
21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
25# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32#
33# Direct questions, comments etc about SAGE UI to www.evl.uic.edu/cavern/forum
34#
35# Author: Ratko Jagodic
36#       
37############################################################################
38
39
40
41import wx, time
42from SAGEDrawObject import PointerObject
43
44
45class DeviceManager:
46    """ An object that brings all the devices under one roof and manages them """
47    def __init__(self, sageData, sageGate):
48        self.sageData = sageData
49        self.sageGate = sageGate
50        self.minAppSize = 250
51        self.pointers = {}  # key=pointerId, value=PointerObject
52
53
54    def __addPointerObject(self, pointerId):
55        if pointerId not in self.pointers:
56            self.pointers[pointerId] = PointerObject(pointerId, self.sageGate)
57        return self.pointers[pointerId]
58
59
60    def processMouseMove(self, pointerId, x, y):
61        ptr = self.__addPointerObject(pointerId)
62        (app, regionId) = self.sageData.checkHits(x,y)
63        if ptr.leftDown:
64            # release the mouse button after 15 secs
65            if time.time() - ptr.leftDownTime > 15:
66                self.processMouseUp(pointerId, x, y)
67            return
68       
69        ptr.updateRegion(regionId, app)
70       
71        if regionId > 0:
72            if ptr.lastRegion != ptr.region:  # corner
73                ptr.showResizePointer()
74        elif ptr.lastRegion[0] > 0:  # it was on a corner previously and now it's not
75            ptr.reset()
76            ptr.lastRegion = [regionId, app]
77           
78        #self.sageGate.movePointer(pointerId, dx, dy)
79
80       
81    def processMouseDown(self, pointerId, x, y):
82        ptr = self.__addPointerObject(pointerId)
83        (app, regionId) = self.sageData.checkHits(x,y)
84        if regionId < 0 or app.hasCapture():
85            return
86       
87        # record the info about this click
88        ptr.onMouseDown(app, regionId, x, y)
89
90        # process the click
91        if regionId == 0:
92            app.captureMouse(pointerId)
93            self.__handleAppWindowHit(pointerId, app.getId())
94        else:
95            app.captureMouse(pointerId)
96            self.__handleAppCornerHit(pointerId, app.getId(), regionId)
97
98
99       
100    def processMouseUp(self, pointerId, x, y):
101        ptr = self.__addPointerObject(pointerId)
102        if not ptr.leftDown: return
103       
104        ptr.onMouseUp()
105
106        if ptr.lastRegion[0] == 0:    # move
107            ptr.lastRegion[1].releaseMouse()
108            # bring the app to front if necessary and then move it
109            if self.sageData.getTopApp()[0] != ptr.lastRegion[1].getId():
110                self.sageGate.bringToFront(ptr.lastRegion[1].getId())
111            self.sageGate.moveWindow(ptr.lastRegion[1].getId(), x-ptr.clickPos[0], y-ptr.clickPos[1])
112           
113        elif ptr.lastRegion[0] > 0:   # resize
114            (l,r,t,b) = ptr.lastRegion[1].getBounds()
115            w = r-l
116            h = t-b
117            aspectRatio = w/float(h)
118            ptr.lastRegion[1].releaseMouse()
119           
120            # don't allow resizing below minAppSize
121            dx = x-ptr.clickPos[0]
122            if ptr.lastRegion[0] == 1:
123                l+=dx; b=b+dx/aspectRatio
124                if r-l < self.minAppSize: l = r - self.minAppSize
125                if t-b < self.minAppSize: b = t - self.minAppSize
126            elif ptr.lastRegion[0] == 2:
127                l+=dx; t=t-dx/aspectRatio
128                if r-l < self.minAppSize: l = r - self.minAppSize
129                if t-b < self.minAppSize: t = b + self.minAppSize
130            elif ptr.lastRegion[0] == 3:
131                r+=dx; t=t+dx/aspectRatio
132                if r-l < self.minAppSize: r = l + self.minAppSize
133                if t-b < self.minAppSize: t = b + self.minAppSize
134            elif ptr.lastRegion[0] == 4:
135                r+=dx; b=b-dx/aspectRatio
136                if r-l < self.minAppSize: r = l + self.minAppSize
137                if t-b < self.minAppSize: b = t - self.minAppSize     
138           
139            self.sageGate.resizeWindow(ptr.lastRegion[1].getId(), l, r, b, t)
140       
141       
142
143    def __handleAppCornerHit(self, pointerId, windowId, cornerId):
144        """ a corner of a window was hit """
145        (l,r,t,b) = self.sageData.getApp(windowId).getBounds()
146        self.sageGate.setMouseResizeState(pointerId, cornerId, l, r, t, b)
147       
148
149    def __handleAppWindowHit(self, pointerId, windowId):
150        """ an app window was hit but not the corner """
151        (l,r,t,b) = self.sageData.getApp(windowId).getBounds()
152        self.sageGate.setMouseMoveState(pointerId, l, r, t, b)
153       
154
155
156
157
158
159class PollTimer(wx.Timer):
160    def SetCallback(self, cb):
161        self.callback = cb
162    def Notify(self):
163        self.callback()
164
165
166
167
168class Pointers:
169    """ main class that keeps track of and manages all the devices on this machine """
170    def __init__(self, sageGate, sageData):
171        self.sageGate = sageGate
172        self.sageData = sageData
173        self.dispSize = sageData.getSageDisplayInformation()[3:5]
174        self.devManager = DeviceManager(sageData, sageGate)
175
176        # out devices
177        self.numJoy = wx.Joystick().GetNumberJoysticks()
178        self.joysticks = {}
179        self.__addJoystick()
180##         self.mouse = None  # it will be MousePointer
181
182        # start the timer that will periodically poll the devices
183        self.t = PollTimer()
184        period = 50  # poll X times/sec (roughly)
185        self.t.SetCallback(self.poll)
186        self.t.Start(1000/period)
187
188
189    def __addJoystick(self):
190        self.sageGate.registerCallbackFunction( 40018, self.onNewJoystickDevice )
191        if len(self.joysticks) < self.numJoy:
192            self.sageGate.addPointer()
193##         else:
194##             print "adding a mouse!"
195##             self.sageGate.registerCallbackFunction( 40018, self.onNewMouseDevice )
196##             self.sageGate.addPointer()
197
198
199    def onNewJoystickDevice(self, data):
200        pointerId = int(data.split()[0])
201        self.joysticks[pointerId] = JoyPointer( self.dispSize, pointerId, \
202                                                len(self.joysticks), \
203                                                self.onButtonDown, self.onButtonUp )
204        self.sageGate.showObject(pointerId)
205        if len(self.joysticks) < self.numJoy:  #add another one
206            self.sageGate.addPointer()
207##         else:   # finally add a mouse
208##             self.sageGate.registerCallbackFunction( 40018, self.onNewMouseDevice )
209##             self.sageGate.addPointer()
210           
211
212##     def onNewMouseDevice(self, data):
213##         pointerId = int(data.split()[0])
214##         self.sageGate.showObject(pointerId)
215##         self.mouse = MousePointer(self.dispSize, pointerId, self.onButtonDown, self.onButtonUp)
216       
217
218##     def shareMouse(self):
219##         if self.mouse:
220##             self.mouse.start()
221       
222
223    def stop(self):
224        if self.t.IsRunning():
225            self.t.Stop()
226
227        for pointerId in self.joysticks.iterkeys():
228            self.sageGate.removePointer(pointerId)
229        ## if self.mouse:
230##             self.sageGate.removePointer(self.mouse.getId())
231
232
233    def onButtonDown(self, pointerId, btn, x, y):
234        self.devManager.processMouseDown(pointerId, x, y)
235
236
237    def onButtonUp(self, pointerId, btn, x, y):
238        self.devManager.processMouseUp(pointerId, x, y)
239
240       
241    def poll(self):
242        """ here we poll the device just for their position
243            the button clicks are dealt with through event handling
244        """
245        for id, joy in self.joysticks.iteritems():
246            dX, dY = joy.getPos()
247            if dX != 0 or dY != 0:
248                self.devManager.processMouseMove(id, joy.getX(), joy.getY())
249                self.sageGate.movePointer(id, dX, dY)
250
251##         if self.mouse and self.mouse.isShared():
252##             dX, dY = self.mouse.getPos()
253##             if dX != 0 or dY != 0:
254##                 self.devManager.processMouseMove(self.mouse.getId(), self.mouse.getX(), self.mouse.getY())
255##                 print "moving pointer to", dX, dY
256##                 self.sageGate.movePointer(self.mouse.getId(), dX, dY)
257
258
259
260class JoyPointer:
261    def __init__(self, dispSize, pointerId, deviceId, onDown, onUp):
262        self.dispSize = dispSize
263        self.pointerId = pointerId
264        self.onDown = onDown
265        self.onUp = onUp
266        self.x = self.y = 100
267
268        # the top frame will capture all our joystick events
269        self.win = wx.GetApp().GetTopWindow()
270        self.joy = wx.Joystick(deviceId)
271        self.joy.SetCapture(self.win)
272        self.win.Bind(wx.EVT_JOY_BUTTON_DOWN, self.onButton)
273        self.win.Bind(wx.EVT_JOY_BUTTON_UP, self.onButton)
274       
275        # analog axis parameters and calibration values
276        # we'll make an assumption that both axis have the
277        # same range of values
278        xmin = self.joy.GetXMin()
279        xmax = self.joy.GetXMax()
280        rng = abs(xmax - xmin)
281        self.centerX = abs( int((xmin + xmax)/2)) #so that 0 <-> max and -max/2 <-> max/2 both work
282        self.drift = int(0.08*rng)
283        self.usefulRange = abs( int( rng/2 - self.drift) )
284
285        # for converting the movement from joystick axis position
286        # to pixel distance on the SAGE display
287        # it depends on the joystick position AND on the display size
288        joyToMouseRatio = 70
289        displayFactor = dispSize[0] / 10000.0 + 0.2
290        self.moveRatio = joyToMouseRatio * displayFactor
291
292
293    def getId(self):
294        return self.pointerId
295   
296
297    def getX(self):
298        return self.x
299
300
301    def getY(self):
302        return self.y
303
304
305    def onButton(self, evt):
306        """ left=1, right=2, middle=3 """
307        btn = -1
308       
309        if evt.GetButtonChange() == 0:                 btn = 1
310        elif evt.GetButtonChange() == wx.JOY_BUTTON1:  btn = 2       
311        elif evt.GetButtonChange() == wx.JOY_BUTTON2:  btn = 3
312        elif evt.GetButtonChange() == wx.JOY_BUTTON3:  btn = 4
313        if btn == -1: return  #dont act on any other button
314       
315        if evt.ButtonUp():
316            self.onUp(self.pointerId, btn, self.x, self.y)
317        else:
318            self.onDown(self.pointerId, btn, self.x, self.y)
319       
320       
321    def getPos(self):
322        """ returns (x, y) """
323
324        # get the analog axis position and map it to some
325        # pixel distance in sage coordinates
326        (dx,dy) = self.joy.GetPosition()
327        (dx,dy) = self.toSAGECoords(dx,dy)
328       
329        dx = int(round(dx))
330        dy = int(round(dy))
331       
332        # keep the pointer inside the display
333        if self.x + dx < 0:
334            dx = -self.x
335        elif self.x + dx > self.dispSize[0]:
336            dx = self.dispSize[0] - self.x
337        if self.y + dy < 0:
338            dy = -self.y
339        elif self.y + dy > self.dispSize[1]:
340            dy = self.dispSize[1] - self.y
341
342        # accumulate the position
343        self.x += dx
344        self.y += dy
345       
346        return dx, dy
347
348           
349    def toSAGECoords(self, x, y):
350        # first map the values from 0-max to -max/2 to max/2 (if needed)
351        x -= self.centerX
352        y -= self.centerX
353
354        # apply the movement threshold and get the normalized crds
355        if abs(x) <= self.drift:
356            x = 0
357        else:
358            if x > 0:  x = float(x-self.drift)/self.usefulRange
359            else: x = float(x+self.drift)/self.usefulRange
360            x *= self.moveRatio #50
361
362        if abs(y) <= self.drift:
363            y = 0
364        else:
365            if y > 0:  y = float(y-self.drift)/self.usefulRange
366            else: y = float(y+self.drift)/self.usefulRange
367            y *= -self.moveRatio #(-50)
368
369        return (x,y)
370
371
372
373   
374## class MousePointer:
375##     def __init__(self, dispSize, pointerId, onDown, onUp):
376##         self.dispSize = dispSize
377##         self.pointerId = pointerId
378##         self.onDown = onDown
379##         self.onUp = onUp
380##         self.screenSize = wx.GetDisplaySize()
381##         self.frame = wx.GetApp().GetTopWindow()
382
383##         # figure out the mapping between the local screen and the display
384##         i = self.dispSize.index( max(self.dispSize[0], self.dispSize[1]) )
385##         self.moveRatio = self.dispSize[i] / float(self.screenSize[i])
386##         self.x = self.y = 100
387##         self.__shared = False
388
389       
390##     def getX(self):
391##         return self.x
392
393
394##     def getY(self):
395##         return self.y
396
397
398##     def getId(self):
399##         return self.pointerId
400
401
402##     def isShared(self):
403##         return self.__shared
404   
405
406##     def makeControls(self):
407##         winSize = (400, 200)
408##         winPos = (self.screenSize[0]/2 - winSize[0]/2, self.screenSize[1]/2 - winSize[1]/2)
409##         self.dlg = wx.Frame(self.frame, wx.ID_ANY, "", \
410##                              winPos, winSize)
411##         self.dlg.Bind(wx.EVT_CLOSE, self.onClose)
412##         self.dlg.Bind(wx.EVT_KEY_DOWN, self.onKey)
413##         self.dlg.Bind(wx.EVT_LEFT_DOWN, self.onButton)
414##         self.dlg.Bind(wx.EVT_RIGHT_DOWN, self.onButton)
415##         self.dlg.Bind(wx.EVT_MIDDLE_DOWN, self.onButton)
416##         self.dlg.Bind(wx.EVT_LEFT_UP, self.onButton)
417##         self.dlg.Bind(wx.EVT_RIGHT_UP, self.onButton)
418##         self.dlg.Bind(wx.EVT_MIDDLE_UP, self.onButton)
419       
420##         sizer = wx.BoxSizer(wx.VERTICAL)
421##         label = wx.StaticText(self.dlg, wx.ID_ANY, "Press ESC to stop sharing your mouse")
422##         sizer.Add(label, 0, wx.ALIGN_CENTER | wx.BOTTOM | wx.LEFT | wx.RIGHT, border=5)
423##         self.dlg.SetSizer(sizer)
424##         self.dlg.CaptureMouse()
425##         self.__shared = True
426##         self.dlg.Show()
427
428
429##     def onClose(self, evt):
430##         self.stop()
431       
432
433##     def onKey(self, evt):
434##         if evt.GetKeyCode() == wx.WXK_ESCAPE:
435##             self.Close(True)
436##         else:
437##             evt.Skip()
438
439
440##     def onButton(self, evt):
441##         btn = -1
442##         if evt.Button(wx.MOUSE_BTN_LEFT):     btn = 1
443##         elif evt.Button(wx.MOUSE_BTN_RIGHT):  btn = 2
444##         elif evt.Button(wx.MOUSE_BTN_MIDDLE): btn = 3
445
446##         if btn == -1: return  #dont act on any other button
447       
448##         if evt.ButtonUp():
449##             self.onUp(self.pointerId, btn, self.x, self.y)
450##         else:
451##             self.onDown(self.pointerId, btn, self.x, self.y)
452           
453
454##     def start(self):
455##         self.makeControls()
456       
457
458##     def stop(self):
459##         self.__shared = False
460##         if self.dlg.HasCapture():
461##             self.dlg.ReleaseMouse()
462##         self.dlg.Destroy()
463
464
465##     def getPos(self):
466##         # get the new mouse position and figure out how
467##         # much it moved in sage coordinates
468##         p = wx.GetMousePosition().Get()
469##         newPos = [ int(p[0]*self.moveRatio), int(p[1]*self.moveRatio) ]
470##         dx = newPos[0] - self.x
471##         dy = newPos[1] - self.y
472       
473##         # keep the pointer inside the display
474##         if self.x + dx < 0:
475##             dx = -self.x
476##         elif self.x + dx > self.dispSize[0]:
477##             dx = self.dispSize[0] - self.x
478##         if self.y + dy < 0:
479##             dy = -self.y
480##         elif self.y + dy > self.dispSize[1]:
481##             dy = self.dispSize[1] - self.y
482           
483##         # accumulate the position
484##         self.x += dx
485##         self.y += dy
486       
487##         return dx, dy
488           
Note: See TracBrowser for help on using the repository browser.