Module fullscreenwrapper2
[hide private]
[frames] | no frames]

Source Code for Module fullscreenwrapper2

  1  ''' 
  2  Created on Jul 19, 2012 
  3   
  4  @author: Hariharan Srinath 
  5  ''' 
  6  import abc 
  7  #import android 
  8  import cPickle 
  9  import json 
 10  import sys 
 11  import time 
 12  import os 
 13  import hashlib 
14 15 -class BaseDict(dict):
16 ''' 17 implements a dictionary that can be accessed by BaseDict[key] as well as by BaseDict.key to allow more pythonic access 18 credits: BaseDict pattern at http://code.activestate.com/recipes/473790/ under PSF license 19 ''' 20
21 - def __init__(self, data=None):
22 if data: 23 dict.__init__(self, data) 24 else: 25 dict.__init__(self)
26
27 - def __setattr__(self, name, val):
28 if name in self.__dict__: 29 self.__dict__[name]= val 30 else: 31 self[name] = val
32
33 - def __getattr__(self, name):
34 if name in self.__dict__: 35 return self.__dict__[name] 36 else: 37 return self[name]
38
39 - def setDict(self, name, val):
40 ''' 41 setDict(name, val): Assign *val* to the key *name* of __dict__. 42 >>> bd.setDict('height', 160) 43 {} 44 >>> bd.getDict()['height'] 45 160 46 ''' 47 self.__dict__[name] = val 48 return self
49
50 - def getDict(self):
51 ''' 52 Return the internal __dict__. 53 >>> bd.setDict('height', 160) 54 {} 55 >>> bd.getDict()['height'] 56 160 57 ''' 58 return self.__dict__
59
60 - def setItem(self, name, val):
61 ''' 62 Set the value of dict key *name* to *val*. Note this dict 63 is not the __dict__. 64 ''' 65 self[name] = val 66 return self
67
68 - def __getstate__(self):
69 ''' Needed for cPickle in .copy() ''' 70 return self.__dict__.copy()
71
72 - def __setstate__(self,dict):
73 ''' Needed for cPickle in .copy() ''' 74 self.__dict__.update(dict)
75
76 - def copy(self):
77 ''' 78 Return a copy. 79 ''' 80 return cPickle.loads(cPickle.dumps(self))
81
82 83 -class EventHandler(object):
84 ''' 85 Defines an SL4A event handler and provides a matching function to compare vs. Android.eventPoll().result 86 87 SL4A eventdata returned by Android.eventWait() or Android.eventPoll().result in general take the form of a dict: 88 {"data":{"attribute1":value,"attribute2":value}, "name":"event_name", "time":eventtime} 89 90 The EventHandler object consists of an event_name, a compare_attribute to look for within the "data" dict & a 91 compare_value which the compare_attribute will get matched against. It also has optionally an event_handler_fn 92 which stores a reference to the method to be called and the reference to the view referred to by the event. 93 94 fullscreenwrapper2 module pre-defines click_EventHandler, itemclick_EventHandler and key_EventHandler which are 95 commonly used with Layout views for your convenience 96 97 When the FullScreenWrapper2App class which handles events finds a match, it will call the function defined in the 98 EventHandler passing the view & a copy of the eventdata. The event handler method signature should therefore be: 99 def event_handler_function(self, view, eventdata): 100 ''' 101
102 - def __init__(self,event_name, compare_attribute,compare_value,view = None, handler_function=None):
103 ''' 104 creates an SL4A event handler 105 106 SL4A eventdata returned by Android.eventWait() or Android.eventPoll().result in general take the form of a dict: 107 {"data":{"attribute1":value,"attribute2":value}, "name":"event_name", "time":eventtime} 108 109 The EventHandler object consists of an event_name, a compare_attribute to look for within the "data" dict & a 110 compare_value which the compare_attribute will get matched against. It also has optionally an event_handler_fn 111 which stores a reference to the method to be called and the reference to the view referred to by the event. 112 113 The compare_attribute can be None. if this is the case, then the event_name alone is matched. You can use this feature 114 to catch other SL4A API events like sensor events 115 ''' 116 self.view = view 117 self.event_name = event_name 118 self.compare_attribute = compare_attribute 119 self.compare_value = compare_value 120 self.event_handler_fn = handler_function
121
122 - def match_event_data(self, event_data):
123 ''' 124 Provides a matching function to compare event handler vs. data returned by Android.eventPoll().result or Android.eventWait() 125 126 SL4A eventdata returned by Android.eventWait() or Android.eventPoll().result in general take the form of a dict: 127 {"data":{"attribute1":value,"attribute2":value}, "name":"event_name", "time":eventtime} 128 129 The function first matches event_data[event_name] and then tries to match event_data["data"][compare_attribute] to compare_value 130 returns True on match, False on no-match or event not found 131 132 The compare_attribute can be None. if this is the case, then the event_name alone is matched. You can use this feature 133 to catch other SL4A API events like sensor events 134 ''' 135 try: 136 if event_data["name"]==self.event_name: 137 if self.compare_attribute != None: 138 if event_data["data"][self.compare_attribute]==self.compare_value: 139 return True 140 else: 141 return True 142 except: 143 return False 144 else: 145 return False
146
147 - def __str__(self):
148 ''' 149 convenience function for debugging 150 ''' 151 return str(self.event_name)+":"+str(self.compare_attribute)+"="+str(self.compare_value)
152
153 -class click_EventHandler(EventHandler):
154 ''' 155 predefined click event handler for use with Views 156 157 This is the event handler to typically associate with TextView, Button, ImageView etc. You only need to pass the view to 158 link the click event to & the handler function & rest of event handler initialization is handled automatically 159 ''' 160 EVENT_NAME = "click" 161 COMPARE_ATTRIBUTE = "id" 162
163 - def __init__(self,view, handler_function=None):
164 ''' 165 predefined click event handler for use with Views 166 167 This is the event handler to typically associate with TextView, Button, ImageView etc. You only need to pass the view to 168 link the click event to & the handler function & rest of event handler initialization is handled automatically 169 ''' 170 super(click_EventHandler,self).__init__(self.EVENT_NAME,self.COMPARE_ATTRIBUTE,view.view_id,view,handler_function)
171
172 -class itemclick_EventHandler(EventHandler):
173 ''' 174 predefined itemclick event handler for use with Views 175 176 This is the event handler to typically associate with ListView. You only need to pass the ListView to link the itemclick event 177 to & the handler function & rest of event handler initialization is handled automatically 178 ''' 179 EVENT_NAME = "itemclick" 180 COMPARE_ATTRIBUTE = "id" 181
182 - def __init__(self,view,handler_function=None):
183 super(itemclick_EventHandler,self).__init__(self.EVENT_NAME,self.COMPARE_ATTRIBUTE,view.view_id,view,handler_function)
184
185 -class key_EventHandler(EventHandler):
186 ''' 187 predefined key event handler for use with Layout. defaults to Back Key with key_id = "4" 188 189 This is the event handler to typically associate with a layout. You need to pass key_id to associate with (defaults to 190 back key = "4") & the handler function & rest of event handler initialization is handled automatically 191 ''' 192 EVENT_NAME = "key" 193 COMPARE_ATTRIBUTE = "key" 194
195 - def __init__(self,key_match_id="4",view=None,handler_function=None):
196 super(key_EventHandler,self).__init__(self.EVENT_NAME,self.COMPARE_ATTRIBUTE,key_match_id,view,handler_function)
197
198 -class _internal_exit_signal():
199 ''' 200 Internal to fullscreenwrapper2 - do not use in your programs. Used by FullScreenWrapper2App to signal the eventloop to stop. 201 ''' 202 EVENT_NAME = "fullscreenwrapper2_internal_exit_signal" 203 COMPARE_ATTRIBUTE = "id" 204 ''' 205 the event handler in exit signal checks to ensure it is receiving exit signal from the same Process ID 206 ''' 207 eventhandler = EventHandler(EVENT_NAME,COMPARE_ATTRIBUTE,str(os.getpid()),None,None) 208 209 ''' 210 used to post exit signal to the event loop 211 ''' 212 @classmethod
214 data = {cls.COMPARE_ATTRIBUTE:str(os.getpid())} 215 FullScreenWrapper2App.get_android_instance().eventPost(cls.EVENT_NAME, json.dumps(data),True)
216
217 218 -class View(object):
219 ''' 220 Defines a View and provides pythonic access to its properties & a mechanism to define events. 221 222 You don't create views yourself. They are created by FullScreenWrapper2App.show_layout() after showing the xml 223 and are populated in the Layout.views which is a BaseDict => ie. a dict that allows access by both [key] and .key 224 225 You can access a view's properties simply by Layout.views.viewname.property to get & set property. Doing this 226 calls the appropriate SL4A api function like fullSetProperty() 227 228 To add and remove events, use the View.add_event() and View.remove_event() methods. To set the contents of 229 a ListView, use the View.set_listitems() method 230 '''
231 - def __init__(self,view_id, view_type):
232 ''' 233 View constructer called with view_id & view_type. DO NOT create a view yourself. 234 235 Views are created by FullScreenWrapper2App.show_layout() after showing the xml and are populated 236 in the Layout.views which is a BaseDict => ie. a dict that allows access by both [key] and .key 237 ''' 238 self.view_type = view_type 239 self.view_id = view_id 240 self._events = {}
241
242 - def add_event(self, eventhandler):
243 ''' 244 Used to add an EventHandler to the view. 245 246 You would typically add one of click_EventHandler or itemclick_EventHandler (for List Views) 247 to a view 248 ''' 249 self._events[eventhandler.event_name]=eventhandler
250
251 - def remove_event(self,event_name):
252 ''' 253 removes an event added previously by matching the event_name. Use this to temporarily disable a view's click event 254 ''' 255 self._events.pop(event_name)
256
257 - def set_listitems(self,listitems):
258 ''' 259 sets a list for a ListView. Takes a list of str as input 260 ''' 261 FullScreenWrapper2App.set_list_contents(self.view_id, listitems)
262
263 - def __setattr__(self, name, value):
264 ''' 265 This allows pythonic access to setting a View's properties by calling SL4A api 266 267 For eg: Layout.views.viewname.color = "#FFFFFFFF" 268 ''' 269 if name in ("view_type","view_id","_events"): 270 object.__setattr__(self,name,value) 271 else: 272 #sys.stderr.write("calling sl4a to set name:"+str(name)+" value:"+value+"\n") 273 return FullScreenWrapper2App.set_property_value(self.id, name, value)
274
275 - def __getattr__(self, name):
276 ''' 277 This allows pythonic access to getting a View's properties by calling SL4A api 278 279 For eg: buttontext = Layout.views.buttonname.text 280 ''' 281 #sys.stderr.write("calling sl4a to get name:"+str(name)+"\n") 282 return FullScreenWrapper2App.get_property_value(self.view_id, name)
283
284 - def __str__(self):
285 ''' 286 str(View) will return the View.text 287 ''' 288 try: 289 return self.text 290 except AttributeError: 291 return None
292
293 -class Layout(object):
294 ''' 295 Defines a "screen" with an xml layout that contains views. Layout is a abstract class - you MUST derive your own Layout class. 296 297 To use a Layout, you need to first derive a class with Layout as a base class: MyLayout(Layout) and define the functions on_show(self) 298 and on_close(self). in your MyLayout.__init__(), you MUST include a call to super(MyLayout,self).__init__(xml, title) 299 300 the xml property stores the xml text. This is used by FullScreenWrapper2App.show_layout() to actually display the layout. 301 302 The layout contains importantly a BaseDict called views. A BaseDict is a dict that allows access to members by either [key] or .key 303 304 IMPORTANT: The views BaseDict is populated by FullScreenWrapper2App.show_layout() once the xml is displayed on the screen. Your layout's Views that have an id 305 only become accessible once the xml is displayed & the Layout.on_show() function is caled by the framework. DO NOT try to access views in the __init__() and 306 put all your view initialization code & event handler attachment in the on_show() function. 307 308 The views BaseDict allows you to access & modify properties of your views & allows event based interaction. You would typically access view 309 properties as Layout.views.view_id.property with the FullScreenWrapper2 framework making the appropriate SL4A api calls 310 to access the property. To set events for the views, use Layout.views.view_id.add_event(EventHandler) 311 312 The FullScreenWrapper2App actually stores layout objects in a stack allowing you to seamlessly the right parent layout on closing a child layout. This 313 lets you build a natural interaction using the "back" key. Note however that every time a layout is shown, its views are created afresh & the Layout.views 314 BaseDict is cleared & re-populated and the Layout.on_show() function is called. This is why you should put all your view initialization & event handler setup 315 code in Layout.on_show() 316 317 Layout.on_close method MUST also be defined - though it can simply be a 1 line function containing pass. This is called when a layout is either closed 318 or a child layout is opened. This method to save state. 319 320 Layouts also allow you to set "Layout" events through Layout.add_event() - you would typically use this for things like "back key press" or even for 321 other events which are accessible through the SL4A EventFacade's event system like sensor data. For catching these events, you would typically set 322 EventHandler.compare_attribute to None. 323 324 Layout events are internally handled by adding a special "layout" view to the views collection identified by a hashtag. You should not yourself 325 access this special view. 326 ''' 327 __metaclass__ = abc.ABCMeta 328
329 - def __init__(self,xml,title):
330 ''' 331 creates a layout and stes its xml and title, initializes the views collection 332 333 NOTE that this DOES NOT display the layout and the layout Views are also not populated. The special "layout" view for handling Layout 334 evnets however is created here 335 ''' 336 self.uid = hashlib.md5(str(title)+str(os.getpid())+str(time.time())).hexdigest() 337 self.title = title 338 self.xml = xml 339 self.views = BaseDict() 340 self._reset()
341
342 - def _reset(self):
343 ''' 344 This function will clear the views collection & add the special "Layout" view 345 which is used to handle layout events internally 346 ''' 347 self.views.clear() 348 #adds a dummy view representing the layout for event management 349 self.views[self.uid]= View(self.uid,"Layout")
350
351 - def add_event(self, eventhandler):
352 ''' 353 This function adds a Layout event. This event is added to the special "layout" view in the views collection 354 ''' 355 self.views[self.uid].add_event(eventhandler)
356
357 - def remove_event(self,event_name):
358 ''' 359 This function removes a Layout event by event name. This event is actually stored in the special "layout" view in the views collection 360 ''' 361 self.views[self.uid].remove_event(event_name)
362 363 @abc.abstractmethod
364 - def on_show(self):
365 ''' 366 The on_show method is called after your layout is displayed to allow you to initialize your layout's views' attributes & setup event handlers. 367 368 on_show is an abstract method which MUST be defined in the your layout class. FullScreenWrapper2App.show_layout() displays the layout & populates the views BaseDict collection 369 and then calls Layout.on_show() letting you do your view initializations & setup event handlers. This function is called every time a view is displayed - for eg. after a child 370 layout is closed & the parent layout shown again on screen 371 372 If you have saved state in Layout.on_close() be sure to read back state & populate data in your layout's views in on_show() 373 ''' 374 pass
375 376 @abc.abstractmethod
377 - def on_close(self):
378 ''' 379 The on_close method MUST be defined & is called both when your layout is closed or before displaying a child layout to let you save state. 380 381 If you're saving state here, you can read back state on on_show() method 382 ''' 383 pass
384
385 386 -class FullScreenWrapper2App(object):
387 ''' 388 FullScreenWrapper2App implements the "App" incorporating an eventloop & a layout stack with methods to display & close layouts and access SL4A FullScreenUI API functions 389 390 You SHOULD NOT instantiate a FullScreenWrapper2App but rather, simply call its class methods. To use the app, you first need to call FullScreenWrapper2App.initialize(android_instance) with the droid = android.Android() 391 object that you have created in you program. This is always subsequently accessible by FullScreenWrapper2App.get_android_instance() 392 393 You can then call FullScreenWrapper2App.show_layout() and FullScreenWrapper2App.close_layout() to show and close layouts respectively. FullScreenWrapper2App places layouts in an internal stack. This lets the framework seamlessly handle 394 parent->show child->close child->show parent type of transitions simplifying your code. It also gives you a method to exit the app by calling FullScreenWrapper2App.exit_FullScreenWrapper2App() at any time. This internally works by signalling 395 the eventloop to terminate the loop by posting an internal event 396 397 the internal function called by FullScreenWrapper2App.show_layout() and close_layout() actually populates the layout's views once it is shown & also calls the Layout.on_show() function 398 399 Once you have called show_layout() and your first layout is displayed on screen with its view properties & event handlers set, you should call FullScreenWrapper2App.eventloop() to start the event loop. The event loop will keep polling for 400 the event queue and dispatch events to the appropriate handler functions 401 402 The FullScreenWrapper also defines a few "convenience" functions which are used to set and access fullscreen properties via SL4A api calls 403 ''' 404 _android_instance = None 405 _layouts = [] 406 407 SHOW_LAYOUT_PUSH_OVER_CURRENT = 0 408 SHOW_LAYOUT_REPLACING_CURRENT = 1 409 _SHOW_LAYOUT_POP_CURRENT = 2 410 411 412 @classmethod
413 - def initialize(cls, android_instance):
414 ''' 415 You MUST call this first with your droid = android.Android() instance before calling any other function 416 ''' 417 cls._android_instance = android_instance
418 419 @classmethod
420 - def get_android_instance(cls):
421 ''' 422 this allows you to access the android.Android() instance set in FullScreenWrapper2App.initialize() at any time 423 ''' 424 if cls._android_instance!=None: 425 return cls._android_instance 426 else: 427 raise RuntimeError("You need to call FullScreenWrapper2App.initialize(android_instance) first")
428 429 @classmethod
430 - def show_layout(cls, layout, show_mode = SHOW_LAYOUT_PUSH_OVER_CURRENT):
431 ''' 432 This will show the layout, set the title, clean & re-populate layout's views BaseDict collection & call the Layout.on_show() 433 434 this will also push the layout to the top FullScreenWrapper2App._layouts[] stack. If there is already a parent layout showing, then 435 this will call the parent layout's on_close() function to let the parent layout save state 436 ''' 437 438 if show_mode == cls.SHOW_LAYOUT_PUSH_OVER_CURRENT or show_mode == cls.SHOW_LAYOUT_REPLACING_CURRENT or show_mode == cls._SHOW_LAYOUT_POP_CURRENT: 439 curlayoutidx = len(cls._layouts)-1 440 441 if(curlayoutidx > -1): 442 cls._layouts[curlayoutidx].on_close() 443 444 cls.get_android_instance().fullShow(layout.xml) 445 cls.get_android_instance().fullSetTitle(layout.title) 446 447 viewsdict = cls.get_android_instance().fullQuery().result 448 layout._reset() 449 450 for viewname in iter(viewsdict): 451 layout.views[viewname] = View(viewname, viewsdict[viewname]["type"]) 452 453 if show_mode == cls.SHOW_LAYOUT_PUSH_OVER_CURRENT: 454 cls._layouts.append(layout) 455 elif show_mode == cls.SHOW_LAYOUT_REPLACING_CURRENT: 456 if(curlayoutidx > -1): 457 cls._layouts.pop() 458 cls._layouts.append(layout) 459 elif show_mode == cls._SHOW_LAYOUT_POP_CURRENT: 460 if(curlayoutidx > -1): 461 cls._layouts.pop() 462 463 layout.on_show()
464 465 @classmethod
466 - def close_layout(cls):
467 ''' 468 This will first call a layout's on_close() function to help save state & then close the active layout. 469 470 If the layout being closed is a child layout, then this will pop the child layout from the FullScreenWrapper2App._layouts[] stack 471 and show the parent immediately below the child layout in the stack. 472 ''' 473 curlayoutidx = len(cls._layouts)-1 474 475 if curlayoutidx >0: 476 cls.show_layout(cls._layouts[curlayoutidx-1], cls._SHOW_LAYOUT_POP_CURRENT) 477 elif curlayoutidx == 0: 478 cls.get_android_instance().fullDismiss() 479 cls.exit_FullScreenWrapper2App()
480 481 @classmethod
483 ''' 484 convenience function to exit the app. this works by signalling the eventloop to stop 485 ''' 486 cls.get_android_instance().fullDismiss() 487 #curlayout = cls._layouts[len(cls._layouts)-1] 488 #curlayout._reset() 489 _internal_exit_signal.post_internal_exit_signal()
490 491 @classmethod
492 - def eventloop(cls):
493 ''' 494 The main event loop to catch & dispatch events in the active/topmost layout in the _layouts[] stack & its views. 495 496 Call this once your first layout's event handlers are setup from your main() program. This catches & dispatches events 497 by matching EventHandlers in the ACTIVE/TOPMOST layout int he _layouts[] stack & its views. 498 499 Note that only the active layout & its views are matched with events. This function also looks for "exit" signal 500 which can be raised by calling exit_FullScreenWrapper2App() to terminate the event loop. 501 ''' 502 if len(cls._layouts)<1: 503 raise RuntimeError("Trying to start eventloop without a layout visible") 504 505 while(True): 506 evt=cls.get_android_instance().eventPoll() 507 if(len(evt.result)>0): 508 eventdata=evt.result[0] 509 510 #this corrects an eventpost issue where an extra "" wraps the json 511 try: 512 if type(eventdata["data"]) != type({}): 513 eventdata["data"]=json.loads(eventdata["data"]) 514 except: 515 pass 516 517 #sys.stderr.write("in event loop-got an event\n") 518 #sys.stderr.write(str(eventdata)+"\n") 519 520 if _internal_exit_signal.eventhandler.match_event_data(eventdata): 521 break 522 523 curlayout = cls._layouts[len(cls._layouts)-1] 524 525 for viewname in iter(curlayout.views): 526 view = curlayout.views[viewname] 527 #sys.stderr.write("Checking with"+ view.view_id+"\n") 528 for eventname in iter(view._events): 529 event = view._events[eventname] 530 if event.match_event_data(eventdata): 531 if event.event_handler_fn != None: 532 event.event_handler_fn(event.view, eventdata) 533 event_handled = True 534 #sys.stderr.write("found a match in view "+str(event.view.view_id)) 535 break
536 537 538 539 #Functions to manipulate contents of the FullScreen - these provide a thin cover over droid.fullset/query 540 @classmethod
541 - def set_list_contents(cls,id, list):
542 return cls.get_android_instance().fullSetList(id, list)
543 544 @classmethod
545 - def set_property_value(cls,id, property, value):
546 '''Set the value of an XML view's property''' 547 return cls.get_android_instance().fullSetProperty(id, property,value)
548 549 @classmethod
550 - def get_property_value(cls,id, property):
551 '''Get the value of a given XML view's property''' 552 ret = cls.get_android_instance().fullQueryDetail(id).result 553 554 try: 555 return ret[property] 556 except: 557 #sys.stderr.write("The property "+property+" for the view "+id+" was not found\n") 558 return None
559