From 276ac1db87826edb80d09d93d4114fef6d17c2db Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 26 Jul 2010 15:06:27 -0400 Subject: [PATCH] Rearranged gui.panel.commands --- hooke/ui/gui/__init__.py | 101 ++++++++++++-------------- hooke/ui/gui/panel/commands.py | 129 ++++++++++++++++++++++++++++----- hooke/ui/gui/panel/playlist.py | 32 ++++++-- 3 files changed, 180 insertions(+), 82 deletions(-) diff --git a/hooke/ui/gui/__init__.py b/hooke/ui/gui/__init__.py index c70a0c7..720c364 100644 --- a/hooke/ui/gui/__init__.py +++ b/hooke/ui/gui/__init__.py @@ -243,6 +243,12 @@ class HookeFrame (wx.Frame): ('commands', panel.commands.Commands( commands=self.commands, selected=self.gui.config['selected command'], + callbacks={ + 'execute': self.execute_command, + 'select_plugin': self.select_plugin, + 'select_command': self.select_command, +# 'selection_changed': self.panelProperties.select(self, method, command), #SelectedTreeItem = selected_item, + }, parent=self, style=wx.WANTS_CHARS|wx.NO_BORDER, # WANTS_CHARS so the panel doesn't eat the Return key. @@ -315,10 +321,6 @@ class HookeFrame (wx.Frame): # TODO: playlist callbacks return # TODO: cleanup - #commands tree - evtmgr.eventManager.Register(self.OnExecute, wx.EVT_BUTTON, self._c['commands'].ExecuteButton) - evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self._c['commands']._c['tree']) - evtmgr.eventManager.Register(self.OnTreeCtrlItemActivated, wx.EVT_TREE_ITEM_ACTIVATED, self._c['commands']._c['tree']) evtmgr.eventManager.Register(self.OnUpdateNote, wx.EVT_BUTTON, self.panelNote.UpdateButton) #property editor self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged) @@ -362,6 +364,46 @@ class HookeFrame (wx.Frame): perspectivesFile.write(perspective) perspectivesFile.close() + def execute_command(self, _class, method, command, args): + self.cmd.inqueue.put(CommandMessage(command, args)) + while True: + msg = self.cmd.outqueue.get() + if isinstance(msg, Exit): + return True + elif isinstance(msg, CommandExit): + self.cmd.stdout.write(msg.__class__.__name__+'\n') + self.cmd.stdout.write(str(msg).rstrip()+'\n') + break + elif isinstance(msg, ReloadUserInterfaceConfig): + self.cmd.ui.reload_config(msg.config) + continue + elif isinstance(msg, Request): + self._handle_request(msg) + continue + self.cmd.stdout.write(str(msg).rstrip()+'\n') + #TODO: run the command + #command = ''.join(['self.do_', item_text, '()']) + #self.AppendToOutput(command + '\n') + #exec(command) + + def select_plugin(self, _class, method, plugin): + for option in config[section]: + properties.append([option, config[section][option]]) + + def select_command(self, _class, method, command): + self.select_plugin(command.plugin) + plugin = self.GetItemText(selected_item) + if plugin != 'core': + doc_string = eval('plugins.' + plugin + '.' + plugin + 'Commands.__doc__') + else: + doc_string = 'The module "core" contains Hooke core functionality' + if doc_string is not None: + self.panelAssistant.ChangeValue(doc_string) + else: + self.panelAssistant.ChangeValue('') + panel.propertyeditor.PropertyEditor.Initialize(self.panelProperties, properties) + self.gui.config['selected command'] = command + def AddPlaylistFromFiles(self, files=[], name='Untitled'): if files: playlist = lib.playlist.Playlist(self, self.drivers) @@ -618,15 +660,6 @@ class HookeFrame (wx.Frame): def _on_erase_background(self, event): event.Skip() - def OnExecute(self, event): - item = self._c['commands']._c['tree'].GetSelection() - if item.IsOk(): - if not self._c['commands']._c['tree'].ItemHasChildren(item): - item_text = self._c['commands']._c['tree'].GetItemText(item) - command = ''.join(['self.do_', item_text, '()']) - #self.AppendToOutput(command + '\n') - exec(command) - def OnExit(self, event): self.Close() @@ -718,48 +751,6 @@ class HookeFrame (wx.Frame): def _on_size(self, event): event.Skip() - def OnTreeCtrlCommandsSelectionChanged(self, event): - selected_item = event.GetItem() - if selected_item is not None: - plugin = '' - section = '' - #deregister/register the listener to avoid infinite loop - evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlCommandsSelectionChanged) - self._c['commands']._c['tree'].SelectItem(selected_item) - evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self._c['commands']._c['tree']) - self.panelProperties.SelectedTreeItem = selected_item - #if a command was clicked - properties = [] - if not self._c['commands']._c['tree'].ItemHasChildren(selected_item): - item_plugin = self._c['commands']._c['tree'].GetItemParent(selected_item) - plugin = self._c['commands']._c['tree'].GetItemText(item_plugin) - if self.gui.config.has_key(plugin): - #config = self._c['commands']._c['tree'].GetPyData(item_plugin) - config = self.gui.config[plugin] - section = self._c['commands']._c['tree'].GetItemText(selected_item) - #display docstring in help window - doc_string = eval('self.do_' + section + '.__doc__') - if section in config: - for option in config[section]: - properties.append([option, config[section][option]]) - else: - plugin = self._c['commands']._c['tree'].GetItemText(selected_item) - if plugin != 'core': - doc_string = eval('plugins.' + plugin + '.' + plugin + 'Commands.__doc__') - else: - doc_string = 'The module "core" contains Hooke core functionality' - if doc_string is not None: - self.panelAssistant.ChangeValue(doc_string) - else: - self.panelAssistant.ChangeValue('') - panel.propertyeditor.PropertyEditor.Initialize(self.panelProperties, properties) - #save the currently selected command/plugin to the config file - self.gui.config['command']['command'] = section - self.gui.config['command']['plugin'] = plugin - - def OnTreeCtrlItemActivated(self, event): - self.OnExecute(event) - def OnUpdateNote(self, event): ''' Saves the note to the active file. diff --git a/hooke/ui/gui/panel/commands.py b/hooke/ui/gui/panel/commands.py index d26d5b0..1d8313a 100644 --- a/hooke/ui/gui/panel/commands.py +++ b/hooke/ui/gui/panel/commands.py @@ -1,15 +1,29 @@ #!/usr/bin/env python """Commands and settings panel for Hooke. + +This panel handles command generation of +:class:`hooke.ui.CommandMessage`\s for all of the commands that don't +have panels of their own. Actually it can generate +:class:`hooke.ui.CommandMessage`\s for those as well, but the +command-specific panel will probably have a nicer interface. + +# TODO: command arguments. """ -import os.path +import types import wx +from ....util.callback import callback, in_callback + class Tree (wx.TreeCtrl): - def __init__(self, commands, selected, *args, **kwargs): + """ + + `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( @@ -22,47 +36,118 @@ class Tree (wx.TreeCtrl): 'plugin': 0, 'command': 1, } - self.AddRoot(text='Commands and Settings', image=self.image['root']) + 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._callbacks = callbacks self._setup_commands(commands, selected) def _setup_commands(self, commands, selected): - self._commands = commands - selected = None - tree_root = self.GetRootItem() + 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} - plugin_roots = {} + selected = None plugins = sorted(set([c.plugin for c in commands]), key=lambda p:p.name) for plugin in plugins: - plugin_roots[plugin.name] = self.AppendItem( - parent=tree_root, + self._plugins[plugin.name] = plugin + _id = self.AppendItem( + parent=self._c['root'], text=plugin.name, - image=self.image['plugin'], - data=wx.TreeItemData(plugin)) - + 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): - item = self.AppendItem( - parent=plugin_roots[command.plugin.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 = item - for key,value in plugin_roots.items(): - self.Expand(value) - #make sure the selected command/plugin is visible in the tree + 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 + return _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 select_plugin(self, plugin): + in_callback(self, plugin) + + def select_command(self, command): + in_callback(self, command) + + def execute(self): + item = self.GetSelection() + if item.IsOk(): + if not self.ItemHasChildren(item): + item_text = self.GetItemText(item) + # TODO: generate args + in_callback(self, command, args) + class Commands (wx.Panel): - def __init__(self, commands, selected, *args, **kwargs): + """ + + `callbacks` is shared with the underlying :class:`Tree`. + """ + def __init__(self, commands, selected, callbacks, *args, **kwargs): super(Commands, self).__init__(*args, **kwargs) self._c = { 'tree': Tree( commands=commands, selected=selected, + callbacks=callbacks, parent=self, pos=wx.Point(0, 0), size=wx.Size(160, 250), @@ -74,3 +159,9 @@ class Commands (wx.Panel): sizer.Add(self._c['tree'], 1, wx.EXPAND) self.SetSizer(sizer) sizer.Fit(self) + + self.Bind(wx.EVT_BUTTON, self._on_execute_button) + self._callbacks = callbacks + + def _on_execute_button(self, event): + self._c['tree'].execute() diff --git a/hooke/ui/gui/panel/playlist.py b/hooke/ui/gui/panel/playlist.py index 2982744..524b006 100644 --- a/hooke/ui/gui/panel/playlist.py +++ b/hooke/ui/gui/panel/playlist.py @@ -42,7 +42,7 @@ class Tree (wx.TreeCtrl): } self._c = { 'menu': Menu(self._on_delete), - 'root': self.AddRoot('Playlists', self.image['root']) + 'root': self.AddRoot(text='Playlists', image=self.image['root']) } self.Bind(wx.EVT_RIGHT_DOWN, self._on_context_menu) self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_curve_select) @@ -76,13 +76,29 @@ class Tree (wx.TreeCtrl): # See ._setup_playlists(). 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 + return _id + def _on_curve_select(self, event): """Act on playlist/curve selection. Currently just a hook for a potential callback. """ _id = self.GetSelection() - name = self._name_for_id(_id) + name = self._name_for_id[self._canonical_id(_id)] if self._is_curve(name): playlist = self._playlists[name[0]] curve = playlist.current() @@ -93,7 +109,7 @@ class Tree (wx.TreeCtrl): """ # TODO: dup with _on_curve_select? hit_id, hit_flags = self.HitTest(event.GetPosition()) if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0: - name = self._name_for_id[hit_id] + name = self._name_for_id[self._canonical_id(hit_id)] if self._is_curve(name): self.set_selected_curve(name[0], name[1]) else: @@ -109,7 +125,7 @@ class Tree (wx.TreeCtrl): """ hit_id,hit_flags = self.HitTest(event.GetPosition()) if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0: - self._hit_id = hit_id # store for the callbacks + self._hit_id = self._canonical_id(hit_id) # store for callbacks self.PopupMenu( Menu(self._on_delete), event.GetPoint()) menu.Destroy() @@ -120,8 +136,8 @@ class Tree (wx.TreeCtrl): Determines the clicked item and calls the appropriate `.delete_*()` method on it. """ - if hasattr(self, '_hit_id'): # called via ._c['menu'] - _id = self._hit_id + #if hasattr(self, '_hit_id'): # called via ._c['menu'] + _id = self._hit_id del(self._hit_id) name = self._name_for_id[_id] if self._is_curve(name): @@ -200,7 +216,7 @@ class Tree (wx.TreeCtrl): """Return the selected :class:`hooke.playlist.Playlist`. """ _id = self.GetSelection() - name = self._name_for_id(_id) + name = self._name_for_id[self._canonical_id(_id)] if self._is_curve(name): name = name[0] return self._playlists[name] @@ -209,7 +225,7 @@ class Tree (wx.TreeCtrl): """Return the selected :class:`hooke.curve.Curve`. """ _id = self.GetSelection() - name = self._name_for_id(_id) + name = self._name_for_id[self._canonical_id(_id)] if self._is_curve(name): p_name,c_name = name playlist = self._playlists[p_name] -- 2.26.2