Define (a dummy) get_menu_entries in 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         super(View, self).exit()
141
142     def refresh(self):
143         """
144         Update the gui after a change in model or some user interaction.
145         Should be filled by subclasses.
146         """
147         super(View, self).refresh()
148
149     def get_menu_entries(self):
150         """ Returns a list of menu entries to display. Will be
151         automatically updated on each refresh.
152
153         Each menu entry is a tuple of a title for the entry and a
154         function to call when the entry is selected.
155         """
156         return []
157
158 class ListView(View):
159     def __init__(self):
160         super(ListView, self).__init__()
161         self.set_view(appuifw.Listbox(self.items(),self.entry_selected))
162
163     def entry_selected(self):
164         """
165         This function is called when the user selects an an entry (e.g.,
166         navigates to it and push the ok button).
167         """
168         super(ListView).entry_selected()
169     
170     def update(self,subject=None):
171         #logger.log(u'Updated %s'%repr(self))
172         if self.lock:
173             self.lock.signal()
174         #pass
175
176     def index_changed(self,adjustment=None):
177         if adjustment:
178             index = self.selected_index() + adjustment
179         else:
180             index = self.selected_index()
181         if index < 0:
182             index = len(self.widgets) - 1
183         if index >= len(self.widgets):
184             index = 0
185         self.set_bindings_for_selection(index)
186
187     def refresh(self):
188         appuifw.app.menu=self.get_menu_entries()
189
190     def set_index(self,index):
191         if index > len(self.widgets):
192             index = len(self.widgets)
193         if index < 0:
194             index = 0
195         self.view.set_list(self.items(),index)
196
197     def selected_index(self):
198         return self.view.current()
199
200 class WidgetBasedListView(ListView):
201     def __init__(self):
202         self.widgets = self.generate_widgets()
203         super(WidgetBasedListView,self).__init__()
204
205     def run(self):
206         self.refresh()
207         self.set_bindings_for_selection(0)
208         ListView.run(self)
209
210     def notify(self,object,attribute,new=None,old=None):
211         self.refresh()
212     def refresh(self):
213         self.widgets = self.generate_widgets()
214         self.redisplay_widgets()
215         super(WidgetBasedListView,self).refresh()
216     def redisplay_widgets(self):
217         self.set_index(self.selected_index())
218     def items(self):
219         return self.all_widget_texts()
220     def all_widget_texts(self):
221         return [entry.list_repr() for entry in self.widgets]
222
223     
224
225     def current_widget(self):
226         return self.widgets[self.selected_index()]
227         
228
229 class KeyBindingView(View):
230     
231     def __init__(self):
232         self.binding_map = {}
233         super(KeyBindingView,self).__init__()
234
235     def set_keybindings(self, binding_map):
236         """
237         Set a new map of key bindings. This map maps method names to a
238         tuple of keyname and description.
239
240         The method name refers to a method on the selected item, or the
241         current view.
242
243         Example: { 'search_item' : ('0', 'Search item') }
244
245         """
246         self.binding_map = binding_map
247
248     def get_menu_entries(self):
249         menu_entries=[]
250         for key,key_name,description,function in self.key_and_menu_bindings(self.selected_index()):
251             if description != '':
252                 if key:
253                     if key_name == 'Backspace': key_name='C'
254                     description='[%s] '%key_name +description
255                 else:
256                     description='    '+description
257                 menu_entries.append((description,function)) 
258         menu_entries.append((u'Exit', self.exit))
259         return menu_entries + super(KeyBindingView, self).get_menu_entries()
260
261     def set_bindings_for_selection(self,selected_index):
262         self.remove_all_key_bindings()
263         
264         for key,key_name,description,function in self.key_and_menu_bindings(selected_index):
265             if key:
266                 self.view.bind(key,function)
267         self.view.bind(EKeyUpArrow,lambda: self.index_changed(-1))
268         self.view.bind(EKeyDownArrow,lambda: self.index_changed(1))
269         
270     def remove_all_key_bindings(self):
271         for key in all_key_values():
272             self.view.bind(key,no_action)
273
274 class SearchableListView(WidgetBasedListView):
275     def __init__(self):
276         self.current_entry_filter_index = -1
277         self.entry_filters = []
278         self.filtered_list = lambda:[]
279         self.lock = None
280         super(SearchableListView,self).__init__()
281
282     def set_filters(self, entry_filters):
283         """
284         Set the filters that could be applied to this list. Each filter
285         can be applied in turn by calling switch_entry_filter (for
286         example from a key binding).
287
288         The entry_filters argument should be a list of filters. The
289         active filter is stored into self.filtered_list and should be
290         processed by generate_widgets in the subclass.
291         """
292         self.current_entry_filter_index = 0
293         self.entry_filters = entry_filters
294         self.filtered_list = self.entry_filters[0]
295
296     def search_item(self):
297         selected_item = appuifw.selection_list(self.all_widget_texts(),search_field=1)
298         if selected_item == None or selected_item == -1:
299             selected_item = self.selected_index()
300         self.view.set_list(self.items(),selected_item)
301         self.set_bindings_for_selection(selected_item)
302
303     def switch_entry_filter(self):
304         self.current_entry_filter_index += 1
305         self.filtered_list = self.entry_filters[self.current_entry_filter_index % len(self.entry_filters)]
306         self.refresh()
307
308
309 class EditableListView(SearchableListView,KeyBindingView):
310     def __init__(self):
311         super(EditableListView, self).__init__()
312
313     def key_and_menu_bindings(self,selected_index):
314         key_and_menu_bindings=[]
315         for function in applicable_functions(self.widgets[selected_index],self.binding_map)+\
316             applicable_functions(self,self.binding_map):
317             execute_and_update_function = self.execute_and_update(function)
318             (key,description) = self.binding_map[function.__name__]
319             key_and_menu_bindings.append((get_key(key),key,description,execute_and_update_function))
320         return key_and_menu_bindings
321
322     def entry_selected(self):
323         self.current_widget().change()
324         self.refresh()
325     def execute_and_update(self,function):
326         return lambda: (function(),self.refresh(),self.index_changed())
327
328     def notify(self,item,attribute,new=None,old=None):
329         self.refresh()
330
331 #class DisplayableFunction:
332 #    def __init__(self,display_name,function):
333 #        self.display_name = display_name
334 #        self.function = function
335 #    def list_repr(self):
336 #        return self.display_name
337 #    def execute(self):
338 #        function()
339
340 # Public API
341 __all__= ('EditableListView','show_config')