Got 'Perspectives' menu working
authorW. Trevor King <wking@drexel.edu>
Tue, 27 Jul 2010 20:35:43 +0000 (16:35 -0400)
committerW. Trevor King <wking@drexel.edu>
Tue, 27 Jul 2010 20:35:43 +0000 (16:35 -0400)
hooke/ui/gui/__init__.py
hooke/ui/gui/dialog/__init__.py
hooke/ui/gui/dialog/save_file.py [new file with mode: 0644]
hooke/ui/gui/dialog/selection.py
hooke/ui/gui/menu.py

index e9e45f504ac48d9a9e2482929ec18ad8e6e55186..1a6492e644766d5e971d79ad83e9c305e273f0b7 100644 (file)
@@ -9,6 +9,7 @@ import wxversion
 wxversion.select(WX_GOOD)\r
 \r
 import copy\r
+import os\r
 import os.path\r
 import platform\r
 import shutil\r
@@ -30,6 +31,8 @@ from ...command import CommandExit, Exit, Success, Failure, Command, Argument
 from ...config import Setting\r
 from ...interaction import Request, BooleanRequest, ReloadUserInterfaceConfig\r
 from ...ui import UserInterface, CommandMessage\r
+from .dialog.selection import Selection as SelectionDialog\r
+from .dialog.save_file import select_save_file\r
 from . import menu as menu\r
 from . import navbar as navbar\r
 from . import panel as panel\r
@@ -82,6 +85,7 @@ class HookeFrame (wx.Frame):
                 'view_panel': self._on_panel_visibility,\r
                 'save_perspective': self._on_save_perspective,\r
                 'delete_perspective': self._on_delete_perspective,\r
+                'select_perspective': self._on_select_perspective,\r
                 })\r
         self.SetMenuBar(self._c['menu bar'])\r
 \r
@@ -90,7 +94,7 @@ class HookeFrame (wx.Frame):
             style=wx.ST_SIZEGRIP)\r
         self.SetStatusBar(self._c['status bar'])\r
 \r
-        self._update_perspectives()\r
+        self._setup_perspectives()\r
         self._bind_events()\r
 \r
         name = self.gui.config['active perspective']\r
@@ -303,22 +307,6 @@ class HookeFrame (wx.Frame):
                 return index\r
         return -1\r
 \r
-    def _restore_perspective(self, name):\r
-        # TODO: cleanup\r
-        self.gui.config['active perspective'] = name  # TODO: push to engine's Hooke\r
-        self._c['manager'].LoadPerspective(self._perspectives[name])\r
-        self._c['manager'].Update()\r
-        for pane in self._c['manager'].GetAllPanes():\r
-            if pane.name in self._c['menu bar']._c['view']._c.keys():\r
-                pane.Check(pane.window.IsShown())\r
-\r
-    def _SavePerspectiveToFile(self, name, perspective):\r
-        filename = ''.join([name, '.txt'])\r
-        filename = lh.get_file_path(filename, ['perspective'])\r
-        perspectivesFile = open(filename, 'w')\r
-        perspectivesFile.write(perspective)\r
-        perspectivesFile.close()\r
-\r
     def select_plugin(self, _class=None, method=None, plugin=None):\r
         for option in config[section]:\r
             properties.append([option, config[section][option]])\r
@@ -457,7 +445,7 @@ class HookeFrame (wx.Frame):
         del self._c['manager']\r
         self.Destroy()\r
 \r
