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
21 class Tree (wx.TreeCtrl):
\r
24 `callbacks` is shared with the parent :class:`Commands`.
\r
26 def __init__(self, commands, selected, callbacks, *args, **kwargs):
\r
27 super(Tree, self).__init__(*args, **kwargs)
\r
28 imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)
\r
29 imglist.Add(wx.ArtProvider.GetBitmap(
\r
30 wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))
\r
31 imglist.Add(wx.ArtProvider.GetBitmap(
\r
32 wx.ART_EXECUTABLE_FILE, wx.ART_OTHER, wx.Size(16, 16)))
\r
33 self.AssignImageList(imglist)
\r
40 'root': self.AddRoot(
\r
41 text='Commands and Settings', image=self.image['root']),
\r
43 self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_selection_changed)
\r
44 self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._on_execute)
\r
46 self._callbacks = callbacks
\r
47 self._setup_commands(commands, selected)
\r
49 def _setup_commands(self, commands, selected):
\r
50 self._plugins = {} # {name: hooke.plugin.Plugin()}
\r
51 self._commands = {} # {name: hooke.command.Command()}
\r
53 # In both of the following dicts, command names are
\r
54 # (plugin.name, command.name) to avoid cross-plugin
\r
55 # collisions. See ._is_command().
\r
56 self._id_for_name = {} # {name: id}
\r
57 self._name_for_id = {} # {id: name}
\r
60 plugins = sorted(set([c.plugin for c in commands]),
\r
61 key=lambda p:p.name)
\r
62 for plugin in plugins:
\r
63 self._plugins[plugin.name] = plugin
\r
64 _id = self.AppendItem(
\r
65 parent=self._c['root'],
\r
67 image=self.image['plugin'])
\r
68 self._id_for_name[plugin.name] = _id
\r
69 self._name_for_id[_id] = plugin.name
\r
70 for command in sorted(commands, key=lambda c:c.name):
\r
71 name = (command.plugin.name, command.name)
\r
72 self._commands[name] = command
\r
73 _id = self.AppendItem(
\r
74 parent=self._id_for_name[command.plugin.name],
\r
76 image=self.image['command'])
\r
77 self._id_for_name[name] = _id
\r
78 self._name_for_id[_id] = name
\r
79 if command.name == selected:
\r
82 for plugin in self._plugins.values():
\r
83 self.Expand(self._id_for_name[plugin.name])
\r
84 # make sure the selected command/plugin is visible in the tree
\r
85 if selected is not None:
\r
86 self.SelectItem(selected, True)
\r
87 self.EnsureVisible(selected)
\r
89 def _is_command(self, name): # name from ._id_for_name / ._name_for_id
\r
90 """Return `True` if `name` corresponds to a :class:`hooke.command.Command`.
\r
92 # Plugin names are strings, Command names are tuples.
\r
93 # See ._setup_commands().
\r
94 return not isinstance(name, types.StringTypes)
\r
96 def _canonical_id(self, _id):
\r
97 """Return a canonical form of `_id` suitable for accessing `._name_for_id`.
\r
99 For some reason, `.GetSelection()`, etc. return items that
\r
100 hash differently than the original `.AppendItem()`-returned
\r
101 IDs. This means that `._name_for_id[self.GetSelection()]`
\r
102 will raise `KeyError`, even if there is an id `X` in
\r
103 `._name_for_id` for which `X == self.GetSelection()` will
\r
104 return `True`. This method "canonicalizes" IDs so that the
\r
105 hashing is consistent.
\r
107 for c_id in self._name_for_id.keys():
\r
112 def _on_selection_changed(self, event):
\r
113 active_id = event.GetItem()
\r
114 selected_id = self.GetSelection()
\r
115 name = self._name_for_id[self._canonical_id(selected_id)]
\r
116 if self._is_command(name):
\r
117 self.select_command(self._commands[name])
\r
119 self.select_plugin(self._plugins[name])
\r
121 def _on_execute(self, event):
\r
124 def select_plugin(self, plugin):
\r
125 in_callback(self, plugin)
\r
127 def select_command(self, command):
\r
128 in_callback(self, command)
\r
131 item = self.GetSelection()
\r
133 if not self.ItemHasChildren(item):
\r
134 item_text = self.GetItemText(item)
\r
135 # TODO: generate args
\r
136 in_callback(self, command, args)
\r
139 class Commands (wx.Panel):
\r
142 `callbacks` is shared with the underlying :class:`Tree`.
\r
144 def __init__(self, commands, selected, callbacks, *args, **kwargs):
\r
145 super(Commands, self).__init__(*args, **kwargs)
\r
150 callbacks=callbacks,
\r
152 pos=wx.Point(0, 0),
\r
153 size=wx.Size(160, 250),
\r
154 style=wx.TR_DEFAULT_STYLE|wx.NO_BORDER|wx.TR_HIDE_ROOT),
\r
155 'execute': wx.Button(self, label='Execute'),
\r
157 sizer = wx.BoxSizer(wx.VERTICAL)
\r
158 sizer.Add(self._c['execute'], 0, wx.EXPAND)
\r
159 sizer.Add(self._c['tree'], 1, wx.EXPAND)
\r
160 self.SetSizer(sizer)
\r
163 self.Bind(wx.EVT_BUTTON, self._on_execute_button)
\r
164 self._callbacks = callbacks
\r
166 def _on_execute_button(self, event):
\r
167 self._c['tree'].execute()
\r