bbb5f0a8bf6609160f74ad52e28ee261712b6845
[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         appuifw.app.menu=self.get_menu_entries()
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 set_index(self,index):
187         if index > len(self.widgets):
188             index = len(self.widgets)
189         if index < 0:
190             index = 0
191         self.view.set_list(self.items(),index)
192
193     def selected_index(self):
194         return self.view.current()
195
196 class WidgetBasedListView(ListView):
197     def __init__(self):
198         self.widgets = self.generate_widgets()
199         super(WidgetBasedListView,self).__init__()
200
201     def run(self):
202         self.refresh()
203         self.set_bindings_for_selection(0)
204         ListView.run(self)
205
206     def notify(self,object,attribute,new=None,old=None):
207         self.refresh()
208     def refresh(self):
209         self.widgets = self.generate_widgets()
210         self.redisplay_widgets()
211         super(WidgetBasedListView,self).refresh()
212     def redisplay_widgets(self):
213         self.set_index(self.selected_index())
214     def items(self):
215         return self.all_widget_texts()
216     def all_widget_texts(self):
217         return [entry.list_repr() for entry in self.widgets]
218
219     
220
221     def current_widget(self):
222         return self.widgets[self.selected_index()]
223         
224
225 class KeyBindingView(View):
226     
227     def __init__(self):
228         self.binding_map = {}
229         super(KeyBindingView,self).__init__()
230
231     def set_keybindings(self, binding_map):
232         """
233         Set a new map of key bindings. This map maps method names to a
234         tuple of keyname and description.
235
236         The method name refers to a method on the selected item, or the
237         current view.
238
239         Example: { 'search_item' : ('0', 'Search item') }
240
241         """
242         self.binding_map = binding_map
243
244     def get_menu_entries(self):
245         menu_entries=[]
246         for key,key_name,description,function in self.key_and_menu_bindings(self.selected_index()):
247             if description != '':
248                 if key:
249                     if key_name == 'Backspace': key_name='C'
250                     description='[%s] '%key_name +description
251                 else:
252                     description='    '+description
253                 menu_entries.append((description,function)) 
254         menu_entries.append((u'Exit', self.exit))
255         return menu_entries + super(KeyBindingView, self).get_menu_entries()
256
257     def set_bindings_for_selection(self,selected_index):
258         self.remove_all_key_bindings()
259         
260         for key,key_name,description,function in self.key_and_menu_bindings(selected_index):
261             if key:
262                 self.view.bind(key,function)
263         self.view.bind(EKeyUpArrow,lambda: self.index_changed(-1))
264         self.view.bind(EKeyDownArrow,lambda: self.index_changed(1))
265         
266     def remove_all_key_bindings(self):
267         for key in all_key_values():
268             self.view.bind(key,no_action)
269
270 class SearchableListView(WidgetBasedListView):
271     def __init__(self):
272         self.current_entry_filter_index = -1
273         self.entry_filters = []
274         self.filtered_list = lambda:[]
275         self.lock = None
276         super(SearchableListView,self).__init__()
277
278     def set_filters(self, entry_filters):
279         """
280         Set the filters that could be applied to this list. Each filter
281         can be applied in turn by calling switch_entry_filter (for
282         example from a key binding).
283
284         The entry_filters argument should be a list of filters. The
285         active filter is stored into self.filtered_list and should be
286         processed by generate_widgets in the subclass.
287         """
288         self.current_entry_filter_index = 0
289         self.entry_filters = entry_filters
290         self.filtered_list = self.entry_filters[0]
291
292     def search_item(self):
293         selected_item = appuifw.selection_list(self.all_widget_texts(),search_field=1)
294         if selected_item == None or selected_item == -1:
295             selected_item = self.selected_index()
296         self.view.set_list(self.items(),selected_item)
297         self.set_bindings_for_selection(selected_item)
298
299     def switch_entry_filter(self):
300         self.current_entry_filter_index += 1
301         self.filtered_list = self.entry_filters[self.current_entry_filter_index % len(self.entry_filters)]
302         self.refresh()
303
304
305 class EditableListView(SearchableListView,KeyBindingView):
306     def __init__(self):
307         super(EditableListView, self).__init__()
308
309     def key_and_menu_bindings(self,selected_index):
310         key_and_menu_bindings=[]
311         for function in applicable_functions(self.widgets[selected_index],self.binding_map)+\
312             applicable_functions(self,self.binding_map):
313             execute_and_update_function = self.execute_and_update(function)
314             (key,description) = self.binding_map[function.__name__]
315             key_and_menu_bindings.append((get_key(key),key,description,execute_and_update_function))
316         return key_and_menu_bindings
317
318     def entry_selected(self):
319         self.current_widget().change()
320         self.refresh()
321     def execute_and_update(self,function):
322         return lambda: (function(),self.refresh(),self.index_changed())
323
324     def notify(self,item,attribute,new=None,old=None):
325         self.refresh()
326
327 #class DisplayableFunction:
328 #    def __init__(self,display_name,function):
329 #        self.display_name = display_name
330 #        self.function = function
331 #    def list_repr(self):
332 #        return self.display_name
333 #    def execute(self):
334 #        function()
335
336 # Public API
337 __all__= ('EditableListView','show_config')