-    def _update_perspectives(self):\r
+    def _setup_perspectives(self):\r
         """Add perspectives to menubar and _perspectives.\r
         """\r
         self._perspectives = {\r
@@ -468,9 +456,9 @@ class HookeFrame (wx.Frame):
             files = sorted(os.listdir(path))\r
             for fname in files:\r
                 name, extension = os.path.splitext(fname)\r
-                if extension != '.txt':\r
+                if extension != self.gui.config['perspective extension']:\r
                     continue\r
-                fpath = os.path.join(path, fpath)\r
+                fpath = os.path.join(path, fname)\r
                 if not os.path.isfile(fpath):\r
                     continue\r
                 perspective = None\r
@@ -483,105 +471,94 @@ class HookeFrame (wx.Frame):
         if not self._perspectives.has_key(selected_perspective):\r
             self.gui.config['active perspective'] = 'Default'  # TODO: push to engine's Hooke\r
 \r
-        self._update_perspective_menu()\r
         self._restore_perspective(selected_perspective)\r
+        self._update_perspective_menu()\r
 \r
     def _update_perspective_menu(self):\r
         self._c['menu bar']._c['perspective'].update(\r
             sorted(self._perspectives.keys()),\r
-            self.gui.config['active perspective'],\r
-            self._on_restore_perspective)\r
-\r
-    def _on_restore_perspective(self, event):\r
-        name = self._c['menu bar'].FindItemById(event.GetId()).GetLabel()\r
+            self.gui.config['active perspective'])\r
+\r
+    def _save_perspective(self, perspective, perspective_dir, name,\r
+                          extension=None):\r
+        path = os.path.join(perspective_dir, name)\r
+        if extension != None:\r
+            path += extension\r
+        if not os.path.isdir(perspective_dir):\r
+            os.makedirs(perspective_dir)\r
+        with open(path, 'w') as f:\r
+            f.write(perspective)\r
+        self._perspectives[name] = perspective\r
         self._restore_perspective(name)\r
+        self._update_perspective_menu()\r
 \r
-    def _on_save_perspective(self, event):\r
-        def nameExists(name):\r
-            menu_position = self._c['menu bar'].FindMenu('Perspective')\r
-            menu = self._c['menu bar'].GetMenu(menu_position)\r
-            for item in menu.GetMenuItems():\r
-                if item.GetText() == name:\r
-                    return True\r
-            return False\r
-\r
-        done = False\r
-        while not done:\r
-            dialog = wx.TextEntryDialog(self, 'Enter a name for the new perspective:', 'Save perspective')\r
-            dialog.SetValue('New perspective')\r
-            if dialog.ShowModal() != wx.ID_OK:\r
-                return\r
-            else:\r
-                name = dialog.GetValue()\r
-\r
-            if nameExists(name):\r
-                dialogConfirm = wx.MessageDialog(self, 'A file with this name already exists.\n\nDo you want to replace it?', 'Confirm', wx.YES_NO|wx.ICON_QUESTION|wx.CENTER)\r
-                if dialogConfirm.ShowModal() == wx.ID_YES:\r
-                    done = True\r
-            else:\r
-                done = True\r
+    def _delete_perspectives(self, perspective_dir, names,\r
+                             extension=None):\r
+        print 'pop', names\r
+        for name in names:\r
+            path = os.path.join(perspective_dir, name)\r
+            if extension != None:\r
+                path += extension\r
+            os.remove(path)\r
+            del(self._perspectives[name])\r
+        self._update_perspective_menu()\r
+        if self.gui.config['active perspective'] in names:\r
+            self._restore_perspective('Default')\r
+        # TODO: does this bug still apply?\r
+        # Unfortunately, there is a bug in wxWidgets for win32 (Ticket #3258\r
+        #   http://trac.wxwidgets.org/ticket/3258 \r
+        # ) that makes the radio item indicator in the menu disappear.\r
+        # The code should be fine once this issue is fixed.\r
 \r
+    def _restore_perspective(self, name):\r
+        if name != self.gui.config['active perspective']:\r
+            print 'restoring perspective:', name\r
+            self.gui.config['active perspective'] = name  # TODO: push to engine's Hooke\r
+            self._c['manager'].LoadPerspective(self._perspectives[name])\r
+            self._c['manager'].Update()\r
+            for pane in self._c['manager'].GetAllPanes():\r
+                if pane.name in self._c['menu bar']._c['view']._c.keys():\r
+                    pane.Check(pane.window.IsShown())\r
+\r
+    def _on_save_perspective(self, *args):\r
         perspective = self._c['manager'].SavePerspective()\r
