-#!/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 types\r
-\r
-import wx\r
-\r
-from ....util.callback import callback, in_callback\r
-from . import Panel\r
-\r
-\r
-class Tree (wx.TreeCtrl):\r
- """The main widget of :class:`CommandsPanel`.\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
- wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))\r
- imglist.Add(wx.ArtProvider.GetBitmap(\r
- wx.ART_EXECUTABLE_FILE, wx.ART_OTHER, wx.Size(16, 16)))\r
- self.AssignImageList(imglist)\r
- self.image = {\r
- 'root': 0,\r
- 'plugin': 0,\r
- 'command': 1,\r
- }\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
- self.Bind(wx.EVT_MOTION, self._on_motion)\r
-\r
- self._callbacks = callbacks\r
- self._setup_commands(commands, selected)\r
- self._last_tooltip = None\r
-\r
- def _setup_commands(self, commands, selected):\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
- selected = None\r
- plugins = sorted(set([c.plugin for c in commands]),\r
- key=lambda p:p.name)\r
- for plugin in plugins:\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
- 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
- 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 = _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
- raise KeyError(_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 _on_motion(self, event):\r
- """Enable tooltips.\r
- """\r
- hit_id,hit_flags = self.HitTest(event.GetPosition())\r
- try:\r
- hit_id = self._canonical_id(hit_id)\r
- except KeyError:\r
- hit_id = None\r
- if hit_id == None:\r
- msg = ''\r
- else:\r
- name = self._name_for_id[hit_id]\r
- if self._is_command(name):\r
- msg = self._commands[name].help()\r
- else:\r
- msg = '' # self._plugins[name].help() TODO: Plugin.help method\r
- if msg != self._last_tooltip:\r
- self._last_tooltip = msg\r
- event.GetEventObject().SetToolTipString(msg)\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
- _id = self.GetSelection()\r
- name = self._name_for_id[self._canonical_id(_id)]\r
- if self._is_command(name):\r
- command = self._commands[name]\r
- in_callback(self, command)\r
-\r
-\r
-class CommandsPanel (Panel, wx.Panel):\r
- """UI for selecting from available commands.\r
-\r
- `callbacks` is shared with the underlying :class:`Tree`.\r
- """\r
- def __init__(self, callbacks=None, commands=None, selected=None, **kwargs):\r
- super(CommandsPanel, self).__init__(\r
- name='commands', callbacks=callbacks, **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
- style=wx.TR_DEFAULT_STYLE|wx.NO_BORDER|wx.TR_HIDE_ROOT),\r
- 'execute': wx.Button(self, label='Execute'),\r
- }\r
- sizer = wx.BoxSizer(wx.VERTICAL)\r
- sizer.Add(self._c['execute'], 0, wx.EXPAND)\r
- sizer.Add(self._c['tree'], 1, wx.EXPAND)\r
- # Put 'tree' second because its min size may be large enough\r
- # to push the button out of view.\r
- self.SetSizer(sizer)\r
- sizer.Fit(self)\r
-\r
- self.Bind(wx.EVT_BUTTON, self._on_execute_button)\r
-\r
- def _on_execute_button(self, event):\r
- self._c['tree'].execute()\r
+#!/usr/bin/env python
+
+"""Commands and settings panel for Hooke.
+
+This panel handles command generation of
+:class:`hooke.engine.CommandMessage`\s for all of the commands that
+don't have panels of their own. Actually it can generate
+:class:`hooke.engine.CommandMessage`\s for those as well, but the
+command-specific panel will probably have a nicer interface.
+
+# TODO: command arguments.
+"""
+
+import types
+
+import wx
+
+from ....util.callback import callback, in_callback
+from . import Panel
+
+
+class Tree (wx.TreeCtrl):
+ """The main widget of :class:`CommandsPanel`.
+
+ `callbacks` is shared with the parent :class:`Commands`.
+ """
+ def __init__(self, commands, selected, callbacks, *args, **kwargs):
+ super(Tree, self).__init__(*args, **kwargs)
+ imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)
+ imglist.Add(wx.ArtProvider.GetBitmap(
+ wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))
+ imglist.Add(wx.ArtProvider.GetBitmap(
+ wx.ART_EXECUTABLE_FILE, wx.ART_OTHER, wx.Size(16, 16)))
+ self.AssignImageList(imglist)
+ self.image = {
+ 'root': 0,
+ 'plugin': 0,
+ 'command': 1,
+ }
+ self._c = {
+ 'root': self.AddRoot(
+ text='Commands and Settings', image=self.image['root']),
+ }
+ self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_selection_changed)
+ self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._on_execute)
+ self.Bind(wx.EVT_MOTION, self._on_motion)
+
+ self._callbacks = callbacks
+ self._setup_commands(commands, selected)
+ self._last_tooltip = None
+
+ def _setup_commands(self, commands, selected):
+ self._plugins = {} # {name: hooke.plugin.Plugin()}
+ self._commands = {} # {name: hooke.command.Command()}
+
+ # In both of the following dicts, command names are
+ # (plugin.name, command.name) to avoid cross-plugin
+ # collisions. See ._is_command().
+ self._id_for_name = {} # {name: id}
+ self._name_for_id = {} # {id: name}
+
+ selected = None
+ plugins = sorted(set([c.plugin for c in commands]),
+ key=lambda p:p.name)
+ for plugin in plugins:
+ self._plugins[plugin.name] = plugin
+ _id = self.AppendItem(
+ parent=self._c['root'],
+ text=plugin.name,
+ image=self.image['plugin'])
+ self._id_for_name[plugin.name] = _id
+ self._name_for_id[_id] = plugin.name
+ for command in sorted(commands, key=lambda c:c.name):
+ name = (command.plugin.name, command.name)
+ self._commands[name] = command
+ _id = self.AppendItem(
+ parent=self._id_for_name[command.plugin.name],
+ text=command.name,
+ image=self.image['command'])
+ self._id_for_name[name] = _id
+ self._name_for_id[_id] = name
+ if command.name == selected:
+ selected = _id
+
+ #for plugin in self._plugins.values():
+ # self.Expand(self._id_for_name[plugin.name])
+ # make sure the selected command/plugin is visible in the tree
+ if selected is not None:
+ self.SelectItem(selected, True)
+ self.EnsureVisible(selected)
+
+ def _is_command(self, name): # name from ._id_for_name / ._name_for_id
+ """Return `True` if `name` corresponds to a :class:`hooke.command.Command`.
+ """
+ # Plugin names are strings, Command names are tuples.
+ # See ._setup_commands().
+ return not isinstance(name, types.StringTypes)
+
+ def _canonical_id(self, _id):
+ """Return a canonical form of `_id` suitable for accessing `._name_for_id`.
+
+ For some reason, `.GetSelection()`, etc. return items that
+ hash differently than the original `.AppendItem()`-returned
+ IDs. This means that `._name_for_id[self.GetSelection()]`
+ will raise `KeyError`, even if there is an id `X` in
+ `._name_for_id` for which `X == self.GetSelection()` will
+ return `True`. This method "canonicalizes" IDs so that the
+ hashing is consistent.
+ """
+ for c_id in self._name_for_id.keys():
+ if c_id == _id:
+ return c_id
+ raise KeyError(_id)
+
+ def _on_selection_changed(self, event):
+ active_id = event.GetItem()
+ selected_id = self.GetSelection()
+ name = self._name_for_id[self._canonical_id(selected_id)]
+ if self._is_command(name):
+ self.select_command(self._commands[name])
+ else:
+ self.select_plugin(self._plugins[name])
+
+ def _on_execute(self, event):
+ self.execute()
+
+ def _on_motion(self, event):
+ """Enable tooltips.
+ """
+ hit_id,hit_flags = self.HitTest(event.GetPosition())
+ try:
+ hit_id = self._canonical_id(hit_id)
+ except KeyError:
+ hit_id = None
+ if hit_id == None:
+ msg = ''
+ else:
+ name = self._name_for_id[hit_id]
+ if self._is_command(name):
+ msg = self._commands[name].help()
+ else:
+ msg = '' # self._plugins[name].help() TODO: Plugin.help method
+ if msg != self._last_tooltip:
+ self._last_tooltip = msg
+ event.GetEventObject().SetToolTipString(msg)
+
+ def select_plugin(self, plugin):
+ in_callback(self, plugin)
+
+ def select_command(self, command):
+ in_callback(self, command)
+
+ def execute(self):
+ _id = self.GetSelection()
+ name = self._name_for_id[self._canonical_id(_id)]
+ if self._is_command(name):
+ command = self._commands[name]
+ in_callback(self, command)
+
+
+class CommandsPanel (Panel, wx.Panel):
+ """UI for selecting from available commands.
+
+ `callbacks` is shared with the underlying :class:`Tree`.
+ """
+ def __init__(self, callbacks=None, commands=None, selected=None, **kwargs):
+ super(CommandsPanel, self).__init__(
+ name='commands', callbacks=callbacks, **kwargs)
+ self._c = {
+ 'tree': Tree(
+ commands=commands,
+ selected=selected,
+ callbacks=callbacks,
+ parent=self,
+ pos=wx.Point(0, 0),
+ size=wx.Size(160, 250),
+ style=wx.TR_DEFAULT_STYLE|wx.NO_BORDER|wx.TR_HIDE_ROOT),
+ 'execute': wx.Button(self, label='Execute'),
+ }
+ sizer = wx.BoxSizer(wx.VERTICAL)
+ sizer.Add(self._c['execute'], 0, wx.EXPAND)
+ sizer.Add(self._c['tree'], 1, wx.EXPAND)
+ # Put 'tree' second because its min size may be large enough
+ # to push the button out of view.
+ self.SetSizer(sizer)
+ sizer.Fit(self)
+
+ self.Bind(wx.EVT_BUTTON, self._on_execute_button)
+
+ def _on_execute_button(self, event):
+ self._c['tree'].execute()