('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
\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
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
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
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
#!/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
'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
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
}\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
# 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
""" # 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
"""\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
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
"""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
"""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