a07091b3fb06126b37812e7770ab2b4f746041f9
[matthijs/upstream/mobilegtd.git] / src / gui / gui.py
1 from config.config import *
2 from model.projects import Projects
3
4 import appuifw
5 import thread
6 from log.logging import logger
7 from e32 import Ao_lock, in_emulator
8 from key_codes import *
9 import key_codes
10
11
12 def show_config(cfg):        
13     fields = []
14     for k, v in cfg.items():
15         v = cfg.format_value(v)
16         if isinstance(v, int) or isinstance(v, long):
17             tname = 'number'
18             v = int(v)
19         elif isinstance(v, list) or isinstance(v, tuple):
20             for item in v[0]:
21                 if not isinstance(item, unicode):
22                     raise Exception("list can contain only unicode objects, "\
23                                     "object %r is not supported" % item)
24             
25             tname = 'combo'
26         elif isinstance(v, unicode):
27             tname = 'text'
28         else:
29             raise Exception("%s has non-supported value" % k)
30
31         fields.append((unicode(k), tname, v))
32
33
34     form = appuifw.Form(fields=fields, flags=appuifw.FFormEditModeOnly | \
35                         appuifw.FFormDoubleSpaced)
36
37     saved = [False]
38     def save_hook(param):
39         saved[0] = True
40     form.save_hook = save_hook
41     
42     form.execute()
43
44     # return true if user saved, false otherwise
45     if not saved[0]:
46         return False
47     
48     for label, tname, value in form:
49         if tname == 'combo':
50             value = (value[0], int(value[1]))
51
52         cfg[str(label)] = cfg.parse_value(value)
53
54     return True
55
56
57 def no_action():
58     pass
59
60 def applicable_functions(obj,allowed_function_names):
61     function_names = [function_name for function_name in dir(obj) if function_name in allowed_function_names]
62     return [eval('obj.%s'%function_name) for function_name in function_names]
63
64 def get_key(key_name):
65     if not key_name:
66         key = None
67     else:
68         key=eval('EKey%s'%key_name)
69     return key
70
71 def key_shortname(key_name):
72     """ Find the one-character name for a key """
73     if not key_name:
74         return None
75     elif key_name == 'Backspace':
76         return 'C'
77     else:
78         return key_name
79
80 def all_key_names():
81     return filter(lambda entry:entry[0:4]=='EKey',dir(key_codes))
82 def all_key_values():
83     key_values=[
84                 EKey0,
85                 EKey1,
86                 EKey2,
87                 EKey3,
88                 EKey4,
89                 EKey5,
90                 EKey6,
91                 EKey7,
92                 EKey8,
93                 EKey9,
94                 EKeyStar,
95                 EKeyHash,
96                 ]
97     return key_values
98
99
100 def save_gui(object):
101     object.old_gui = appuifw.app.body
102     object.old_menu = appuifw.app.menu
103     object.old_exit_key_handler = appuifw.app.exit_key_handler
104     object.old_title=appuifw.app.title
105
106 def restore_gui(object):
107     appuifw.app.body = object.old_gui
108     appuifw.app.menu = object.old_menu
109     appuifw.app.exit_key_handler = object.old_exit_key_handler
110     appuifw.app.title = object.old_title
111
112 class View(object):
113     def __init__(self):
114         self.title = None
115         self.view = None
116         self.lock = Ao_lock()
117         self.exit_flag = False
118         super(View, self).__init__()
119
120     def set_title(self, title):
121         self.title = title
122    
123     def set_view(self, view):
124         """
125         Sets the main view to be displayed (e.g., an appuifw.Listbox
126         instance).
127         """
128         self.view = view
129
130     def run(self):
131         self.adjustment = None
132         save_gui(self)
133         appuifw.app.screen=COMMON_CONFIG['screen'].encode('utf-8')
134         appuifw.app.title=self.title
135         appuifw.app.body=self.view
136         appuifw.app.exit_key_handler=self.exit
137         try:
138             self.lock.wait()
139             while not self.exit_flag:
140                 self.refresh()
141                 self.lock.wait()
142         except:
143             pass
144         restore_gui(self)
145
146     def exit(self):
147         self.exit_flag = True
148         self.lock.signal()
149
150     def update(self,subject=None):
151         """
152         Update the current view (e.g., make sure refresh is called). We
153         can't call it directly, since we're in another thread.
154         """
155         if self.lock:
156             self.lock.signal()
157
158     def refresh(self):
159         """
160         Called when the current view must be updated. Never call
161         directly. Subclasses should extend this method, not update.
162         """
163         appuifw.app.menu=self.get_menu_entries()
164
165     def get_menu_entries(self):
166         """ Returns a list of menu entries to display. Will be
167         automatically updated on each refresh.
168
169         Each menu entry is a tuple of a title for the entry and a
170         function to call when the entry is selected.
171         """
172         return []
173
174 class ListView(View):
175     def __init__(self):
176         super(ListView, self).__init__()
177         self.set_view(appuifw.Listbox(self.items(),self.entry_selected))
178         self.view.bind(EKeyUpArrow,lambda: self.arrow_key_pressed(-1))
179         self.view.bind(EKeyDownArrow,lambda: self.arrow_key_pressed(1))
180
181     def arrow_key_pressed(self, dir):
182         """
183         This function is called when an arrow key is pressed. Since we
184         don't get any "current list index has changed" events, we'll
185         have to create these ourselves this way.
186
187         Since the current index is only updated after the key event,
188         we'll have to adjust the index with the direction of the
189         keypress (-1 for up, +1 for down).
190         """
191         new_index = (self.selected_index() + dir) % len(self.items())
192         self.index_changed(new_index)
193
194     def entry_selected(self):
195         """
196         This function is called when the user selects an an entry (e.g.,
197         navigates to it and push the ok button).
198         """
199         pass
200
201     def index_changed(self,new_index):
202         """
203         This function is called when the index changes. The given index
204         is the new index (don't use self.selected_index() here, since it
205         won't be correct yet!).
206         """
207         pass
208
209     def items(self):
210         """ This function should return the list of items to display.
211         See appuifw.ListBox for valid elements for this list. """
212         return []
213
214     def set_index(self,index):
215         """ Changes the currently selected item to index. """
216         self.view.set_list(self.items(),index % len(self.items()))
217
218     def selected_index(self):
219         """ Returns the currently selected index. """
220         return self.view.current()
221
222 class WidgetBasedListView(ListView):
223     def __init__(self):
224         self.widgets = self.generate_widgets()
225         super(WidgetBasedListView,self).__init__()
226
227     def run(self):
228         self.refresh()
229         self.set_bindings_for_selection(0)
230         ListView.run(self)
231
232     def index_changed(self,new_index):
233         self.set_bindings_for_selection(new_index)
234         super(WidgeteBasedListView, self).index_changed(new_index)
235
236     def notify(self,object,attribute,new=None,old=None):
237         self.refresh()
238     def refresh(self):
239         self.widgets = self.generate_widgets()
240         self.redisplay_widgets()
241         super(WidgetBasedListView,self).refresh()
242     def redisplay_widgets(self):
243         self.set_index(self.selected_index())
244     def items(self):
245         return self.all_widget_texts()
246     def all_widget_texts(self):
247         return [entry.list_repr() for entry in self.widgets]
248
249     
250
251     def current_widget(self):
252         return self.widgets[self.selected_index()]
253         
254
255 class KeyBindingView(View):
256     
257     def __init__(self):
258         self.binding_map = {}
259         super(KeyBindingView,self).__init__()
260
261     def set_keybindings(self, binding_map):
262         """
263         Set a new map of key bindings. This map maps method names to a
264         tuple of keyname and description.
265
266         The method name refers to a method on the selected item, or the
267         current view.
268
269         Example: { 'search_item' : ('0', 'Search item') }
270
271         """
272         self.binding_map = binding_map
273
274     def get_menu_entries(self):
275         menu_entries=[]
276         for key,key_name,description,function in self.key_and_menu_bindings(self.selected_index()):
277             if description != '':
278                 if key:
279                     if key_name == 'Backspace': key_name='C'
280                     description='[%s] '%key_name +description
281                 else:
282                     description='    '+description
283                 menu_entries.append((description,function)) 
284         menu_entries.append((u'Exit', self.exit))
285         return menu_entries + super(KeyBindingView, self).get_menu_entries()
286
287     def set_bindings_for_selection(self,selected_index):
288         self.remove_all_key_bindings()
289         
290         for key,key_name,description,function in self.key_and_menu_bindings(selected_index):
291             if key:
292                 self.view.bind(key,function)
293         
294     def remove_all_key_bindings(self):
295         for key in all_key_values():
296             self.view.bind(key,no_action)
297
298 class SearchableListView(WidgetBasedListView):
299     def __init__(self):
300         self.current_entry_filter_index = -1
301         self.entry_filters = []
302         self.filtered_list = lambda:[]
303         self.lock = None
304         super(SearchableListView,self).__init__()
305
306     def set_filters(self, entry_filters):
307         """
308         Set the filters that could be applied to this list. Each filter
309         can be applied in turn by calling switch_entry_filter (for
310         example from a key binding).
311
312         The entry_filters argument should be a list of filters. The
313         active filter is stored into self.filtered_list and should be
314         processed by generate_widgets in the subclass.
315         """
316         self.current_entry_filter_index = 0
317         self.entry_filters = entry_filters
318         self.filtered_list = self.entry_filters[0]
319
320     def search_item(self):
321         selected_item = appuifw.selection_list(self.all_widget_texts(),search_field=1)
322         if selected_item == None or selected_item == -1:
323             selected_item = self.selected_index()
324         self.view.set_list(self.items(),selected_item)
325         self.set_bindings_for_selection(selected_item)
326
327     def switch_entry_filter(self):
328         self.current_entry_filter_index += 1
329         self.filtered_list = self.entry_filters[self.current_entry_filter_index % len(self.entry_filters)]
330         self.refresh()
331
332
333 class EditableListView(SearchableListView,KeyBindingView):
334     def __init__(self):
335         super(EditableListView, self).__init__()
336
337     def key_and_menu_bindings(self,selected_index):
338         key_and_menu_bindings=[]
339         for function in applicable_functions(self.widgets[selected_index],self.binding_map)+\
340             applicable_functions(self,self.binding_map):
341             execute_and_update_function = self.execute_and_update(function)
342             (key,description) = self.binding_map[function.__name__]
343             key_and_menu_bindings.append((get_key(key),key,description,execute_and_update_function))
344         return key_and_menu_bindings
345
346     def entry_selected(self):
347         self.current_widget().change()
348         self.refresh()
349     def execute_and_update(self,function):
350         return lambda: (function(),self.refresh(),self.index_changed())
351
352     def notify(self,item,attribute,new=None,old=None):
353         self.refresh()
354
355 #class DisplayableFunction:
356 #    def __init__(self,display_name,function):
357 #        self.display_name = display_name
358 #        self.function = function
359 #    def list_repr(self):
360 #        return self.display_name
361 #    def execute(self):
362 #        function()
363
364 # Public API
365 __all__= ('EditableListView','show_config')