1 from config.config import *
2 from model.projects import Projects
6 from log.logging import logger
7 from e32 import Ao_lock, in_emulator
8 from key_codes import *
14 for k, v in cfg.items():
15 v = cfg.format_value(v)
16 if isinstance(v, int) or isinstance(v, long):
19 elif isinstance(v, list) or isinstance(v, tuple):
21 if not isinstance(item, unicode):
22 raise Exception("list can contain only unicode objects, "\
23 "object %r is not supported" % item)
26 elif isinstance(v, unicode):
29 raise Exception("%s has non-supported value" % k)
31 fields.append((unicode(k), tname, v))
34 form = appuifw.Form(fields=fields, flags=appuifw.FFormEditModeOnly | \
35 appuifw.FFormDoubleSpaced)
40 form.save_hook = save_hook
44 # return true if user saved, false otherwise
48 for label, tname, value in form:
50 value = (value[0], int(value[1]))
52 cfg[str(label)] = cfg.parse_value(value)
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]
64 def get_key(key_name):
68 key=eval('EKey%s'%key_name)
71 def key_shortname(key_name):
72 """ Find the one-character name for a key """
75 elif key_name == 'Backspace':
81 return filter(lambda entry:entry[0:4]=='EKey',dir(key_codes))
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
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
114 # Store a list of keys we bound, so we can unbind them
118 self.lock = Ao_lock()
119 self.exit_flag = False
120 super(View, self).__init__()
122 def set_title(self, title):
125 def set_view(self, view):
127 Sets the main view to be displayed (e.g., an appuifw.Listbox
133 self.adjustment = None
135 appuifw.app.screen=COMMON_CONFIG['screen'].encode('utf-8')
136 appuifw.app.title=self.title
137 appuifw.app.body=self.view
138 appuifw.app.exit_key_handler=self.exit
140 while not self.exit_flag:
144 # TODO: Find out which exceptions to catch here. Catching
145 # and silencing all exceptions is not a good idea.
150 self.exit_flag = True
153 def update(self,subject=None):
155 Update the current view (e.g., make sure refresh is called). We
156 can't call it directly, since we're in another thread.
163 Called when the current view must be updated. Never call
164 directly. Subclasses should extend this method, not update.
168 def refresh_menu(self):
170 Refresh the menu and its bindings. Calls self.menu_items() to
173 # Two helper functions
174 def shortcut_prefix(key_name):
175 short = key_shortname(key_name)
177 return '[%s]' % short
181 def do_entry((text, callback, key_name)):
182 key = get_key(key_name)
184 self.view.bind(key, callback)
185 self.menu_keys.append(key)
186 title = "%s %s" % (shortcut_prefix(key_name), text)
187 return(title, callback)
189 # Clear the bindings we previously added (we can't just clear
190 # all bindings, since other classes might have added other
192 for key in self.menu_keys:
193 self.view.bind(key, no_action)
196 # Set the menu, and let do_entry add binds at the same time.
197 appuifw.app.menu = [do_entry(item) for item in self.menu_items()]
199 def menu_items(self):
201 Should return a list of menu items. Each menu item is a tuple:
202 (text, callback, shortcut key name).
204 return [(u'Exit', self.exit, None)]
206 class ListView(View):
208 super(ListView, self).__init__()
209 self.current_index = None
210 self.items_cache = self.items()
211 self.set_view(appuifw.Listbox(self.items_cache, self.entry_selected))
212 self.view.bind(EKeyUpArrow,lambda: self.arrow_key_pressed(-1))
213 self.view.bind(EKeyDownArrow,lambda: self.arrow_key_pressed(1))
215 def arrow_key_pressed(self, dir):
217 This function is called when an arrow key is pressed. Since we
218 don't get any "current list index has changed" events, we'll
219 have to create these ourselves this way.
221 Since the current index is only updated after the key event,
222 we'll have to adjust the index with the direction of the
223 keypress (-1 for up, +1 for down).
225 self.current_index = self.wrap_index(self.selected_index() + dir)
227 self.current_index = None
231 super(ListView, self).refresh()
233 def refresh_list(self):
234 """ Reload the list items. Calls items() again. """
235 # Remember which item was selected
236 selected = self.selected_item()
238 self.items_cache = self.items()
240 # Try to find the selected item in the new list (based on
242 selected_index = self.items_cache.index(selected)
244 # If the selected item is no longer present, just keep the
245 # index the same (but be careful not to fall off the end).
246 selected_index = self.clip_index(self.selected_index())
247 # Update the items in the view
248 self.view.set_list(self.items_cache, selected_index)
252 super(ListView, self).run()
254 def entry_selected(self):
256 This function is called when the user selects an an entry (e.g.,
257 navigates to it and push the ok button).
261 def index_changed(self):
263 This function is called when the index changes. The given index
264 is the new index (don't use self.selected_index() here, since it
265 won't be correct yet!).
270 """ This function should return the list of items to display.
271 See appuifw.ListBox for valid elements for this list. """
274 def set_index(self,index):
275 """ Changes the currently selected item to index. """
276 self.view.set_list(self.items_cache, self.clip_index(index))
278 def selected_item(self):
279 """ Returns the (title of the) currently selected list item. """
280 if not self.items_cache:
281 return None # No items, so none is selected.
282 return self.items_cache[self.selected_index()]
285 def selected_index(self):
286 """ Returns the currently selected index. """
287 if not self.current_index is None:
288 # We allow the current index to be overridden, so it can be
289 # valid during index_changed events. See arrow_key_pressed.
290 return self.current_index
292 return self.view.current()
294 def clip_index(self, index):
296 Make sure the given index fits within the bounds of this
297 list. If it doesn't, clip it off at the ends of the list (e.g,
300 max_index = len(self.items_cache) - 1
301 return max (0, min(max_index, index))
303 def wrap_index(self, index):
305 Make sure the given index fits within the bounds of this
306 list. If it doesn't, wrap it around (e.g., -1 becomes 5 in a
309 count = len(self.items_cache)
312 class WidgetBasedListView(ListView):
314 self.binding_map = {}
315 self.widgets = self.generate_widgets()
316 super(WidgetBasedListView,self).__init__()
318 def index_changed(self):
320 super(WidgetBasedListView, self).index_changed()
322 def entry_selected(self):
323 self.current_widget().change()
326 def notify(self,object,attribute,new=None,old=None):
330 self.refresh_widgets()
331 super(WidgetBasedListView,self).refresh()
333 def refresh_widgets(self):
334 """ Refresh the widget list. Calls generate_widgets(). """
335 self.widgets = self.generate_widgets()
337 def redisplay_widgets(self):
339 Redisplay the widgets. Should be called if the widgets
340 themselves have changed, does not call generate_widgets again.
345 # Let ListView show each widget's text.
346 return self.all_widget_texts()
348 def all_widget_texts(self):
350 Return the widget texts as they should be displayed in the
353 return [entry.list_repr() for entry in self.widgets]
355 def current_widget(self):
356 """ Returns the currently selected widget. """
357 return self.widgets[self.selected_index()]
359 def generate_widgets():
360 """ This function should return a list of widgets. """
363 def menu_items(self):
364 # Determine the current menu based on the methods available on
365 # the selected widget and on ourselves.
367 def make_callback(f):
368 # This is rather complicated, but loops don't have their own
369 # scope, so putting do_callback inside the loop would not
370 # capture the value of function, but only a reference to the
371 # loop variable (always evaluating to the final value...)
376 for function in applicable_functions(self.current_widget(),self.binding_map)+\
377 applicable_functions(self,self.binding_map):
378 (key,description) = self.binding_map[function.__name__]
379 menu_items.append((description, make_callback(function), key))
380 menu_items += super(WidgetBasedListView, self).menu_items()
383 def set_menu(self, binding_map):
385 Set a new map of menu entries with hotkeys. This map maps method names to a
386 tuple of keyname and description.
388 Keyname is a string containing the name of the key (the
389 part after EKey, e.g., "0", "Star", etc.). Keyname can be "", in
390 which case the item has no shortcut.
392 The method name refers to a method on the selected widget, or
395 Example: { 'search_item' : ('0', 'Search item') }
397 self.binding_map = binding_map
399 class SearchableListView(WidgetBasedListView):
401 self.current_entry_filter_index = -1
402 self.entry_filters = []
403 self.filtered_list = lambda:[]
405 super(SearchableListView,self).__init__()
407 def set_filters(self, entry_filters):
409 Set the filters that could be applied to this list. Each filter
410 can be applied in turn by calling switch_entry_filter (for
411 example from a key binding).
413 The entry_filters argument should be a list of filters. The
414 active filter is stored into self.filtered_list and should be
415 processed by generate_widgets in the subclass.
417 self.current_entry_filter_index = 0
418 self.entry_filters = entry_filters
419 self.filtered_list = self.entry_filters[0]
421 def search_item(self):
422 selected_item = appuifw.selection_list(self.all_widget_texts(),search_field=1)
423 if selected_item == None or selected_item == -1:
424 selected_item = self.selected_index()
425 self.view.set_list(self.items(),selected_item)
426 self.set_bindings_for_selection(selected_item)
428 def switch_entry_filter(self):
429 self.current_entry_filter_index += 1
430 self.filtered_list = self.entry_filters[self.current_entry_filter_index % len(self.entry_filters)]
433 #class DisplayableFunction:
434 # def __init__(self,display_name,function):
435 # self.display_name = display_name
436 # self.function = function
437 # def list_repr(self):
438 # return self.display_name
443 __all__= ('SearchableListView','show_config')