-        self._SavePerspectiveToFile(name, perspective)\r
-        self.gui.config['active perspectives'] = name\r
-        self._update_perspective_menu()\r
-#        if nameExists(name):\r
-#            #check the corresponding menu item\r
-#            menu_item = self.GetPerspectiveMenuItem(name)\r
-#            #replace the perspectiveStr in _pespectives\r
-#            self._perspectives[name] = perspective\r
-#        else:\r
-#            #because we deal with radio items, we need to do some extra work\r
-#            #delete all menu items from the perspectives menu\r
-#            for item in self._perspectives_menu.GetMenuItems():\r
-#                self._perspectives_menu.DeleteItem(item)\r
-#            #recreate the perspectives menu\r
-#            self._perspectives_menu.Append(ID_SavePerspective, 'Save Perspective')\r
-#            self._perspectives_menu.Append(ID_DeletePerspective, 'Delete Perspective')\r
-#            self._perspectives_menu.AppendSeparator()\r
-#            #convert the perspectives dictionary into a list\r
-#            # the list contains:\r
-#            #[0]: name of the perspective\r
-#            #[1]: perspective\r
-#            perspectives_list = [key for key, value in self._perspectives.iteritems()]\r
-#            perspectives_list.append(name)\r
-#            perspectives_list.sort()\r
-#            #add all previous perspectives\r
-#            for index, item in enumerate(perspectives_list):\r
-#                menu_item = self._perspectives_menu.AppendRadioItem(ID_FirstPerspective + index, item)\r
-#                if item == name:\r
-#                    menu_item.Check()\r
-#            #add the new perspective to _perspectives\r
-#            self._perspectives[name] = perspective\r
-\r
-    def _on_delete_perspective(self, event):\r
-        dialog = panel.selection.Selection(\r
-            options=sorted(os.listdir(self.gui.config['perspective path'])),\r
+        name = self.gui.config['active perspective']\r
+        if name == 'Default':\r
+            name = 'New perspective'\r
+        name = select_save_file(\r
+            directory=self.gui.config['perspective path'],\r
+            name=name,\r
+            extension=self.gui.config['perspective extension'],\r
+            parent=self,\r
+            message='Enter a name for the new perspective:',\r
+            caption='Save perspective')\r
+        if name == None:\r
+            return\r
+        self._save_perspective(\r
+            perspective, self.gui.config['perspective path'], name=name,\r
+            extension=self.gui.config['perspective extension'])\r
+\r
+    def _on_delete_perspective(self, *args, **kwargs):\r
+        options = sorted([p for p in self._perspectives.keys()\r
+                          if p != 'Default'])\r
+        dialog = SelectionDialog(\r
+            options=options,\r
             message="\nPlease check the perspectives\n\nyou want to delete and click 'Delete'.\n",\r
             button_id=wx.ID_DELETE,\r
-            callbacks={'button': self._on_delete_perspective},\r
             selection_style='multiple',\r
             parent=self,\r
-            label='Delete perspective(s)',\r
+            title='Delete perspective(s)',\r
             style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)\r
         dialog.CenterOnScreen()\r
         dialog.ShowModal()\r
+        names = [options[i] for i in dialog.selected]\r
         dialog.Destroy()\r
-        self._update_perspective_menu()\r
-        # Unfortunately, there is a bug in wxWidgets for win32 (Ticket #3258\r
-        #   http://trac.wxwidgets.org/ticket/3258 \r
-        # ) that makes the radio item indicator in the menu disappear.\r
-        # The code should be fine once this issue is fixed.\r
+        self._delete_perspectives(\r
+            self.gui.config['perspective path'], names=names,\r
+            extension=self.gui.config['perspective extension'])\r
 \r
-    def _on_delete_perspective(self, _class, method, options, selected):\r
-        for p in selected:\r
-            self._perspectives.remove(p)\r
-            if p == self.gui.config['active perspective']:\r
-                self.gui.config['active perspective'] = 'Default'\r
-            path = os.path.join(self.gui.config['perspective path'],\r
-                                p+'.txt')\r
-            remove(path)\r
-        self._update_perspective_menu()\r
+    def _on_select_perspective(self, _class, method, name):\r
+        self._restore_perspective(name)\r
 \r
     def _on_dir_ctrl_left_double_click(self, event):\r
         file_path = self.panelFolders.GetPath()\r
@@ -951,7 +928,7 @@ class HookeFrame (wx.Frame):
         notebook.SetSelection(index)\r
         notebook.DeletePage(notebook.GetSelection())\r
         self.Parent.DeleteFromPlaylists(playlist_name)\r
-        \r
+\r
 \r
 class HookeApp (wx.App):\r
     """A :class:`wx.App` wrapper around :class:`HookeFrame`.\r
@@ -997,7 +974,8 @@ class HookeApp (wx.App):
         return True\r
 \r
     def _setup_splash_screen(self):\r
-        if self.gui.config['show splash screen']:\r
+        if self.gui.config['show splash screen'] == 'True': # HACK: config should decode\r
+            print 'splash', self.gui.config['show splash screen']\r
             path = self.gui.config['splash screen image']\r
             if os.path.isfile(path):\r
                 duration = int(self.gui.config['splash screen duration'])  # HACK: config should decode types\r
@@ -1047,6 +1025,9 @@ class GUI (UserInterface):
             Setting(section=self.setting_section, option='perspective path',\r
                     value=os.path.join('resources', 'gui', 'perspective'),\r
                     help='Directory containing perspective files.'), # TODO: allow colon separated list, like $PATH.\r
+            Setting(section=self.setting_section, option='perspective extension',\r
+                    value='.txt',\r
+                    help='Extension for perspective files.'),\r
             Setting(section=self.setting_section, option='hide extensions',\r
                     value=False,\r
                     help='Hide file extensions when displaying names.'),\r
index 0d037942e7f8592fc9833a6f074c72c159b13557..0cb446e2537a13bb1361e48eb2ec4aa6c2215e7d 100644 (file)
@@ -1,39 +1,4 @@
 # Copyright\r
 \r
-from ....util.pluggable import IsSubclass, construct_graph\r
-\r
-\r
-HANDLER_MODULES = [\r
-    'boolean',\r
-    'float',\r
-#    'int'\r
-#    'point',\r
-    'selection',\r
-    'string'\r
-    ]\r
-"""List of handler modules.  TODO: autodiscovery\r
-"""\r
-\r
-class Handler (object):\r
-    """Base class for :class:`~hooke.interaction.Request` handlers.\r
-    \r
-    :attr:`name` identifies the request type and should match the\r
-    module name.\r
-    """\r
-    def __init__(self, name):\r
-        self.name = name\r
-\r
-    def run(self, hooke_frame, msg):\r
-        raise NotImplemented\r
-\r
-    def _cancel(self, *args, **kwargs):\r
-        # TODO: somehow abort the running command\r
-\r
-\r
-HANDLERS = construct_odict(\r
-    this_modname=__name__,\r
-    submodnames=USER_INTERFACE_MODULES,\r
-    class_selector=IsSubclass(UserInterface, blacklist=[UserInterface]))\r
-""":class:`hooke.compat.odict.odict` of :class:`Handler`\r
-instances keyed by `.name`.\r
+"""A collection of useful popup dialogs for user interaction.\r
 """\r
diff --git a/hooke/ui/gui/dialog/save_file.py b/hooke/ui/gui/dialog/save_file.py
new file mode 100644 (file)
index 0000000..ade99b2
--- /dev/null
@@ -0,0 +1,46 @@
+# Copyright
+
+"""Define :func:`select_save_file`
+"""
+
+import os.path
+
+import wx
+
+
+def select_save_file(directory, name, extension=None, *args, **kwargs):
+    """Get a filename from the user for saving data.
+
+    1) Prompt the user for a name using `name` as the default.
+
+       * If the user cancels, return `None`
+       * If the selected name does not exist, return it.
+
+    2) If the selected name already exists, ask for clobber
+       confirmation.
+
+       * If clobbering is ok, return the selected name.
+       * Otherwise, return to (1).
+    """
+    def path(name):
+        return os.path.join(directory, name+extension)
+    def name_exists(name):
+        os.path.exists(path(name))
+        
+    while True:
+        dialog = wx.TextEntryDialog(*args, **kwargs)
+        dialog.SetValue(name)
+        if dialog.ShowModal() != wx.ID_OK:
+            return  # abort
+        name = dialog.GetValue()    
+        if not name_exists(name):
+            return name
+        dialogConfirm = wx.MessageDialog(
+            parent=self,
+            message='\n\n'.join(
+                ['A file with this name already exists.',
+                 'Do you want to replace it?']),
+                caption='Confirm',
+                style=wx.YES_NO|wx.ICON_QUESTION|wx.CENTER)
+        if dialogConfirm.ShowModal() == wx.ID_YES:
+            return name
index 15baec2cccee822f93ac00d5d37ef4b8769daaff..bc5f18587c366a4dc1c45f2859e51187393d1048 100644 (file)
@@ -4,6 +4,7 @@
 """\r
 \r
 from os import remove\r
+import types\r
 \r
 import wx\r
 \r
@@ -22,11 +23,13 @@ class Selection (wx.Dialog):
     .. _standard wx IDs:\r
       http://docs.wxwidgets.org/stable/wx_stdevtid.html#stdevtid\r
     """\r
-    def __init__(self, options, message, button_id, callbacks,\r
+    def __init__(self, options, message, button_id, callbacks=None,\r
                  default=None, selection_style='single', *args, **kwargs):\r
         super(Selection, self).__init__(*args, **kwargs)\r
 \r
         self._options = options\r
+        if callbacks == None:\r
+            callbacks = {}\r
         self._callbacks = callbacks\r
         self._selection_style = selection_style\r
 \r
@@ -39,46 +42,40 @@ class Selection (wx.Dialog):
         size = wx.Size(175, 200)\r
         if selection_style == 'single':\r
             self._c['listbox'] = wx.ListBox(\r
-                parent=self, size=size, list=options)\r
+                parent=self, size=size, choices=options)\r
             if default != None:\r
                 self._c['listbox'].SetSelection(default)\r
         else:\r
             assert selection_style == 'multiple', selection_style\r
             self._c['listbox'] = wx.CheckListBox(\r
-                parent=self, size=size, list=options)\r
+                parent=self, size=size, choices=options)\r
             if default != None:\r
                 self._c['listbox'].Check(default)\r
         self.Bind(wx.EVT_BUTTON, self.button, self._c['button'])\r
         self.Bind(wx.EVT_BUTTON, self.cancel, self._c['cancel'])\r
 \r
