3 """Commands and settings panel for Hooke.
5 This panel handles command generation of
6 :class:`hooke.engine.CommandMessage`\s for all of the commands that
7 don't have panels of their own. Actually it can generate
8 :class:`hooke.engine.CommandMessage`\s for those as well, but the
9 command-specific panel will probably have a nicer interface.
11 # TODO: command arguments.
18 from ....util.callback import callback, in_callback
22 class Tree (wx.TreeCtrl):
23 """The main widget of :class:`CommandsPanel`.
25 `callbacks` is shared with the parent :class:`Commands`.
27 def __init__(self, commands, selected, callbacks, *args, **kwargs):
28 super(Tree, self).__init__(*args, **kwargs)
29 imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)
30 imglist.Add(wx.ArtProvider.GetBitmap(
31 wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))
32 imglist.Add(wx.ArtProvider.GetBitmap(
33 wx.ART_EXECUTABLE_FILE, wx.ART_OTHER, wx.Size(16, 16)))
34 self.AssignImageList(imglist)
42 text='Commands and Settings', image=self.image['root']),
44 self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_selection_changed)
45 self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._on_execute)
46 self.Bind(wx.EVT_MOTION, self._on_motion)
48 self._callbacks = callbacks
49 self._setup_commands(commands, selected)
50 self._last_tooltip = None
52 def _setup_commands(self, commands, selected):
53 self._plugins = {} # {name: hooke.plugin.Plugin()}
54 self._commands = {} # {name: hooke.command.Command()}
56 # In both of the following dicts, command names are
57 # (plugin.name, command.name) to avoid cross-plugin
58 # collisions. See ._is_command().
59 self._id_for_name = {} # {name: id}
60 self._name_for_id = {} # {id: name}
63 plugins = sorted(set([c.plugin for c in commands]),
65 for plugin in plugins:
66 self._plugins[plugin.name] = plugin
67 _id = self.AppendItem(
68 parent=self._c['root'],
70 image=self.image['plugin'])
71 self._id_for_name[plugin.name] = _id
72 self._name_for_id[_id] = plugin.name
73 for command in sorted(commands, key=lambda c:c.name):
74 name = (command.plugin.name, command.name)
75 self._commands[name] = command
76 _id = self.AppendItem(
77 parent=self._id_for_name[command.plugin.name],
79 image=self.image['command'])
80 self._id_for_name[name] = _id
81 self._name_for_id[_id] = name
82 if command.name == selected:
85 #for plugin in self._plugins.values():
86 # self.Expand(self._id_for_name[plugin.name])
87 # make sure the selected command/plugin is visible in the tree
88 if selected is not None:
89 self.SelectItem(selected, True)
90 self.EnsureVisible(selected)
92 def _is_command(self, name): # name from ._id_for_name / ._name_for_id
93 """Return `True` if `name` corresponds to a :class:`hooke.command.Command`.
95 # Plugin names are strings, Command names are tuples.
96 # See ._setup_commands().
97 return not isinstance(name, types.StringTypes)
99 def _canonical_id(self, _id):
100 """Return a canonical form of `_id` suitable for accessing `._name_for_id`.
102 For some reason, `.GetSelection()`, etc. return items that
103 hash differently than the original `.AppendItem()`-returned
104 IDs. This means that `._name_for_id[self.GetSelection()]`
105 will raise `KeyError`, even if there is an id `X` in
106 `._name_for_id` for which `X == self.GetSelection()` will
107 return `True`. This method "canonicalizes" IDs so that the
108 hashing is consistent.
110 for c_id in self._name_for_id.keys():
115 def _on_selection_changed(self, event):
116 active_id = event.GetItem()
117 selected_id = self.GetSelection()
118 name = self._name_for_id[self._canonical_id(selected_id)]
119 if self._is_command(name):
120 self.select_command(self._commands[name])
122 self.select_plugin(self._plugins[name])
124 def _on_execute(self, event):
127 def _on_motion(self, event):
130 hit_id,hit_flags = self.HitTest(event.GetPosition())
132 hit_id = self._canonical_id(hit_id)
138 name = self._name_for_id[hit_id]
139 if self._is_command(name):
140 msg = self._commands[name].help()
142 msg = '' # self._plugins[name].help() TODO: Plugin.help method
143 if msg != self._last_tooltip:
144 self._last_tooltip = msg
145 event.GetEventObject().SetToolTipString(msg)
147 def select_plugin(self, plugin):
148 in_callback(self, plugin)
150 def select_command(self, command):
151 in_callback(self, command)
154 _id = self.GetSelection()
155 name = self._name_for_id[self._canonical_id(_id)]
156 if self._is_command(name):
157 command = self._commands[name]
158 in_callback(self, command)
161 class CommandsPanel (Panel, wx.Panel):
162 """UI for selecting from available commands.
164 `callbacks` is shared with the underlying :class:`Tree`.
166 def __init__(self, callbacks=None, commands=None, selected=None, **kwargs):
167 super(CommandsPanel, self).__init__(
168 name='commands', callbacks=callbacks, **kwargs)
176 size=wx.Size(160, 250),
177 style=wx.TR_DEFAULT_STYLE|wx.NO_BORDER|wx.TR_HIDE_ROOT),
178 'execute': wx.Button(self, label='Execute'),
180 sizer = wx.BoxSizer(wx.VERTICAL)
181 sizer.Add(self._c['execute'], 0, wx.EXPAND)
182 sizer.Add(self._c['tree'], 1, wx.EXPAND)
183 # Put 'tree' second because its min size may be large enough
184 # to push the button out of view.
188 self.Bind(wx.EVT_BUTTON, self._on_execute_button)
190 def _on_execute_button(self, event):
191 self._c['tree'].execute()