Reshuffle the gui class structure a bit more.
authorMatthijs Kooijman <matthijs@stdin.nl>
Tue, 3 Nov 2009 14:13:10 +0000 (15:13 +0100)
committerMatthijs Kooijman <matthijs@stdin.nl>
Tue, 3 Nov 2009 14:20:25 +0000 (15:20 +0100)
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.

src/gui/gui.py
src/gui/project_details/project_view.py
src/gui/projects_list/project_list_view.py

index a07091b3fb06126b37812e7770ab2b4f746041f9..94ade0689c9a09b1a4a212d9f38a61660a579c4b 100644 (file)
@@ -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')
index 7827009e621d6a9f2dcce89a01553caf5c9f2249..801b067dcbb3b796db3d59466fe75e0a7e3de993 100644 (file)
@@ -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)
index bbb81f6085627f37c0d40e2cda12f5f611f444e5..3300f4e1310297e518c6839a088f472b08939ea2 100644 (file)
@@ -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')