Rearranged gui.panel.commands
authorW. Trevor King <wking@drexel.edu>
Mon, 26 Jul 2010 19:06:27 +0000 (15:06 -0400)
committerW. Trevor King <wking@drexel.edu>
Mon, 26 Jul 2010 19:06:27 +0000 (15:06 -0400)
hooke/ui/gui/__init__.py
hooke/ui/gui/panel/commands.py
hooke/ui/gui/panel/playlist.py

index c70a0c738ffd748d639bd840fa7f60be23062162..720c364befaee1ef2c5d464cebe3b77f4ef138af 100644 (file)
@@ -243,6 +243,12 @@ class HookeFrame (wx.Frame):
             ('commands', panel.commands.Commands(\r
                     commands=self.commands,\r
                     selected=self.gui.config['selected command'],\r
+                    callbacks={\r
+                        'execute': self.execute_command,\r
+                        'select_plugin': self.select_plugin,\r
+                        'select_command': self.select_command,\r
+#                        'selection_changed': self.panelProperties.select(self, method, command),  #SelectedTreeItem = selected_item,\r
+                        },\r
                     parent=self,\r
                     style=wx.WANTS_CHARS|wx.NO_BORDER,\r
                     # WANTS_CHARS so the panel doesn't eat the Return key.\r
@@ -315,10 +321,6 @@ class HookeFrame (wx.Frame):
         \r
         # TODO: playlist callbacks\r
         return # TODO: cleanup\r
-        #commands tree\r
-        evtmgr.eventManager.Register(self.OnExecute, wx.EVT_BUTTON, self._c['commands'].ExecuteButton)\r
-        evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self._c['commands']._c['tree'])\r
-        evtmgr.eventManager.Register(self.OnTreeCtrlItemActivated, wx.EVT_TREE_ITEM_ACTIVATED, self._c['commands']._c['tree'])\r
         evtmgr.eventManager.Register(self.OnUpdateNote, wx.EVT_BUTTON, self.panelNote.UpdateButton)\r
         #property editor\r
         self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged)\r
@@ -362,6 +364,46 @@ class HookeFrame (wx.Frame):
         perspectivesFile.write(perspective)\r
         perspectivesFile.close()\r
 \r
+    def execute_command(self, _class, method, command, args):\r
+        self.cmd.inqueue.put(CommandMessage(command, args))\r
+        while True:\r
+            msg = self.cmd.outqueue.get()\r
+            if isinstance(msg, Exit):\r
+                return True\r
+            elif isinstance(msg, CommandExit):\r
+                self.cmd.stdout.write(msg.__class__.__name__+'\n')\r
+                self.cmd.stdout.write(str(msg).rstrip()+'\n')\r
+                break\r
+            elif isinstance(msg, ReloadUserInterfaceConfig):\r
+                self.cmd.ui.reload_config(msg.config)\r
+                continue\r
+            elif isinstance(msg, Request):\r
+                self._handle_request(msg)\r
+                continue\r
+            self.cmd.stdout.write(str(msg).rstrip()+'\n')\r
+                #TODO: run the command\r
+                #command = ''.join(['self.do_', item_text, '()'])\r
+                #self.AppendToOutput(command + '\n')\r
+                #exec(command)\r
+\r
+    def select_plugin(self, _class, method, plugin):\r
+        for option in config[section]:\r
+            properties.append([option, config[section][option]])\r
+\r
+    def select_command(self, _class, method, command):\r
+        self.select_plugin(command.plugin)\r
+        plugin = self.GetItemText(selected_item)\r
+        if plugin != 'core':\r
+            doc_string = eval('plugins.' + plugin + '.' + plugin + 'Commands.__doc__')\r
+        else:\r
+            doc_string = 'The module "core" contains Hooke core functionality'\r
+        if doc_string is not None:\r
+            self.panelAssistant.ChangeValue(doc_string)\r
+        else:\r
+            self.panelAssistant.ChangeValue('')\r
+        panel.propertyeditor.PropertyEditor.Initialize(self.panelProperties, properties)\r
+        self.gui.config['selected command'] = command\r
+\r
     def AddPlaylistFromFiles(self, files=[], name='Untitled'):\r
         if files:\r
             playlist = lib.playlist.Playlist(self, self.drivers)\r
@@ -618,15 +660,6 @@ class HookeFrame (wx.Frame):
     def _on_erase_background(self, event):\r
         event.Skip()\r
 \r
-    def OnExecute(self, event):\r
-        item = self._c['commands']._c['tree'].GetSelection()\r
-        if item.IsOk():\r
-            if not self._c['commands']._c['tree'].ItemHasChildren(item):\r
-                item_text = self._c['commands']._c['tree'].GetItemText(item)\r
-                command = ''.join(['self.do_', item_text, '()'])\r
-                #self.AppendToOutput(command + '\n')\r
-                exec(command)\r
-\r
     def OnExit(self, event):\r
         self.Close()\r
 \r
