Move the update method to View.
[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 key_name == '':
66         key = None
67     else:
68         key=eval('EKey%s'%key_name)
69     return key
70
71 def all_key_names():
72     return filter(lambda entry:entry[0:4]=='EKey',dir(key_codes))
73 def all_key_values():
74     key_values=[
75                 EKey0,
76                 EKey1,
77                 EKey2,
78                 EKey3,
79                 EKey4,
80                 EKey5,
81                 EKey6,
82                 EKey7,
83                 EKey8,
84                 EKey9,
85                 EKeyStar,
86                 EKeyHash,
87                 ]
88     return key_values
89
90
91 def save_gui(object):
92     object.old_gui = appuifw.app.body
93     object.old_menu = appuifw.app.menu
94     object.old_exit_key_handler = appuifw.app.exit_key_handler
95     object.old_title=appuifw.app.title
96
97 def restore_gui(object):
98     appuifw.app.body = object.old_gui
99     appuifw.app.menu = object.old_menu
100     appuifw.app.exit_key_handler = object.old_exit_key_handler
101     appuifw.app.title = object.old_title
102
103 class View(object):
104     def __init__(self):
105         self.title = None
106         self.view = None
107         self.lock = Ao_lock()
108         self.exit_flag = False
109         super(View, self).__init__()
110
111     def set_title(self, title):
112         self.title = title
113    
114     def set_view(self, view):
115         """
116         Sets the main view to be displayed (e.g., an appuifw.Listbox
117         instance).
118         """
119         self.view = view
120
121     def run(self):
122         self.adjustment = None
123         save_gui(self)
124         appuifw.app.screen=COMMON_CONFIG['screen'].encode('utf-8')
125         appuifw.app.title=self.title
126         appuifw.app.body=self.view
127         appuifw.app.exit_key_handler=self.exit
128         try:
129             self.lock.wait()
130             while not self.exit_flag:
131                 self.refresh()
132                 self.lock.wait()
133         except:
134             pass
135         restore_gui(self)
136
137     def exit(self):
138         self.exit_flag = True
139         self.lock.signal()
140
141     def update(self,subject=None):
142         """
143         Update the current view (e.g., make sure refresh is called). We
144         can't call it directly, since we're in another thread.
145         """
146         if self.lock:
147             self.lock.signal()
148
149     def refresh(self):
150         """
151         Called when the current view must be updated. Never call
152         directly. Subclasses should extend this method, not update.
153         """
154         appuifw.app.menu=self.get_menu_entries()
155
156     def get_menu_entries(self):
157         """ Returns a list of menu entries to display. Will be
158         automatically updated on each refresh.
159
160         Each menu entry is a tuple of a title for the entry and a
161         function to call when the entry is selected.
162         """
163         return []
164
165 class ListView(View):
166     def __init__(self):
167         super(ListView, self).__init__()
168         self.set_view(appuifw.Listbox(self.items(),self.entry_selected))
169
170     def entry_selected(self):
171         """
172         This function is called when the user selects an an entry (e.g.,
173         navigates to it and push the ok button).
174         """
175         pass
176     
177     def index_changed(self,adjustment=None):
178         if adjustment:
179             index = self.selected_index() + adjustment
180         else:
181             index = self.selected_index()
182         if index < 0:
183             index = len(self.widgets) - 1
184         if index >= len(self.widgets):
185             index = 0
186         self.set_bindings_for_selection(index)
187
188     def set_index(self,index):
189         if index > len(self.widgets):
190             index = len(self.widgets)
191         if index < 0:
192             index = 0
193         self.view.set_list(self.items(),index)
194
195     def selected_index(self):
196         return self.view.current()
197
198 class WidgetBasedListView(ListView):
199     def __init__(self):
200         self.widgets = self.generate_widgets()
201         super(WidgetBasedListView,self).__init__()
202
203     def run(self):
204         self.refresh()
205         self.set_bindings_for_selection(0)
206         ListView.run(self)
207
208     def notify(self,object,attribute,new=None,old=None):
209         self.refresh()
210     def refresh(self):
211         self.widgets = self.generate_widgets()
212         self.redisplay_widgets()
213         super(WidgetBasedListView,self).refresh()
214     def redisplay_widgets(self):
215         self.set_index(self.selected_index())
216     def items(self):
217         return self.all_widget_texts()
218     def all_widget_texts(self):
219         return [entry.list_repr() for entry in self.widgets]
220
221     
222
223     def current_widget(self):
224         return self.widgets[self.selected_index()]
225         
226
227 class KeyBindingView(View):
228     
229     def __init__(self):
230         self.binding_map = {}
231         super(KeyBindingView,self).__init__()
232
233     def set_keybindings(self, binding_map):
234         """
235         Set a new map of key bindings. This map maps method names to a
236         tuple of keyname and description.
237
238         The method name refers to a method on the selected item, or the
239         current view.
240
241         Example: { 'search_item' : ('0', 'Search item') }
242
243         """
244         self.binding_map = binding_map
245
246     def get_menu_entries(self):
247         menu_entries=[]
248         for key,key_name,description,function in self.key_and_menu_bindings(self.selected_index()):
249             if description != '':
250                 if key:
251                     if key_name == 'Backspace': key_name='C'
252                     description='[%s] '%key_name +description
253                 else:
254                     description='    '+description
255                 menu_entries.append((description,function)) 
256         menu_entries.append((u'Exit', self.exit))
257         return menu_entries + super(KeyBindingView, self).get_menu_entries()
258
259     def set_bindings_for_selection(self,selected_index):
260         self.remove_all_key_bindings()
261         
262         for key,key_name,description,function in self.key_and_menu_bindings(selected_index):
263             if key:
264                 self.view.bind(key,function)
265         self.view.bind(EKeyUpArrow,lambda: self.index_changed(-1))
266         self.view.bind(EKeyDownArrow,lambda: self.index_changed(1))
267         
268     def remove_all_key_bindings(self):
269         for key in all_key_values():
270             self.view.bind(key,no_action)
271
272 class SearchableListView(WidgetBasedListView):
273     def __init__(self):
274         self.current_entry_filter_index = -1
275         self.entry_filters = []
276         self.filtered_list = lambda:[]
277         self.lock = None
278         super(SearchableListView,self).__init__()
279
280     def set_filters(self, entry_filters):
281         """
282         Set the filters that could be applied to this list. Each filter
283         can be applied in turn by calling switch_entry_filter (for
284         example from a key binding).
285
286         The entry_filters argument should be a list of filters. The
287         active filter is stored into self.filtered_list and should be
288         processed by generate_widgets in the subclass.
289         """
290         self.current_entry_filter_index = 0
291         self.entry_filters = entry_filters
292         self.filtered_list = self.entry_filters[0]
293
294     def search_item(self):
295         selected_item = appuifw.selection_list(self.all_widget_texts(),search_field=1)
296         if selected_item == None or selected_item == -1:
297             selected_item = self.selected_index()
298         self.view.set_list(self.items(),selected_item)
299         self.set_bindings_for_selection(selected_item)
300
301     def switch_entry_filter(self):
302         self.current_entry_filter_index += 1
303         self.filtered_list = self.entry_filters[self.current_entry_filter_index % len(self.entry_filters)]
304         self.refresh()
305
306
307 class EditableListView(SearchableListView,KeyBindingView):
308     def __init__(self):
309         super(EditableListView, self).__init__()
310
311     def key_and_menu_bindings(self,selected_index):
312         key_and_menu_bindings=[]
313         for function in applicable_functions(self.widgets[selected_index],self.binding_map)+\
314             applicable_functions(self,self.binding_map):
315             execute_and_update_function = self.execute_and_update(function)
316             (key,description) = self.binding_map[function.__name__]
317             key_and_menu_bindings.append((get_key(key),key,description,execute_and_update_function))
318         return key_and_menu_bindings
319
320     def entry_selected(self):
321         self.current_widget().change()
322         self.refresh()
323     def execute_and_update(self,function):
324         return lambda: (function(),self.refresh(),self.index_changed())
325
326     def notify(self,item,attribute,new=None,old=None):
327         self.refresh()
328
329 #class DisplayableFunction:
330 #    def __init__(self,display_name,function):
331 #        self.display_name = display_name
332 #        self.function = function
333 #    def list_repr(self):
334 #        return self.display_name
335 #    def execute(self):
336 #        function()
337
338 # Public API
339 __all__= ('EditableListView','show_config')