X-Git-Url: https://git.stderr.nl/gitweb?a=blobdiff_plain;f=src%2Fgui%2Fgui.py;h=5c4d0b8ee7e221ad26b087909f27ff64091dd20e;hb=b7578d4c2fa84be732ae5afeaa7d365c54e096e0;hp=a07091b3fb06126b37812e7770ab2b4f746041f9;hpb=3f1310771fb7c333ebef7a2f0beac4120a0d8b65;p=matthijs%2Fupstream%2Fmobilegtd.git diff --git a/src/gui/gui.py b/src/gui/gui.py index a07091b..5c4d0b8 100644 --- a/src/gui/gui.py +++ b/src/gui/gui.py @@ -111,6 +111,8 @@ def restore_gui(object): class View(object): def __init__(self): + # Store a list of keys we bound, so we can unbind them + self.menu_keys = [] self.title = None self.view = None self.lock = Ao_lock() @@ -135,12 +137,13 @@ class View(object): appuifw.app.body=self.view appuifw.app.exit_key_handler=self.exit try: - self.lock.wait() while not self.exit_flag: self.refresh() self.lock.wait() except: - pass + # TODO: Find out which exceptions to catch here. Catching + # and silencing all exceptions is not a good idea. + raise restore_gui(self) def exit(self): @@ -160,21 +163,52 @@ class View(object): Called when the current view must be updated. Never call directly. Subclasses should extend this method, not update. """ - appuifw.app.menu=self.get_menu_entries() + self.refresh_menu() - def get_menu_entries(self): - """ Returns a list of menu entries to display. Will be - automatically updated on each refresh. + def refresh_menu(self): + """ + Refresh the menu and its bindings. Calls self.menu_items() to + get the new menu. + """ + # Two helper functions + def shortcut_prefix(key_name): + short = key_shortname(key_name) + if short: + return '[%s]' % short + else: + return ' ' + + def do_entry((text, callback, key_name)): + key = get_key(key_name) + if key: + self.view.bind(key, callback) + self.menu_keys.append(key) + title = "%s %s" % (shortcut_prefix(key_name), text) + return(title, callback) + + # Clear the bindings we previously added (we can't just clear + # all bindings, since other classes might have added other + # bindings) + for key in self.menu_keys: + self.view.bind(key, no_action) + self.menu_keys = [] + + # Set the menu, and let do_entry add binds at the same time. + appuifw.app.menu = [do_entry(item) for item in self.menu_items()] - Each menu entry is a tuple of a title for the entry and a - function to call when the entry is selected. + def menu_items(self): """ - return [] + Should return a list of menu items. Each menu item is a tuple: + (text, callback, shortcut key name). + """ + return [(u'Exit', self.exit, None)] class ListView(View): def __init__(self): super(ListView, self).__init__() - self.set_view(appuifw.Listbox(self.items(),self.entry_selected)) + self.current_index = None + self.items_cache = [] + self.set_view(appuifw.Listbox([], self.entry_selected)) self.view.bind(EKeyUpArrow,lambda: self.arrow_key_pressed(-1)) self.view.bind(EKeyDownArrow,lambda: self.arrow_key_pressed(1)) @@ -188,8 +222,34 @@ class ListView(View): we'll have to adjust the index with the direction of the keypress (-1 for up, +1 for down). """ - new_index = (self.selected_index() + dir) % len(self.items()) - self.index_changed(new_index) + self.current_index = self.wrap_index(self.selected_index() + dir) + self.index_changed() + self.current_index = None + + def refresh(self): + self.refresh_list() + super(ListView, self).refresh() + + def refresh_list(self): + """ Reload the list items. Calls items() again. """ + # Remember which item was selected + selected = self.selected_item() + # Refresh the list + self.items_cache = self.items() + try: + # Try to find the selected item in the new list (based on + # the display text). + selected_index = self.items_cache.index(selected) + except ValueError: + # If the selected item is no longer present, just keep the + # index the same (but be careful not to fall off the end). + selected_index = self.clip_index(self.selected_index()) + # Update the items in the view + self.view.set_list(self.items_cache, selected_index) + + def run(self): + self.index_changed() + super(ListView, self).run() def entry_selected(self): """ @@ -198,7 +258,7 @@ class ListView(View): """ pass - def index_changed(self,new_index): + def index_changed(self): """ This function is called when the index changes. The given index is the new index (don't use self.selected_index() here, since it @@ -213,88 +273,123 @@ class ListView(View): def set_index(self,index): """ Changes the currently selected item to index. """ - self.view.set_list(self.items(),index % len(self.items())) + self.view.set_list(self.items_cache, self.clip_index(index)) + + def selected_item(self): + """ Returns the (title of the) currently selected list item. """ + if not self.items_cache: + return None # No items, so none is selected. + return self.items_cache[self.selected_index()] + def selected_index(self): """ Returns the currently selected index. """ - return self.view.current() + if not self.current_index is None: + # We allow the current index to be overridden, so it can be + # valid during index_changed events. See arrow_key_pressed. + return self.current_index + else: + return self.view.current() + + def clip_index(self, index): + """ + Make sure the given index fits within the bounds of this + list. If it doesn't, clip it off at the ends of the list (e.g, + -1 becomes 0). + """ + max_index = len(self.items_cache) - 1 + return max (0, min(max_index, index)) + def wrap_index(self, index): + """ + Make sure the given index fits within the bounds of this + list. If it doesn't, wrap it around (e.g., -1 becomes 5 in a + 6-element list). + """ + count = len(self.items_cache) + return index % count + class WidgetBasedListView(ListView): def __init__(self): + self.binding_map = {} self.widgets = self.generate_widgets() super(WidgetBasedListView,self).__init__() - def run(self): - self.refresh() - self.set_bindings_for_selection(0) - ListView.run(self) + def index_changed(self): + self.refresh_menu() + super(WidgetBasedListView, self).index_changed() - def index_changed(self,new_index): - self.set_bindings_for_selection(new_index) - super(WidgeteBasedListView, self).index_changed(new_index) + def entry_selected(self): + self.current_widget().change() + self.refresh() def notify(self,object,attribute,new=None,old=None): self.refresh() + def refresh(self): - self.widgets = self.generate_widgets() - self.redisplay_widgets() + self.refresh_widgets() super(WidgetBasedListView,self).refresh() + + def refresh_widgets(self): + """ Refresh the widget list. Calls generate_widgets(). """ + self.widgets = self.generate_widgets() + def redisplay_widgets(self): - self.set_index(self.selected_index()) + """ + Redisplay the widgets. Should be called if the widgets + themselves have changed, does not call generate_widgets again. + """ + self.refresh_list() + def items(self): + # Let ListView show each widget's text. return self.all_widget_texts() + def all_widget_texts(self): + """ + Return the widget texts as they should be displayed in the + list view. + """ return [entry.list_repr() for entry in self.widgets] - - def current_widget(self): + """ Returns the currently selected widget. """ return self.widgets[self.selected_index()] - -class KeyBindingView(View): - - def __init__(self): - self.binding_map = {} - super(KeyBindingView,self).__init__() - - def set_keybindings(self, binding_map): + def generate_widgets(): + """ This function should return a list of widgets. """ + return [] + + def menu_items(self): + # Determine the current menu based on the methods available on + # the selected widget and on ourselves. + menu_items = [] + for function in applicable_functions(self.current_widget(),self.binding_map)+\ + applicable_functions(self,self.binding_map): + (key,description) = self.binding_map[function.__name__] + def do_callback(): + function() + self.update() + menu_items.append((description, do_callback, key)) + menu_items += super(WidgetBasedListView, self).menu_items() + return menu_items + + def set_menu(self, binding_map): """ - Set a new map of key bindings. This map maps method names to a + Set a new map of menu entries with hotkeys. This map maps method names to a tuple of keyname and description. - The method name refers to a method on the selected item, or the - current view. + Keyname is a string containing the name of the key (the + part after EKey, e.g., "0", "Star", etc.). Keyname can be "", in + which case the item has no shortcut. - Example: { 'search_item' : ('0', 'Search item') } + The method name refers to a method on the selected widget, or + the current view. + Example: { 'search_item' : ('0', 'Search item') } """ self.binding_map = binding_map - def get_menu_entries(self): - menu_entries=[] - for key,key_name,description,function in self.key_and_menu_bindings(self.selected_index()): - if description != '': - if key: - if key_name == 'Backspace': key_name='C' - description='[%s] '%key_name +description - else: - description=' '+description - menu_entries.append((description,function)) - menu_entries.append((u'Exit', self.exit)) - return menu_entries + super(KeyBindingView, self).get_menu_entries() - - def set_bindings_for_selection(self,selected_index): - self.remove_all_key_bindings() - - for key,key_name,description,function in self.key_and_menu_bindings(selected_index): - if key: - self.view.bind(key,function) - - def remove_all_key_bindings(self): - for key in all_key_values(): - self.view.bind(key,no_action) - class SearchableListView(WidgetBasedListView): def __init__(self): self.current_entry_filter_index = -1 @@ -329,29 +424,6 @@ class SearchableListView(WidgetBasedListView): self.filtered_list = self.entry_filters[self.current_entry_filter_index % len(self.entry_filters)] self.refresh() - -class EditableListView(SearchableListView,KeyBindingView): - def __init__(self): - super(EditableListView, self).__init__() - - def key_and_menu_bindings(self,selected_index): - key_and_menu_bindings=[] - for function in applicable_functions(self.widgets[selected_index],self.binding_map)+\ - applicable_functions(self,self.binding_map): - execute_and_update_function = self.execute_and_update(function) - (key,description) = self.binding_map[function.__name__] - key_and_menu_bindings.append((get_key(key),key,description,execute_and_update_function)) - return key_and_menu_bindings - - def entry_selected(self): - self.current_widget().change() - self.refresh() - def execute_and_update(self,function): - return lambda: (function(),self.refresh(),self.index_changed()) - - def notify(self,item,attribute,new=None,old=None): - self.refresh() - #class DisplayableFunction: # def __init__(self,display_name,function): # self.display_name = display_name @@ -362,4 +434,4 @@ class EditableListView(SearchableListView,KeyBindingView): # function() # Public API -__all__= ('EditableListView','show_config') +__all__= ('SearchableListView','show_config')