source: modrana.py @ c0ca0ae

Last change on this file since c0ca0ae was c0ca0ae, checked in by xkolman2 <xkolman2@…>, 3 years ago

refactoring & documentation update

git-svn-id: https://nlp.fi.muni.cz/svn/gps_navigace/trunk@443 0858a4d0-ffff-46e5-938e-62b5ecb34222

  • Property mode set to 100755
File size: 31.5 KB
Line 
1#!/usr/bin/python
2#----------------------------------------------------------------------------
3# Rana main GUI.  Displays maps, for use on a mobile device
4#
5# Controls:
6#   * click on the overlay text to change fields displayed
7#----------------------------------------------------------------------------
8# Copyright 2007-2008, Oliver White
9#
10# This program is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program.  If not, see <http://www.gnu.org/licenses/>.
22#----------------------------------------------------------------------------
23#import dbus.glib
24import time
25startTimestamp = time.time()
26import pygtk
27pygtk.require('2.0')
28import gobject
29import gtk
30import math
31import sys
32import traceback
33import os
34from gtk import gdk
35from math import radians
36importsDoneTimestamp = time.time()
37
38
39def update1(mapWidget):
40  mapWidget.update()
41  return(True)
42
43def update2(mapWidget):
44  mapWidget.checkForRedraw()
45  return(True)
46
47def simplePythagoreanDistance(x1, y1, x2, y2):
48    dx = x2 - x1
49    dy = y2 - y1
50    return math.sqrt(dx**2 + dy**2)
51
52class MapWidget(gtk.Widget):
53  __gsignals__ = { \
54    'realize': 'override',
55    'expose-event' : 'override',
56    'size-allocate': 'override',
57    'size-request': 'override',
58    }
59  def __init__(self,device):
60    gtk.Widget.__init__(self)
61    self.draw_gc = None
62    self.device = device
63    self.dmod = None # device specific module
64    self.currentDrawMethod = self.fullDrawMethod
65
66    self.centeringDisableTreshold = 2048
67
68    self.msLongPress = 400
69
70    self.modulesFolder = 'modules'
71
72    """ setting this both to 100 and 1 in mapView and gpsd fixes the arow orientation bug """
73    self.timer1 = gobject.timeout_add(100, update1, self) #default 100
74#    self.timer2 = gobject.timeout_add(10, update2, self) #default 10
75    self.timer3 = None # will be used for timing long press events
76    self.d = {} # List of data
77    self.m = {} # List of modules
78    self.watches = {} # List of data change watches
79    self.maxWatchId = 0
80    self.watch('needRedraw', self._checkForRedrawCB) # react on redraw requests
81    # TODO: add a function for directly requesting redraw
82
83    self.mapRotationAngle = 0 # in radians
84    self.notMovingSpeed = 1 # in m/s
85
86    self.topWindow = None
87
88    self.redraw = True
89
90    self.showRedrawTime = False
91   
92    # map center shifting variables
93    self.centerShift = (0,0)
94
95    # alternative map drag variables
96    self.altMapDragEnabled = False
97    self.altMapDragInProgress = False
98    self.shift = (0,0,0,0)
99
100    self.defaulMethodBindings() # use default map dragging method binding
101
102    self.lastFullRedraw = time.time()
103    self.lastFullRedrawRequest = time.time()
104
105  def loadModules(self):
106    """Load all modules from the specified directory"""
107
108    modulesFolder = self.modulesFolder
109    device = self.device
110    sys.path.append(modulesFolder)
111    print "importing modules:"
112    start = time.clock()
113    self.initInfo={
114              'modrana': self,
115              'device': device, # TODO: do this directly
116              'name': ""
117             }
118
119    # get possible module names
120    moduleNames = self._getModuleNamesFromFolder(modulesFolder)
121    loadModule = self._loadModule
122    # load if possible
123    for moduleName in moduleNames:
124        # filter out .py
125        moduleName = moduleName.split('.')[0]
126        loadModule(moduleName, moduleName[4:])
127
128    # load the device specific module
129    deviceModulesPath = os.path.join(modulesFolder, "device_modules")
130    sys.path.append(deviceModulesPath)
131    dmod = loadModule("device_%s" % device, "device")
132    if dmod == None:
133      print("modRana: loading Neo device module as failsafe")
134      device = "neo"
135      dmod = loadModule("device_%s" % device, "device")
136    self.dmod = dmod
137
138    print "Loaded all modules in %1.2f ms, initialising" % (1000 * (time.clock() - start))
139
140    # make sure all modules have the device module and other variables before first time
141    for m in self.m.values():
142      m.modrana = self # make this class accessible from modules
143      m.dmod = self.dmod
144
145    # as the options module should be already loaded, we can update the viewport
146    self._updateViewport()
147
148    # run what needs to be done before firstTime is called
149    self._modulesLoadedPreFirstTime()
150
151    start = time.clock()
152    for m in self.m.values():
153      m.firstTime()
154
155    # run what needs to be done after firstTime is called
156    self._modulesLoadedPostFirstTime()
157
158    print "Initialization complete in %1.2f ms" % (1000 * (time.clock() - start))
159
160  def _getModuleNamesFromFolder(self,folder):
161    """list a given folder and find all possible module names"""
162
163    return filter(lambda x: x[0:4]=="mod_" and x[-4:]!=".pyc",os.listdir(folder))
164
165  def _loadModule(self, importName, modRanaName):
166    """load a single module by name from path"""
167    startM = time.clock()
168    try:
169      a = __import__(importName)
170      initInfo = self.initInfo
171      name = modRanaName
172      initInfo['name'] = name
173      module = a.getModule(self.m, self.d, initInfo)
174      self.m[name] = module
175      print " * %s: %s (%1.2f ms)" % (name, self.m[name].__doc__, (1000 * (time.clock() - startM)))
176      return module
177    except Exception, e:
178      print "modRana: module: %s/%s failed to load" % (importName, modRanaName)
179      traceback.print_exc(file=sys.stdout) # find what went wrong
180      return None
181
182  def _modulesLoadedPreFirstTime(self):
183    """this is run after all the modules have been loaded,
184    but before their first time is called"""
185    self._updateCenteringShiftCB()
186
187    """to nly update values needed for map drawing when something changes
188       * window is resized
189       * user switches something rleated in options
190       * etc.
191       we use the key watching mechanism
192       once a related key is changed, we update all the values
193       """
194    # watch both centering shift related variables
195    self.watch('posShiftAmount', self._updateCenteringShiftCB)
196    self.watch('posShiftDirection', self._updateCenteringShiftCB)
197    # also watch the viewport
198    self.watch('viewport', self._updateCenteringShiftCB)
199    # and map scaling
200    self.watch('mapScale', self._updateCenteringShiftCB)
201
202  def _modulesLoadedPostFirstTime(self):
203    """this is run after all the modules have been loaded,
204    after before their first time is called"""
205   
206    # check if redrawing time should be printed to terminal
207    if 'showRedrawTime' in self.d and self.d['showRedrawTime'] == True:
208      self.showRedrawTime = True
209
210  def beforeDie(self):
211    print "Shutting-down modules"
212    for m in self.m.values():
213      m.shutdown()
214    time.sleep(2) # leave some times for threads to shut down
215    print "Shuttdown complete"
216   
217  ## OPTIONS SETTING AND WATCHING ##
218
219  def get(self, name, default=None):
220    """Get an item of data"""
221    return(self.d.get(name, default))
222
223  def set(self, name, value, save=False):
224    """Set an item of data"""
225    self.d[name] = value
226    """options are normally saved on shutdown,
227    but for some data we want to make sure they are stored and not
228    los for example becuase of power outage/empty battery, etc."""
229    if save:
230      options = self.m.get('options')
231      if options:
232        options.save()
233    if name in self.watches.keys():
234      self._notifyWatcher(name, value)
235
236  def watch(self, key, callback, *args):
237    """add a callback on an options key
238    callbakc will get:
239    key, newValue, oldValue, *args
240
241    """
242    nrId = self.maxWatchId + 1
243    id = "%d_%s" % (nrId,key)
244    self.maxWatchId = nrId # TODO: recycle ids ? (alla PID)
245    if key not in self.watches:
246      self.watches[key] = [] # create the initial list
247    self.watches[key].append((id,callback,args))
248    return id
249
250  def removeWatch(self, id):
251    """remove watch specified by the given watch id"""
252    (nrId, key) = id.split('_')
253
254    if key in self.watches:
255      remove = lambda x:x==id
256      self.watches[key][:] = [x for x in self.watches[key] if not remove(x)]
257    else:
258      print "modRana: cant remove watch - key does not exist, watchId:", id
259
260  def _notifyWatcher(self, key, newValue):
261    """run callbacks registered on an options key"""
262    callbacks = self.watches.get(key, None)
263    if callbacks:
264      for item in callbacks:
265        (id,callback,args) = item
266        oldValue = self.get(key, None)
267        if callback:
268          callback(key,newValue,oldValue, *args)
269        else:
270          print "invalid watcher callback :", callback
271
272  def update(self):
273    for m in self.m.values():
274      m.update()
275
276  def _checkForRedrawCB(self, key, oldValue, newValue):
277    """react to redraw requests"""
278    if newValue == True:
279      self.forceRedraw()
280     
281  def forceRedraw(self):
282    """Make the window trigger a draw event.
283    TODO: consider replacing this if porting pyroute to another platform"""
284    self.d['needRedraw'] = False
285    """ alter directly, no need to notificate
286    about returning the key to default state"""
287
288    # record timestamp
289    self.lastFullRedrawRequest = time.time()
290
291    if self.redraw:
292      try:
293        self.window.invalidate_rect((0,0,self.rect.width,self.rect.height),False)
294      except Exception, e:
295        print "error in screen invalidating function"
296        print "exception: %s" % e
297
298  def mousedown(self,x,y):
299    """this signalizes start of a drag or a just a click"""
300    pass
301   
302  def click(self, x, y, msDuration):
303    """this fires after a drag is finished or mouse button released"""
304    m = self.m.get("clickHandler",None)
305    if m:
306      m.handleClick(x,y,msDuration)
307      self.update()
308     
309  def released(self,event):
310    """mouse button has been released or
311    the tapping object has been lifted up from the touchscreen"""
312
313    #always unlock drag on release
314    self.unlockDrag()
315
316    if self.altDragEnd:
317      self.altDragEnd(event)
318     
319  def handleDrag(self,x,y,dx,dy,startX,startY,msDuration):
320    # check if centering is on
321    if self.get("centred",True):
322      fullDx = x - startX
323      fullDy = y - startY
324      distSq = fullDx * fullDx + fullDy * fullDy
325      """ check if the drag is strong enought to disable centering
326      -> like this, centering is not dsabled by pressing buttons"""
327      if self.centeringDisableTreshold:
328        if distSq > self.centeringDisableTreshold:
329          self.set("centred", False) # turn off centering after dragging the map (like in TangoGPS)
330          self.d["needRedraw"] = True
331    else:
332      if self.altMapDragEnabled:
333        # start simple map drag if its not already in progress
334        menuName = self.d.get('menu', None)
335        if menuName == None and not self.altMapDragInProgress:
336          self.altDragStart(x-startX,y-startY,dx,dy)
337        elif self.altMapDragInProgress:
338          self.altDragHandler(x-startX,y-startY,dx,dy)
339      else:
340        m = self.m.get("clickHandler",None)
341        if m:
342          m.handleDrag(startX,startY,dx,dy,x,y,msDuration)
343
344  def handleLongPress(self, pressStartEpoch, msCurrentDuration, startX, startY, x, y):
345    """handle long press"""
346    m = self.m.get("clickHandler",None)
347    if m:
348      m.handleLongPress(pressStartEpoch, msCurrentDuration, startX, startY, x, y)
349       
350  def lockDrag(self):
351    """start ignoring drag events"""
352    self.dragLocked = True
353   
354  def unlockDrag(self):
355    """stop ignoring drag events"""
356    self.dragLocked = False
357
358  def setCDDragTreshold(self, treshold):
359    """set the treshold which needs to be reached to disable centering while dragging
360    basically, larger treshold = longer drag is needed to disable centering
361    default value = 2048
362    """
363    self.centeringDisableTreshold = treshold
364   
365  def _updateCenteringShiftCB(self, key=None, oldValue=None, newValue=None):
366    """update shifted centering amount
367
368    this method is called if posShiftAmount or posShiftDirection
369    are set and also once at startup"""
370    (sx,sy,sw,sh) = self.get('viewport')
371    x=0
372    y=0
373    shiftAmount = self.d.get('posShiftAmount', 0.75)
374    """this value might show up as string, so we convert it to float, just to be sure"""
375    floatShiftAmount = float(shiftAmount)
376    shiftDirection = self.d.get('posShiftDirection', "down")
377    if shiftDirection:
378      if shiftDirection == "down":
379        y =  sh * 0.5 * floatShiftAmount
380      elif shiftDirection == "up":
381        y =  - sh * 0.5 * floatShiftAmount
382      elif shiftDirection == "left":
383        x =  - sw * 0.5 * floatShiftAmount
384      elif shiftDirection == "right":
385        x =  + sw * 0.5 * floatShiftAmount
386      """ we dont need to do anything if direction is set to don't shift (False)
387      - 0,0 will be used """
388    self.centerShift = (x,y)
389   
390    # update the viewport expansion variable
391    scale = int(self.get('mapScale', 1))
392    tileSide = 256
393    mapTiles = self.m.get('mapTiles')
394    if mapTiles: # check the mapTiles for tile side length in pixels, if available
395      tileSide = mapTiles.tileSide
396    tileSide = tileSide * scale # apply any possible scaling
397    (centerX,centerY) = ((sw/2.0),(sh/2.0))
398    ulCenterDistance = simplePythagoreanDistance(0, 0, centerX, centerY)
399    centerLLdistance = simplePythagoreanDistance(centerX, centerY, sw, sh)
400    diagonal = max(ulCenterDistance, centerLLdistance)
401    add = int(math.ceil(float(diagonal)/tileSide))
402    self.expandViewportTiles = add
403
404
405  def draw(self, cr, event):
406    """ re/Draw the modrana GUI """
407    start = time.clock()
408    # run the currently used draw method
409    self.currentDrawMethod(cr,event)
410    # enable redraw speed debugging
411    if self.showRedrawTime:
412      print "Redraw took %1.2f ms" % (1000 * (time.clock() - start))
413    self.lastFullRedraw = time.time()
414
415  def getLastFullRedraw(self):
416    return self.lastFullRedraw
417
418  def getLastFullRedrawRequest(self):
419    return self.lastFullRedrawRequest
420
421  def fullDrawMethod(self, cr, event):
422    """ this is the default drawing method
423    draws all layers and should be used together with full screen redraw """
424
425    for m in self.m.values():
426      m.beforeDraw()
427
428    menuName = self.d.get('menu', None)
429    if menuName: # draw the menu
430      for m in self.m.values():
431        m.drawMenu(cr, menuName)
432    else: # draw the map
433      cr.set_source_rgb(0.2,0.2,0.2) # map background
434      cr.rectangle(0,0,self.rect.width,self.rect.height)
435      cr.fill()
436      if (self.d.get("centred", False) and self.d.get("rotateMap", False)):
437        proj = self.m['projection']
438        (lat, lon) = (proj.lat,proj.lon)
439        (x1,y1) = proj.ll2xy(lat, lon)
440
441        (x,y) = self.centerShift
442        cr.translate(x,y)
443        cr.save()
444        # get the speed and angle
445        speed = self.d.get('speed', 0)
446        angle = self.d.get('bearing', 0)
447       
448        """
449        only if current direction angle and speed are known,
450        submit a new angle
451        like this, the map does not revert back to default orientation
452        on short GPS errors
453        """
454        if angle and speed:
455          if speed > self.notMovingSpeed: # do we look like we are moving ?
456            self.mapRotationAngle = angle
457        cr.translate(x1,y1) # translate to the rotation center
458        cr.rotate(radians(360 - self.mapRotationAngle)) # do the rotation
459        cr.translate(-x1,-y1) # translate back
460
461        # Draw the base map, the map overlays, and the screen overlays
462        try:
463          for m in self.m.values():
464            m.drawMap(cr)
465          for m in self.m.values():
466            m.drawMapOverlay(cr)
467        except Exception, e:
468          print "modRana main loop: an exception occured:\n"
469          traceback.print_exc(file=sys.stdout) # find what went wrong
470        cr.restore()
471        cr.translate(-x,-y)
472        for m in self.m.values():
473          m.drawScreenOverlay(cr)
474      else: # centering is disabled, just draw the map
475        try:
476          for m in self.m.values():
477            m.drawMap(cr)
478          for m in self.m.values():
479            m.drawMapOverlay(cr)
480        except Exception, e:
481          print "modRana main loop: an exception occured:\n"
482          traceback.print_exc(file=sys.stdout) # find what went wrong
483        for m in self.m.values():
484          m.drawScreenOverlay(cr)
485
486#    if 'showRedrawTime' in self.d and self.d['showRedrawTime'] == True:
487
488#  def draw2(self, cr1):
489#    start = time.clock()
490#
491#
492##      cr.paint()
493##      print cr.get_target()
494##      print cr.get_target().get_width()
495##      print cr.get_target().get_height()
496#
497#    mapAndMapOverlayBuffer = self.getMapAndMapOverlayBuffer()
498#    if mapAndMapOverlayBuffer:
499#      cr1.set_source_surface(mapAndMapOverlayBuffer, float(self.centerX), float(self.centerY))
500#      cr1.paint()
501#    start1 = time.clock()
502#    for m in self.m.values():
503#      m.drawScreenOverlay(cr1)
504#
505#    # enable redraw speed debugging
506#    if 'showRedrawTime' in self.d and self.d['showRedrawTime'] == True:
507#      print "Redraw1 took %1.2f ms" % (1000 * (time.clock() - start))
508#      print "Redraw2 took %1.2f ms" % (1000 * (time.clock() - start1))
509#
510#  def getMapAndMapOverlayBuffer(self):
511#    if self.mapBuffer == None:
512#      self.mapBuffer = self.drawMapAndMapOverlay()
513#    return self.mapBuffer
514#
515#  def drawMapAndMapOverlay(self):
516#    mapAndMapOverlayBuffer = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.rect.width, self.rect.height)
517#    ct = cairo.Context(mapAndMapOverlayBuffer)
518#    cr = gtk.gdk.CairoContext(ct)
519#
520#    for m in self.m.values():
521#      m.beforeDraw()
522#
523#    menuName = self.d.get('menu', None)
524#    if(menuName != None):
525#      for m in self.m.values():
526#        m.drawMenu(cr1, menuName)
527#    else:
528#      # map background
529#      cr.set_source_rgb(0.2,0.2,0.2)
530#      cr.rectangle(0,0,self.rect.width,self.rect.height)
531#      cr.fill()
532#
533#      cr.save()
534#      if (self.d.get("centred", False)):
535#        if self.d.get("rotateMap", False):
536#
537#          # get the speed and angle
538#          speed = self.d.get('speed', 0)
539#          angle = self.d.get('bearing', 0)
540#
541#          proj = self.m['projection']
542#          (lat, lon) = (proj.lat,proj.lon)
543#          (x1,y1) = proj.ll2xy(lat, lon)
544#
545#          """
546#          only if current direction angle and speed are known,
547#          submit a new angle
548#          like this, the map does not revert back to default orientation
549#          on short GPS errors
550#          """
551#          if angle and speed:
552#            if speed > self.notMovingSpeed: # do we look like we are moving ?
553#              angle = 360 - angle
554#              self.mapRotationAngle = radians(angle)
555#          cr.translate(x1,y1) # translate to the rotation center
556#          cr.rotate(self.mapRotationAngle) # do the rotation
557#          cr.translate(-x1,-y1) # translate back
558#
559#      # Draw the base map, the map overlays, and the screen overlays
560#      for m in self.m.values():
561#        m.drawMap(cr)
562#      cr.restore()
563#      for m in self.m.values():
564#        m.drawMapOverlay(cr)
565#
566#      return mapAndMapOverlayBuffer
567
568## clean map + overlay generation reference
569#    staticMap = cairo.ImageSurface(cairo.FORMAT_ARGB32,w,h)
570#    cr1 = cairo.Context(staticMap)
571#    for m in self.m.values():
572#      m.beforeDraw()
573#    try:
574#      for m in self.m.values():
575#        m.drawMap(cr1)
576#      for m in self.m.values():
577#        m.drawMapOverlay(cr1)
578#    except Exception, e:
579#      print "modRana simple map: an exception occured:\n"
580#      traceback.print_exc(file=sys.stdout) # find what went wrong
581#      self.stopSimpleMapDrag()
582#      return
583#    self.simpleStaticMap = staticMap
584
585## damage area computation reference
586#    damage = gtk.gdk.region_rectangle((x,y,w,h))
587#    keep = gtk.gdk.region_rectangle((dx,dy,w,h))
588#    damage.subtract(keep)
589
590
591## FASTER MAP DRAGGING ##
592
593  def setDefaultDrag(self):
594    """try to revert to the default dragging method"""
595    if self.altDragRevert:
596      self.altDragRevert()
597    else: # just to be sure
598      self.defaulMethodBindings()
599      self.altMapDragEnabled = False
600
601  def setCurrentRedrawMethod(self, method=None):
602    if method == None:
603      self.currentDrawMethod = self.fullDrawMethod
604    else:
605      self.currentDrawMethod = method
606
607
608  def defaulMethodBindings(self):
609    """revert drag method bindings to default state"""
610    self.altDragStart = None # alternative map drag enabler
611    self.altDragHandler = None # alternative map drag handler
612    self.altDragEnd = None
613    self.altDragRevert = None # reverts the current alt map drag to default
614    self.currentDrawMethod = self.fullDrawMethod
615
616  def initGCandBackingPixmap(self,w,h):
617    if self.window:
618      self.gc = self.window.new_gc(background=(gtk.gdk.color_parse('white')))
619      self.gc.set_clip_rectangle(gtk.gdk.Rectangle(0, 0, w, h))
620      self.backingPixmap = gtk.gdk.Pixmap(self.window, int(w), int(h), depth=-1)
621
622# static map image dragging #
623  def staticMapDragEnable(self):
624    """enable static map dragging method"""
625    # first do a cleanup
626    self.setDefaultDrag()
627
628    self.d['needRedraw'] = True
629
630    # then bind all relevant methods to variables
631    self.altDragStart = self.staticMapDragStart
632    self.altDragEnd = self.staticMapDragEnd
633    self.altDragHandler = self.staticMapDrag
634    self.altDragRevert = self.staticMapRevert
635    # enable alternative map drag
636    self.altMapDragEnabled = True
637
638  def staticMapDragStart(self,shiftX,shiftY,dx,dy):
639    """start the simple map dragging procedure"""
640    self.altMapDragInProgress = True
641    self.setCurrentRedrawMethod(self.staticMapPixmapDrag)
642    # get the map and overlay
643    (sx,sy,w,h) = self.d.get('viewport')
644
645    # store current screen content in backing pixmap
646    self.backingPixmap.draw_drawable(self.gc, self.window, 0, 0, 0,0,-1,-1)
647    # initiate first drag
648    self.staticMapDrag(shiftX, shiftY,dx,dy)
649    self.set('needRedraw',True)
650
651  def staticMapDragEnd(self, event):
652    """revert the changes neede for the drag"""
653    proj = self.m['projection']
654    (shiftX,shiftY,dx,dy) = self.shift
655    proj.nudge(shiftX, shiftY)
656    self.shift = (0,0,0,0)
657
658    # disable alternative map drag
659    self.altMapDragInProgress = False
660
661    # return the defaultRedrawMethod
662    self.setCurrentRedrawMethod()
663
664    # redraw the whole screen
665    self.set('needRedraw',True)
666
667  def staticMapDrag(self,shiftX,shiftY,dx,dy):
668    """drag the map"""
669    self.shift = (shiftX,shiftY,dx,dy)
670    (x,y,w,h) = (self.rect.x, self.rect.y, self.rect.width, self.rect.height)
671    self.set('needRedraw',True)
672
673  def staticMapPixmapDrag(self, cr, event):
674    (x,y,w,h) = (self.rect.x, self.rect.y, self.rect.width, self.rect.height)
675    (shiftX,shiftY,dx,dy) = self.shift
676    self.window.draw_drawable(self.gc, self.backingPixmap, 0,0,int(shiftX),int(shiftY),-1,-1)
677
678  def staticMapRevert(self):
679    self.defaulMethodBindings()
680    self.altMapDragEnabled = False
681
682  def do_realize(self):
683    self.set_flags(self.flags() | gtk.REALIZED)
684    self.window = gdk.Window( \
685      self.get_parent_window(),
686      width = self.allocation.width,
687      height = self.allocation.height,
688      window_type = gdk.WINDOW_CHILD,
689      wclass = gdk.INPUT_OUTPUT,
690      event_mask = self.get_events() | gdk.EXPOSURE_MASK)
691    self.window.set_user_data(self)
692    self.style.attach(self.window)
693    self.style.set_background(self.window, gtk.STATE_NORMAL)
694    self.window.move_resize(*self.allocation)
695
696    # initialize resources needed for fast map dragging support
697    self.initGCandBackingPixmap(self.allocation.width, self.allocation.height)
698
699    # alow the modules to manipulate the window TODO: do this more elegantly (using signals ?)
700    for name in self.m:
701      self.m[name].mainWindow = self.window
702      self.m[name].topWindow = self.topWindow
703
704  def do_size_request(self, allocation):
705    pass
706  def do_size_allocate(self, allocation):
707    self.allocation = allocation
708    self.rect = self.allocation
709    self._updateViewport()
710   
711    if self.flags() & gtk.REALIZED:
712      self.window.move_resize(*allocation)
713    newW = allocation[2]
714    newH = allocation[3]
715
716    print "size allocate", allocation
717    # resize the backing pixmap
718    self.initGCandBackingPixmap(self.allocation.width, self.allocation.height)
719
720    # notify all modules
721    for m in self.m.values(): # enable resize handling by modules
722        m.handleResize(newW, newH)
723    self.forceRedraw() # redraw window contents after resize
724
725  def _updateViewport(self):
726    """update the current viewport in the global perzistent dictionary"""
727    self.set('viewport', (self.rect.x, self.rect.y, self.rect.width, self.rect.height))
728
729  def _expose_cairo(self, event, cr):
730    #self.modules['projection'].setView( \
731    #  self.rect.x,
732    #  self.rect.y,
733    #  self.rect.width,
734    #  self.rect.height)
735   
736    if(1): # optional set clipping in cairo
737      cr.rectangle(
738        self.rect.x,
739        self.rect.y,
740        self.rect.width,
741        self.rect.height)
742      cr.clip()
743   
744    self.draw(cr,event)
745  def do_expose_event(self, event):
746    self.chain(event)
747    cr = self.window.cairo_create()
748    return self._expose_cairo(event, cr)
749
750class GuiBase:
751  """Wrapper class for a GUI interface"""
752  def __init__(self, device):
753    # start timing the launch
754    self.timing = []
755    self.addCustomTime("modRana start",startTimestamp)
756    self.addCustomTime("imports done", importsDoneTimestamp)
757    self.addTime("GUI creation")
758
759    # Create the window
760
761    #TODO: do this more cleanly:
762
763    """when on N900, use a hildon StackableWindow, which
764    enables app menu and tohe features on Maemo 5"""
765    if device == 'n900':
766      try:
767        import hildon
768        win = hildon.StackableWindow()
769      except Exception, e:
770        print "creating hildon stackable window failed"
771        print e
772        win = gtk.Window()
773    else:
774      win = gtk.Window()
775
776    win.set_title('modRana')
777    win.connect('delete-event', gtk.main_quit)
778    self.addTime("window created")
779
780    # press length timing
781    self.lastPressEpoch = 0
782    self.pressInProgress = False
783    self.pressLengthTimer = None
784
785    if(device == 'eee'): # test for use with asus eee
786      win.resize(800,600)
787      win.move(gtk.gdk.screen_width() - 900, 50)
788    if(device == 'netbook'): # test for use with asus eee
789      win.resize(800,600)
790      win.move(gtk.gdk.screen_width() - 900, 50)
791    elif(device == 'n900'): # test for use with nokie N900
792      win.resize(800,480)
793    elif(device == 'q7'): # test for use with Smart Q7
794      win.resize(800,480)
795      win.move(gtk.gdk.screen_width() - 900, 50)
796    elif(device == 'n95'): # test for use with nokia 95
797      win.resize(480,640)
798      win.move(gtk.gdk.screen_width() - 500, 50)
799    elif(device == 'square'): # for testing equal side displays
800      win.resize(480,480)
801      win.move(gtk.gdk.screen_width() - 500, 50)
802    elif(device == 'ipaq'): #  for some 240*320 displays (like most old Ipaqs/PocketPCs)
803      win.resize(240,320)
804      win.move(gtk.gdk.screen_width() - 500, 50)
805    else: # test for use with neo freerunner
806      win.resize(480,640)
807      win.move(gtk.gdk.screen_width() - 500, 50)
808   
809    # Events
810    event_box = gtk.EventBox()
811
812    event_box.connect("button_press_event", self.pressed)
813    event_box.connect("button_release_event", self.released)
814    event_box.connect("motion_notify_event", self.moved)
815    win.add(event_box)
816
817    # Create the map
818    self.mapWidget = MapWidget(device)
819    self.mapWidget.topWindow=win # make the main widown accessible from modules
820    event_box.add(self.mapWidget)
821    self.addTime("map widget created")
822
823    # Finalise the window
824    win.show_all()
825    self.addTime("window finalized")
826
827    # start loading modules
828    self.mapWidget.loadModules() # name of the folder with modules
829
830    # add last timing checkpoint
831    self.addTime("all modules loaded")
832
833    # report startup time
834    self.reportStartupTime()
835
836    # start gtk main loop
837    gtk.main()
838    self.mapWidget.beforeDie()
839
840## STARTUP TIMING ##
841
842  def addTime(self, message):
843    timestamp = time.time()
844    self.timing.append((message,timestamp))
845    return (timestamp)
846
847  def addCustomTime(self, message, timestamp):
848    self.timing.append((message,timestamp))
849    return (timestamp)
850
851  def reportStartupTime(self):
852    if self.timing:
853      print "** modRana startup timing **"
854     
855      # print device identificator and name
856      if self.mapWidget.dmod:
857        deviceName = self.mapWidget.dmod.getDeviceName()
858        print "# device: %s (%s)" % (deviceName, device)
859
860      tl = self.timing
861      startupTime = tl[0][1] * 1000
862      lastTime = startupTime
863      totalTime = (tl[-1][1] * 1000) - startupTime
864      for i in tl:
865        (message, t) = i
866        t = 1000 * t # convert to ms
867        timeSpent = t - lastTime
868        timeSinceStart = t - startupTime
869        print "* %s (%1.0f ms), %1.0f/%1.0f ms" % (message, timeSpent, timeSinceStart, totalTime)
870        lastTime = t
871      print "** whole startup: %1.0f ms **" % totalTime
872    else:
873      print "* timing list empty *"
874
875
876
877  def pressed(self, w, event):
878    self.lastPressEpoch=event.time
879    self.pressInProgress = True
880
881    self.dragstartx = event.x
882    self.dragstarty = event.y
883    #print dir(event)
884    #print "Pressed button %d at %1.0f, %1.0f" % (event.button, event.x, event.y)
885   
886    self.dragx = event.x
887    self.dragy = event.y
888    self.mapWidget.mousedown(event.x,event.y)
889
890    if not self.pressLengthTimer:
891      self.pressLengthTimer = gobject.timeout_add(50, self.checkStillPressed, event.time, time.time(), event.x,event.y)
892   
893  def moved(self, w, event):
894    """Drag-handler"""
895
896    self.mapWidget.handleDrag( \
897      event.x,
898      event.y,
899      event.x - self.dragx, 
900      event.y - self.dragy,
901      self.dragstartx,
902      self.dragstarty,
903      event.time - self.lastPressEpoch)
904
905    self.dragx = event.x
906    self.dragy = event.y
907
908  def released(self, w, event):
909    self.pressInProgress = False
910    self.pressLengthTimer = None
911    msDuration = event.time - self.lastPressEpoch
912
913    self.mapWidget.released(event)
914    dx = event.x - self.dragstartx
915    dy = event.y - self.dragstarty
916    distSq = dx * dx + dy * dy
917    # Adjust this to the length^2 of a gerfingerpoken on your touchscreen (1024 is for Freerunner, since it's very high resolution)
918    if distSq < 1024:
919      self.mapWidget.click(event.x, event.y,msDuration)
920
921  def checkStillPressed(self, pressStartEpoch, pressStartTime, startX, startY):
922    """check if a press is still in progress and report:
923    pressStart epoch - to differentiate presses
924    duration
925    start coordinates
926    currentCoordinates
927    if no press is in progress or another press already started, shut down the timer
928    """
929
930    """just to be sure, time out after 60 seconds
931    - provided the released signal is always called, this timeout might not be necessary,
932    but better be safe, than eat the whole battery if the timer is not terminated"""
933    dt = (time.time() - pressStartTime)*1000
934    if dt > 60000:
935      print "long press timeout reached"
936      return False
937
938    if pressStartEpoch == self.lastPressEpoch and self.pressInProgress:
939      self.mapWidget.handleLongPress(pressStartEpoch, dt, startX, startY, self.dragx, self.dragy)     
940      return True
941    else: # the press ended or a new press is in progress -> stop the timer
942      return False
943
944if __name__ == "__main__":
945  """
946  suported devices (for now):
947  'neo' (480*640)
948  'n95' (480*640)
949  'eee' (800*600)
950  'netbook' (800*600)
951  'q7'  (800*480)
952  'n900' (800*480) -> for now can also be used for the Q7 or Q5 (same resolution)
953  'square' (480*480) -> this is for testing screens with equal sides
954  'ipaq' (240*320) -> old Ipaqs (and and other Pocket PCs) had this resolution
955  """
956
957  print " == modRana Starting == "
958
959  try:
960    import sys
961    device = sys.argv[1].lower()
962    print " device string (first parameter): %s " % sys.argv[1]
963  except:
964    device = 'neo'
965    print " no device string in first parameter, using: %s" % device
966
967  program = GuiBase(device)
968
969
Note: See TracBrowser for help on using the repository browser.