From: Matthijs Kooijman Date: Tue, 3 Nov 2009 14:13:10 +0000 (+0100) Subject: Reshuffle the gui class structure a bit more. X-Git-Url: https://git.stderr.nl/gitweb?p=matthijs%2Fupstream%2Fmobilegtd.git;a=commitdiff_plain;h=e3125688649dc16f60c42a7b9479a2663a88bace Reshuffle the gui class structure a bit more. This moves all menu and key shortcut handling code into View, deprecating KeyBindingView. Also, all menu handling for widgets is now directly in WidgetBasedListView, so EditableListView is removed. Some other cleanups have been made. --- diff --git a/src/gui/gui.py b/src/gui/gui.py index a07091b..94ade06 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() @@ -160,20 +162,47 @@ 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() + pass - 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) + return '[%s]' % short if short else ' ' - Each menu entry is a tuple of a title for the entry and a - function to call when the entry is selected. + 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()] + + 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.current_index = None self.set_view(appuifw.Listbox(self.items(),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 +217,13 @@ 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.selected_index() + dir) % len(self.items()) + self.index_changed() + self.current_index = None + + def run(self): + self.index_changed() + super(ListView, self).run() def entry_selected(self): """ @@ -198,7 +232,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 @@ -217,84 +251,91 @@ class ListView(View): 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() 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() super(WidgetBasedListView,self).refresh() + def redisplay_widgets(self): + """ + Redisplay the widgets. Should be called if the widgets + themselves have changed, does not call generate_widgets again. + """ self.set_index(self.selected_index()) + 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 +370,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 +380,4 @@ class EditableListView(SearchableListView,KeyBindingView): # function() # Public API -__all__= ('EditableListView','show_config') +__all__= ('SearchableListView','show_config') diff --git a/src/gui/project_details/project_view.py b/src/gui/project_details/project_view.py index 7827009..801b067 100644 --- a/src/gui/project_details/project_view.py +++ b/src/gui/project_details/project_view.py @@ -4,7 +4,7 @@ import thread from model.model import * from config.config import gtd_directory,Configuration from config.defaultconfig import default_actions_menu -from gui.gui import EditableListView +from gui.gui import SearchableListView from infos_widget import InfosWidget from info_widget import InfoWidget from context_widget import ContextWidget @@ -59,12 +59,12 @@ def ask_for_info(proposition): return appuifw.query(u'Enter info','text',u'%s'%(proposition)) -class ProjectView(EditableListView): +class ProjectView(SearchableListView): def __init__(self,project): self.project = project self.project.observers.append(self) super(ProjectView, self).__init__() - self.set_keybindings(ACTION_LIST_KEYS_AND_MENU) + self.set_menu(ACTION_LIST_KEYS_AND_MENU) self.set_title(self.project.name) self.set_filters([lambda:self.project.actions.with_property(lambda a:a.status==action.active)]) @@ -72,7 +72,7 @@ class ProjectView(EditableListView): self.project.observers.remove(self) # self.project.status = project.active # self.project.status.update(self.project) - EditableListView.exit(self) + super(ProjectView, self).exit() # self.project.dirty = True # self.project.write() # self.projects.update_status(self.project) diff --git a/src/gui/projects_list/project_list_view.py b/src/gui/projects_list/project_list_view.py index bbb81f6..3300f4e 100644 --- a/src/gui/projects_list/project_list_view.py +++ b/src/gui/projects_list/project_list_view.py @@ -1,6 +1,6 @@ from model import model from config import config -from gui.gui import EditableListView +from gui.gui import SearchableListView import appuifw,thread,re from model import * from config.config import gtd_directory,read_sms @@ -20,12 +20,12 @@ from logic import review_visitor PROJECT_LIST_KEYS_AND_MENU = config.Configuration(gtd_directory+"projects.cfg",default_projects_menu) sms_regexp = re.compile('([^\w ]*)',re.U) -class ProjectListView(EditableListView): +class ProjectListView(SearchableListView): def __init__(self,projects): self.projects = projects self.projects.observers.append(self) super(ProjectListView, self).__init__() - self.set_keybindings(PROJECT_LIST_KEYS_AND_MENU) + self.set_menu(PROJECT_LIST_KEYS_AND_MENU) self.set_title(u'Projects') self.set_filters([lambda:projects]) #appuifw.note(u'Before starting thread')