|
Module fullscreenwrapper2
|
|
1 '''
2 Created on Jul 19, 2012
3
4 @author: Hariharan Srinath
5 '''
6 import abc
7
8 import cPickle
9 import json
10 import sys
11 import time
12 import os
13 import hashlib
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
26
28 if name in self.__dict__:
29 self.__dict__[name]= val
30 else:
31 self[name] = val
32
34 if name in self.__dict__:
35 return self.__dict__[name]
36 else:
37 return self[name]
38
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
51 '''
52 Return the internal __dict__.
53 >>> bd.setDict('height', 160)
54 {}
55 >>> bd.getDict()['height']
56 160
57 '''
58 return self.__dict__
59
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
69 ''' Needed for cPickle in .copy() '''
70 return self.__dict__.copy()
71
73 ''' Needed for cPickle in .copy() '''
74 self.__dict__.update(dict)
75
77 '''
78 Return a copy.
79 '''
80 return cPickle.loads(cPickle.dumps(self))
81
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
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
148 '''
149 convenience function for debugging
150 '''
151 return str(self.event_name)+":"+str(self.compare_attribute)+"="+str(self.compare_value)
152
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
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):
184
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):
197
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 '''
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
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
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
262
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
273 return FullScreenWrapper2App.set_property_value(self.id, name, value)
274
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
282 return FullScreenWrapper2App.get_property_value(self.view_id, name)
283
285 '''
286 str(View) will return the View.text
287 '''
288 try:
289 return self.text
290 except AttributeError:
291 return None
292
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
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
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
349 self.views[self.uid]= View(self.uid,"Layout")
350
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
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
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
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
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
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
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
464
465 @classmethod
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
490
491 @classmethod
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
511 try:
512 if type(eventdata["data"]) != type({}):
513 eventdata["data"]=json.loads(eventdata["data"])
514 except:
515 pass
516
517
518
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
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
535 break
536
537
538
539
540 @classmethod
541 - def set_list_contents(cls,id, list):
542 return cls.get_android_instance().fullSetList(id, list)
543
544 @classmethod
546 '''Set the value of an XML view's property'''
547 return cls.get_android_instance().fullSetProperty(id, property,value)
548
549 @classmethod
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
558 return None
559