@@ -718,48 +751,6 @@ class HookeFrame (wx.Frame):
     def _on_size(self, event):\r
         event.Skip()\r
 \r
-    def OnTreeCtrlCommandsSelectionChanged(self, event):\r
-        selected_item = event.GetItem()\r
-        if selected_item is not None:\r
-            plugin = ''\r
-            section = ''\r
-            #deregister/register the listener to avoid infinite loop\r
-            evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlCommandsSelectionChanged)\r
-            self._c['commands']._c['tree'].SelectItem(selected_item)\r
-            evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self._c['commands']._c['tree'])\r
-            self.panelProperties.SelectedTreeItem = selected_item\r
-            #if a command was clicked\r
-            properties = []\r
-            if not self._c['commands']._c['tree'].ItemHasChildren(selected_item):\r
-                item_plugin = self._c['commands']._c['tree'].GetItemParent(selected_item)\r
-                plugin = self._c['commands']._c['tree'].GetItemText(item_plugin)\r
-                if self.gui.config.has_key(plugin):\r
-                    #config = self._c['commands']._c['tree'].GetPyData(item_plugin)\r
-                    config = self.gui.config[plugin]\r
-                    section = self._c['commands']._c['tree'].GetItemText(selected_item)\r
-                    #display docstring in help window\r
-                    doc_string = eval('self.do_' + section + '.__doc__')\r
-                    if section in config:\r
-                        for option in config[section]:\r
-                            properties.append([option, config[section][option]])\r
-            else:\r
-                plugin = self._c['commands']._c['tree'].GetItemText(selected_item)\r
-                if plugin != 'core':\r
-                    doc_string = eval('plugins.' + plugin + '.' + plugin + 'Commands.__doc__')\r
-                else:\r
-                    doc_string = 'The module "core" contains Hooke core functionality'\r
-            if doc_string is not None:\r
-                self.panelAssistant.ChangeValue(doc_string)\r
-            else:\r
-                self.panelAssistant.ChangeValue('')\r
-            panel.propertyeditor.PropertyEditor.Initialize(self.panelProperties, properties)\r
-            #save the currently selected command/plugin to the config file\r
-            self.gui.config['command']['command'] = section\r
-            self.gui.config['command']['plugin'] = plugin\r
-\r
-    def OnTreeCtrlItemActivated(self, event):\r
-        self.OnExecute(event)\r
-\r
     def OnUpdateNote(self, event):\r
         '''\r
         Saves the note to the active file.\r
index d26d5b0cd97536176305cdb4a56394f40ac2498f..1d8313ae1812bb694e9f227d3c9c092910bf5678 100644 (file)
@@ -1,15 +1,29 @@
 #!/usr/bin/env python\r
 \r
 """Commands and settings panel for Hooke.\r
+\r
+This panel handles command generation of\r
+:class:`hooke.ui.CommandMessage`\s for all of the commands that don't\r
+have panels of their own.  Actually it can generate\r
+:class:`hooke.ui.CommandMessage`\s for those as well, but the\r
+command-specific panel will probably have a nicer interface.\r
+\r
+# TODO: command arguments.\r
 """\r
 \r
-import os.path\r
+import types\r
 \r
 import wx\r
 \r
+from ....util.callback import callback, in_callback\r
+\r
 \r
 class Tree (wx.TreeCtrl):\r
-    def __init__(self, commands, selected, *args, **kwargs):\r
+    """\r
+\r
+    `callbacks` is shared with the parent :class:`Commands`.\r
+    """\r
+    def __init__(self, commands, selected, callbacks, *args, **kwargs):\r
         super(Tree, self).__init__(*args, **kwargs)\r
         imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)\r
         imglist.Add(wx.ArtProvider.GetBitmap(\r
@@ -22,47 +36,118 @@ class Tree (wx.TreeCtrl):
             'plugin': 0,\r
             'command': 1,\r
             }\r
-        self.AddRoot(text='Commands and Settings', image=self.image['root'])\r
+        self._c = {\r
+            'root': self.AddRoot(\r
+                text='Commands and Settings', image=self.image['root']),\r
+            }\r
+        self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_selection_changed)\r
+        self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._on_execute)\r
 \r
+        self._callbacks = callbacks\r
         self._setup_commands(commands, selected)\r
 \r
     def _setup_commands(self, commands, selected):\r
-        self._commands = commands\r
-        selected = None\r
-        tree_root = self.GetRootItem()\r
+        self._plugins = {}    # {name: hooke.plugin.Plugin()}\r
+        self._commands = {}   # {name: hooke.command.Command()}\r
+\r
+        # In both of the following dicts, command names are\r
+        # (plugin.name, command.name) to avoid cross-plugin\r
+        # collisions.  See ._is_command().\r
+        self._id_for_name = {}  # {name: id}\r
+        self._name_for_id = {}  # {id: name}\r
 \r
-        plugin_roots = {}\r
+        selected = None\r
         plugins = sorted(set([c.plugin for c in commands]),\r
                          key=lambda p:p.name)\r
         for plugin in plugins:\r
-            plugin_roots[plugin.name] = self.AppendItem(\r
-                parent=tree_root,\r
+            self._plugins[plugin.name] = plugin\r
+            _id = self.AppendItem(\r
+                parent=self._c['root'],\r
                 text=plugin.name,\r
-                image=self.image['plugin'],\r
-                data=wx.TreeItemData(plugin))\r
-\r
+                image=self.image['plugin'])\r
+            self._id_for_name[plugin.name] = _id\r
+            self._name_for_id[_id] = plugin.name\r
         for command in sorted(commands, key=lambda c:c.name):\r
-            item = self.AppendItem(\r
-                parent=plugin_roots[command.plugin.name],\r
+            name = (command.plugin.name, command.name)\r
+            self._commands[name] = command\r
+            _id = self.AppendItem(\r
+                parent=self._id_for_name[command.plugin.name],\r
                 text=command.name,\r
                 image=self.image['command'])\r
+            self._id_for_name[name] = _id\r
+            self._name_for_id[_id] = name\r
             if command.name == selected:\r
-                selected = item\r
-        for key,value in plugin_roots.items():\r
-            self.Expand(value)\r
-        #make sure the selected command/plugin is visible in the tree\r
+                selected = _id\r
+\r
+        for plugin in self._plugins.values():\r
+            self.Expand(self._id_for_name[plugin.name])\r
+        # make sure the selected command/plugin is visible in the tree\r
         if selected is not None:\r
             self.SelectItem(selected, True)\r
             self.EnsureVisible(selected)\r
 \r
+    def _is_command(self, name):  # name from ._id_for_name / ._name_for_id\r
+        """Return `True` if `name` corresponds to a :class:`hooke.command.Command`.\r
+        """\r
+        # Plugin names are strings, Command names are tuples.\r
+        # See ._setup_commands().\r
+        return not isinstance(name, types.StringTypes)\r
+\r
+    def _canonical_id(self, _id):\r
+        """Return a canonical form of `_id` suitable for accessing `._name_for_id`.\r
+\r
+        For some reason, `.GetSelection()`, etc. return items that\r
+        hash differently than the original `.AppendItem()`-returned\r
+        IDs.  This means that `._name_for_id[self.GetSelection()]`\r
+        will raise `KeyError`, even if there is an id `X` in\r
+        `._name_for_id` for which `X == self.GetSelection()` will\r
+        return `True`.  This method "canonicalizes" IDs so that the\r
+        hashing is consistent.\r
+        """\r
+        for c_id in self._name_for_id.keys():\r
+            if c_id == _id:\r
+                return c_id\r
+        return _id\r
+\r
+    def _on_selection_changed(self, event):\r
+        active_id = event.GetItem()\r
+        selected_id = self.GetSelection()\r
+        name = self._name_for_id[self._canonical_id(selected_id)]\r
+        if self._is_command(name):\r
+            self.select_command(self._commands[name])\r
+        else:\r
+            self.select_plugin(self._plugins[name])\r
+\r
+    def _on_execute(self, event):\r
+        self.execute()\r
+\r
+    def select_plugin(self, plugin):\r
+        in_callback(self, plugin)\r
+\r
+    def select_command(self, command):\r
+        in_callback(self, command)\r
+\r
+    def execute(self):\r
+        item = self.GetSelection()\r
+        if item.IsOk():\r
+            if not self.ItemHasChildren(item):\r
+                item_text = self.GetItemText(item)\r
+                # TODO: generate args\r
+                in_callback(self, command, args)\r
+\r
 \r
 class Commands (wx.Panel):\r
-    def __init__(self, commands, selected, *args, **kwargs):\r
+    """\r
+\r
+    `callbacks` is shared with the underlying :class:`Tree`.\r
+    """\r
+    def __init__(self, commands, selected, callbacks, *args, **kwargs):\r
         super(Commands, self).__init__(*args, **kwargs)\r
         self._c = {\r
             'tree': Tree(\r
                 commands=commands,\r
                 selected=selected,\r
+                callbacks=callbacks,\r
                 parent=self,\r
                 pos=wx.Point(0, 0),\r
                 size=wx.Size(160, 250),\r
@@ -74,3 +159,9 @@ class Commands (wx.Panel):
         sizer.Add(self._c['tree'], 1, wx.EXPAND)\r
         self.SetSizer(sizer)\r
         sizer.Fit(self)\r
+\r
+        self.Bind(wx.EVT_BUTTON, self._on_execute_button)\r
+        self._callbacks = callbacks\r
+\r
+    def _on_execute_button(self, event):\r
+        self._c['tree'].execute()\r
index 2982744990dce7c182b6ec7255c1f5c09bcf227f..524b0068a255bcaf2c8e73c478443e3d661b7e14 100644 (file)
@@ -42,7 +42,7 @@ class Tree (wx.TreeCtrl):
             }\r
         self._c = {\r
             'menu': Menu(self._on_delete),\r
-            'root': self.AddRoot('Playlists', self.image['root'])\r
+            'root': self.AddRoot(text='Playlists', image=self.image['root'])\r
             }\r
         self.Bind(wx.EVT_RIGHT_DOWN, self._on_context_menu)\r
         self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_curve_select)\r
@@ -76,13 +76,29 @@ class Tree (wx.TreeCtrl):
         # See ._setup_playlists().\r
         return not isinstance(name, types.StringTypes)\r
 \r
+    def _canonical_id(self, _id):\r
+        """Return a canonical form of `_id` suitable for accessing `._name_for_id`.\r
+\r
+        For some reason, `.GetSelection()`, etc. return items that\r
+        hash differently than the original `.AppendItem()`-returned\r
+        IDs.  This means that `._name_for_id[self.GetSelection()]`\r
+        will raise `KeyError`, even if there is an id `X` in\r
+        `._name_for_id` for which `X == self.GetSelection()` will\r
+        return `True`.  This method "canonicalizes" IDs so that the\r
+        hashing is consistent.\r
+        """\r
+        for c_id in self._name_for_id.keys():\r
+            if c_id == _id:\r
+                return c_id\r
+        return _id\r
+\r
     def _on_curve_select(self, event):\r
         """Act on playlist/curve selection.\r
 \r
         Currently just a hook for a potential callback.\r
         """\r
         _id = self.GetSelection()\r
-        name = self._name_for_id(_id)\r
+        name = self._name_for_id[self._canonical_id(_id)]\r
         if self._is_curve(name):\r
             playlist = self._playlists[name[0]]\r
             curve = playlist.current()\r
@@ -93,7 +109,7 @@ class Tree (wx.TreeCtrl):
         """ # TODO: dup with _on_curve_select?\r
         hit_id, hit_flags = self.HitTest(event.GetPosition())\r
         if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0:\r
-            name = self._name_for_id[hit_id]\r
+            name = self._name_for_id[self._canonical_id(hit_id)]\r
             if self._is_curve(name):\r
                 self.set_selected_curve(name[0], name[1])\r
             else:\r
@@ -109,7 +125,7 @@ class Tree (wx.TreeCtrl):
         """\r
         hit_id,hit_flags = self.HitTest(event.GetPosition())\r
         if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0:\r
-            self._hit_id = hit_id  # store for the callbacks\r
+            self._hit_id = self._canonical_id(hit_id)  # store for callbacks\r
             self.PopupMenu(\r
                 Menu(self._on_delete), event.GetPoint())\r
             menu.Destroy()\r
@@ -120,8 +136,8 @@ class Tree (wx.TreeCtrl):
         Determines the clicked item and calls the appropriate\r
         `.delete_*()` method on it.\r
         """\r
-        if hasattr(self, '_hit_id'):  # called via ._c['menu']\r
-            _id = self._hit_id\r
+        #if hasattr(self, '_hit_id'):  # called via ._c['menu']\r
+        _id = self._hit_id\r
         del(self._hit_id)\r
         name = self._name_for_id[_id]\r
         if self._is_curve(name):\r
@@ -200,7 +216,7 @@ class Tree (wx.TreeCtrl):
         """Return the selected :class:`hooke.playlist.Playlist`.\r
         """\r
         _id = self.GetSelection()\r
-        name = self._name_for_id(_id)\r
+        name = self._name_for_id[self._canonical_id(_id)]\r
         if self._is_curve(name):\r
             name = name[0]\r
         return self._playlists[name]\r
@@ -209,7 +225,7 @@ class Tree (wx.TreeCtrl):
         """Return the selected :class:`hooke.curve.Curve`.\r
         """\r
         _id = self.GetSelection()\r
-        name = self._name_for_id(_id)\r
+        name = self._name_for_id[self._canonical_id(_id)]\r
         if self._is_curve(name):\r
             p_name,c_name = name\r
             playlist = self._playlists[p_name]\r