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