1 #!/usr/bin/env python
\r
3 """Commands and settings panel for Hooke.
\r
5 This panel handles command generation of
\r
6 :class:`hooke.ui.CommandMessage`\s for all of the commands that don't
\r
7 have panels of their own. Actually it can generate
\r
8 :class:`hooke.ui.CommandMessage`\s for those as well, but the
\r
9 command-specific panel will probably have a nicer interface.
\r
11 # TODO: command arguments.
\r
18 from ....util.callback import callback, in_callback
\r
22 class Tree (wx.TreeCtrl):
\r
25 `callbacks` is shared with the parent :class:`Commands`.
\r
27 def __init__(self, commands, selected, callbacks, *args, **kwargs):
\r
28 super(Tree, self).__init__(*args, **kwargs)
\r
29 imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)
\r
30 imglist.Add(wx.ArtProvider.GetBitmap(
\r
31 wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))
\r
32 imglist.Add(wx.ArtProvider.GetBitmap(
\r
33 wx.ART_EXECUTABLE_FILE, wx.ART_OTHER, wx.Size(16, 16)))
\r
34 self.AssignImageList(imglist)
\r
41 'root': self.AddRoot(
\r
42 text='Commands and Settings', image=self.image['root']),
\r
44 self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_selection_changed)
\r
45 self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._on_execute)
\r
47 self._callbacks = callbacks
\r
48 self._setup_commands(commands, selected)
\r
50 def _setup_commands(self, commands, selected):
\r
51 self._plugins = {} # {name: hooke.plugin.Plugin()}
\r
52 self._commands = {} # {name: hooke.command.Command()}
\r
54 # In both of the following dicts, command names are
\r
55 # (plugin.name, command.name) to avoid cross-plugin
\r
56 # collisions. See ._is_command().
\r
57 self._id_for_name = {} # {name: id}
\r
58 self._name_for_id = {} # {id: name}
\r
61 plugins = sorted(set([c.plugin for c in commands]),
\r
62 key=lambda p:p.name)
\r
63 for plugin in plugins:
\r
64 self._plugins[plugin.name] = plugin
\r
65 _id = self.AppendItem(
\r
66 parent=self._c['root'],
\r
68 image=self.image['plugin'])
\r
69 self._id_for_name[plugin.name] = _id
\r
70 self._name_for_id[_id] = plugin.name
\r
71 for command in sorted(commands, key=lambda c:c.name):
\r
72 name = (command.plugin.name, command.name)
\r
73 self._commands[name] = command
\r
74 _id = self.AppendItem(
\r
75 parent=self._id_for_name[command.plugin.name],
\r
77 image=self.image['command'])
\r
78 self._id_for_name[name] = _id
\r
79 self._name_for_id[_id] = name
\r
80 if command.name == selected:
\r
83 for plugin in self._plugins.values():
\r
84 self.Expand(self._id_for_name[plugin.name])
\r
85 # make sure the selected command/plugin is visible in the tree
\r
86 if selected is not None:
\r
87 self.SelectItem(selected, True)
\r
88 self.EnsureVisible(selected)
\r
90 def _is_command(self, name): # name from ._id_for_name / ._name_for_id
\r
91 """Return `True` if `name` corresponds to a :class:`hooke.command.Command`.
\r
93 # Plugin names are strings, Command names are tuples.
\r
94 # See ._setup_commands().
\r
95 return not isinstance(name, types.StringTypes)
\r
97 def _canonical_id(self, _id):
\r
98 """Return a canonical form of `_id` suitable for accessing `._name_for_id`.
\r
100 For some reason, `.GetSelection()`, etc. return items that
\r
101 hash differently than the original `.AppendItem()`-returned
\r
102 IDs. This means that `._name_for_id[self.GetSelection()]`
\r
103 will raise `KeyError`, even if there is an id `X` in
\r
104 `._name_for_id` for which `X == self.GetSelection()` will
\r
105 return `True`. This method "canonicalizes" IDs so that the
\r
106 hashing is consistent.
\r
108 for c_id in self._name_for_id.keys():
\r
113 def _on_selection_changed(self, event):
\r
114 active_id = event.GetItem()
\r
115 selected_id = self.GetSelection()
\r
116 name = self._name_for_id[self._canonical_id(selected_id)]
\r
117 if self._is_command(name):
\r
118 self.select_command(self._commands[name])
\r
120 self.select_plugin(self._plugins[name])
\r
122 def _on_execute(self, event):
\r
125 def select_plugin(self, plugin):
\r
126 in_callback(self, plugin)
\r
128 def select_command(self, command):
\r
129 in_callback(self, command)
\r
132 item = self.GetSelection()
\r
134 if not self.ItemHasChildren(item):
\r
135 item_text = self.GetItemText(item)
\r
136 # TODO: generate args
\r
137 in_callback(self, command, args)
\r
140 class CommandsPanel (Panel, wx.Panel):
\r
143 `callbacks` is shared with the underlying :class:`Tree`.
\r
145 def __init__(self, callbacks=None, commands=None, selected=None, **kwargs):
\r
146 super(CommandsPanel, self).__init__(
\r
147 name='commands', callbacks=callbacks, **kwargs)
\r
152 callbacks=callbacks,
\r
154 pos=wx.Point(0, 0),
\r
155 size=wx.Size(160, 250),
\r
156 style=wx.TR_DEFAULT_STYLE|wx.NO_BORDER|wx.TR_HIDE_ROOT),
\r
157 'execute': wx.Button(self, label='Execute'),
\r
159 sizer = wx.BoxSizer(wx.VERTICAL)
\r
160 sizer.Add(self._c['tree'], 1, wx.EXPAND)
\r
161 sizer.Add(self._c['execute'], 0, wx.EXPAND)
\r
162 self.SetSizer(sizer)
\r
165 self.Bind(wx.EVT_BUTTON, self._on_execute_button)
\r
166 self._callbacks = callbacks
\r
168 def _on_execute_button(self, event):
\r
169 self._c['tree'].execute()
\r