-        border_width = 5\r
-\r
         b = wx.BoxSizer(wx.HORIZONTAL)\r
-        b.Add(window=self._c['button'],\r
-              flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,\r
-              border=border_width)\r
-        b.Add(window=self._c['cancel'],\r
-              flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,\r
-              border=border_width)\r
-\r
+        self._add(b, 'button')\r
+        self._add(b, 'cancel')\r
         v = wx.BoxSizer(wx.VERTICAL)\r
-        v.Add(window=self._c['text'],\r
-              flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,\r
-              border=border_width)\r
-        v.Add(window=self._c['listbox'],\r
-              proportion=1,\r
-              flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,\r
-              border=border_width)\r
-        v.Add(window=wx.StaticLine(\r
+        self._add(v, 'text')\r
+        self._add(v, 'listbox')\r
+        self._add(v, wx.StaticLine(\r
                 parent=self, size=(20,-1), style=wx.LI_HORIZONTAL),\r
-              flag=wx.GROW,\r
-              border=border_width)\r
-        v.Add(window=b,\r
-              flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL,\r
-              border=border_width)\r
+                  flag=wx.GROW)\r
+        self._add(v, b)\r
         self.SetSizer(v)\r
         v.Fit(self)\r
 \r
+    def _add(self, sizer, item,\r
+            flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,\r
+            border=5):\r
+        kwargs = {'flag':flag, 'border':border}\r
+        if isinstance(item, types.StringTypes):\r
+            item = self._c[item]\r
+        kwargs['item'] = item # window\r
+        sizer.Add(**kwargs)\r
+\r
     @callback\r
     def cancel(self, event):\r
         """Close the dialog.\r
@@ -92,6 +89,7 @@ class Selection (wx.Dialog):
             selected = self._c['listbox'].GetSelection()\r
         else:\r
             assert self._selection_style == 'multiple', self._selection_style\r
-            selected = self._c['listbox'].GetChecked())\r
+            selected = self._c['listbox'].GetChecked()\r
+        self.selected = selected\r
         in_callback(self, options=self._options, selected=selected)\r
         self.EndModal(wx.ID_CLOSE)\r
index 320bc436b49497819585d127379519a678943528..c422f9f3627260c07d2e56a63a0c01677aeccc96 100644 (file)
@@ -21,6 +21,7 @@ class Menu (wx.Menu):
     """
     def __init__(self, parent=None, **kwargs):
         self._parent = parent
+        self._bindings = []
         super(Menu, self).__init__(**kwargs)
 
     def Bind(self, **kwargs):
@@ -29,6 +30,24 @@ class Menu (wx.Menu):
         while not isinstance(obj, wx.Frame):
             obj = obj._parent
         obj.Bind(**kwargs)
+        self._bindings.append(kwargs)
+
+    def Unbind(self, **kwargs):
+        assert 'id' in kwargs, kwargs
+        try:
+            self._bindings.remove(kwargs)
+        except ValueError:
+            pass
+        kwargs.pop('handler', None)
+        obj = self
+        while not isinstance(obj, wx.Frame):
+            obj = obj._parent
+        obj.Unbind(**kwargs)
+
+    def _unbind_all_items(self):
+        for kwargs in self._bindings:
+            self.Unbind(**kwargs)
+        self._bindings = []
 
 
 class MenuBar (wx.MenuBar):
@@ -89,24 +108,44 @@ class PerspectiveMenu (Menu):
         self._callbacks = callbacks
         self._c = {}
 
-    def update(self, perspectives, selected, callback):
+    def update(self, perspectives, selected):
         """Rebuild the perspectives menu.
         """
+        self._unbind_all_items()
         for item in self.GetMenuItems():
-            self.UnBind(item.GetId)
             self.DeleteItem(item)
         self._c = {
             'save': self.Append(id=wx.ID_ANY, text='Save Perspective'),
             'delete': self.Append(id=wx.ID_ANY, text='Delete Perspective'),
             }
+        self.Bind(event=wx.EVT_MENU, handler=self.save_perspective,
+                  id=self._c['save'].GetId())
+        self.Bind(event=wx.EVT_MENU, handler=self.delete_perspective,
+                  id=self._c['delete'].GetId())
         self.AppendSeparator()
         for label in perspectives:
             self._c[label] = self.AppendRadioItem(id=wx.ID_ANY, text=label)
-            self.Bind(event=wx.EVT_MENU, handler=callback,
+            self.Bind(event=wx.EVT_MENU, handler=self.select_perspective,
                       id=self._c[label].GetId())
             if label == selected:
                 self._c[label].Check(True)
 
+    @callback
+    def save_perspective(self, event):
+        pass
+
+    @callback
+    def delete_perspective(self, event):
+        pass
+
+    def select_perspective(self, event):
+        _id = event.GetId()
+        item = self.FindItemById(_id)
+        label = item.GetLabel()
+        selected = item.IsChecked()
+        assert selected == True, label
+        in_callback(self, name=label)
+
 
 class HelpMenu (Menu):
     def __init__(self, callbacks=None, **kwargs):