1 # Copyright (C) 2007-2010 Massimo Sandal <devicerandom@gmail.com>
2 # W. Trevor King <wking@drexel.edu>
4 # This file is part of Hooke.
6 # Hooke is free software: you can redistribute it and/or modify it
7 # under the terms of the GNU Lesser General Public License as
8 # published by the Free Software Foundation, either version 3 of the
9 # License, or (at your option) any later version.
11 # Hooke is distributed in the hope that it will be useful, but WITHOUT
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
14 # Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with Hooke. If not, see
18 # <http://www.gnu.org/licenses/>.
20 """This plugin contains example commands to teach how to write an
21 Hooke plugin, including description of main Hooke internals.
27 from numpy import arange
29 from ..command import Command, Argument, Failure
30 from ..config import Setting
31 from ..interaction import PointRequest, PointResponse
32 from ..util.si import ppSI, split_data_label
34 from .curve import CurveArgument
37 class TutorialPlugin (Plugin):
38 """An example plugin explaining how to code plugins.
40 Unlike previous versions of Hooke, the class name is no longer
41 important. Plugins identify themselves to
42 :func:`hooke.util.pluggable.construct_graph` by being subclasses
43 of :class:`hooke.plugin.Plugin`. However, for consistency we
44 suggest the following naming scheme, show here for the 'tutorial'
47 =========== ==============
48 module file tutorial.py
49 class name TutorialPlugin
51 =========== ==============
53 To ensure filename sanity,
54 :func:`hooke.util.pluggable.construct_graph` requires that
55 :attr:`name` does match the submodule name, but don't worry,
56 you'll get a clear exception message if you make a mistake.
59 """TutorialPlugin initialization code.
61 We call our base class' :meth:`__init__` and setup
64 # This is the plugin initialization. When Hooke starts and
65 # the plugin is loaded, this function is executed. If there
66 # is something you need to do when Hooke starts, code it in
68 print 'I am the Tutorial plugin initialization!'
70 # This super() call similar to the old-style
72 # but super() is more robust under multiple inheritance.
73 # See Guido's introduction:
74 # http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
75 # And the related PEPs:
76 # http://www.python.org/dev/peps/pep-0253/
77 # http://www.python.org/dev/peps/pep-3135/
78 super(TutorialPlugin, self).__init__(name='tutorial')
80 # We want :meth:`commands` to return a list of
81 # :class:`hooke.command.Command` instances. Rather than
82 # instantiate the classes for each call to :meth:`commands`,
83 # we instantiate them in a list here, and rely on
84 # :meth:`hooke.plugin.Plugin.commands` to return copies of
86 self._commands = [DoNothingCommand(self), HookeInfoCommand(self),
87 PointInfoCommand(self),]
89 def dependencies(self):
90 """Return a list of names of :class:`hooke.plugin.Plugin`\s we
93 Some plugins use features from other plugins. Hooke makes sure that
94 plugins are configured in topological order and that no plugin is
95 enabled if it is missing dependencies.
99 def default_settings(self):
100 """Return a list of :class:`hooke.config.Setting`\s for any
101 configurable plugin settings.
103 The suggested section setting is::
105 Setting(section=self.setting_section, help=self.__doc__)
107 You only need to worry about this if your plugin has some
108 "magic numbers" that the user may want to tweak, but that
109 won't be changing on a per-command basis.
111 You should lead off the list of settings with the suggested
112 section setting mentioned above.
115 # We disable help wrapping, since we've wrapped
116 # TutorialPlugin.__doc__ ourselves, and it's more than one
117 # paragraph (textwrap.fill, used in
118 # :meth:`hooke.config.Setting.write` only handles one
119 # paragraph at a time).
120 Setting(section=self.setting_section, help=self.__doc__,
122 Setting(section=self.setting_section, option='favorite color',
123 value='orange', help='Your personal favorite color.'),
127 # Define common or complicated arguments
129 # Often, several commands in a plugin will use similar arguments. For
130 # example, many curves in the 'playlist' plugin need a playlist to act
131 # on. Rather than repeating an argument definition in several times,
132 # you can keep your code DRY (Don't Repeat Yourself) by defining the
133 # argument at the module level and referencing it during each command
136 def color_callback(hooke, command, argument, value):
137 """If `argument` is `None`, default to the configured 'favorite color'.
139 :class:`hooke.command.Argument`\s may have static defaults, but
140 for dynamic defaults, they use callback functions (like this one).
144 return command.plugin.config['favorite color']
146 ColorArgument = Argument(
147 name='color', type='string', callback=color_callback,
148 help="Pick a color, any color.")
149 # See :func:`hooke.ui.gui.panel.propertyeditor.prop_from_argument` for
150 # a situation where :attr:`type` is important.
153 class DoNothingCommand (Command):
154 """This is a boring but working example of an actual Hooke command.
156 As for :class:`hooke.plugin.Plugin`\s, the class name is not
157 important, but :attr:`name` is. :attr:`name` is used (possibly
158 with some adjustment) as the name for accessing the command in the
159 various :class:`hooke.ui.UserInterface`\s. For example the
160 `'do nothing'` command can be run from the command line UI with::
164 Note that if you now start Hooke with the command's plugin
165 activated and you type in the Hooke command line "help do_nothing"
166 you will see this very text as output. That is because we set
167 :attr:`_help` to this class' docstring on initialization.
169 def __init__(self, plugin):
170 # See the comments in TutorialPlugin.__init__ for details
171 # about super() and the docstring of
172 # :class:`hooke.command.Command` for details on the __init__()
174 super(DoNothingCommand, self).__init__(
176 arguments=[ColorArgument],
177 help=self.__doc__, plugin=plugin)
179 def _run(self, hooke, inqueue, outqueue, params):
180 """This is where the command-specific magic will happen.
182 If you haven't already, read the Architecture section of
183 :file:`doc/hacking.txt` (also available `online`_). It
184 explains the engine/UI setup in more detail.
187 http://www.physics.drexel.edu/~wking/rsrch/hooke/hacking.html#architecture
189 The return value (if any) of this method is ignored. You
190 should modify the :class:`hooke.hooke.Hooke` instance passed
191 in via `hooke` and/or return things via `outqueue`. `inqueue`
192 is only important if your command requires mid-command user
195 By the time this method is called, all the argument
196 preprocessing (callbacks, defaults, etc.) have already been
197 handled by :meth:`hooke.command.Command.run`.
199 # On initialization, :class:`hooke.hooke.Hooke` sets up a
200 # logger to use for Hooke-related messages. Please use it
201 # instead of debugging 'print' calls, etc., as it is more
203 log = logging.getLogger('hooke')
204 log.debug('Watching %s paint dry' % params['color'])
207 class HookeInfoCommand (Command):
208 """Get information about the :class:`hooke.hooke.Hooke` instance.
210 def __init__(self, plugin):
211 super(HookeInfoCommand, self).__init__(
213 help=self.__doc__, plugin=plugin)
215 def _run(self, hooke, inqueue, outqueue, params):
216 outqueue.put('Hooke info:')
217 # hooke.config contains a :class:`hooke.config.HookeConfigParser`
218 # with the current hooke configuration settings.
219 config_file = StringIO.StringIO()
220 hooke.config.write(config_file)
221 outqueue.put('configuration:\n %s'
222 % '\n '.join(config_file.getvalue().splitlines()))
223 # The plugin's configuration settings are also available.
224 outqueue.put('plugin config: %s' % self.plugin.config)
225 # hooke.plugins contains :class:`hooke.plugin.Plugin`\s defining
226 # :class:`hooke.command.Command`\s.
227 outqueue.put('plugins: %s'
228 % ', '.join([plugin.name for plugin in hooke.plugins]))
229 # hooke.drivers contains :class:`hooke.driver.Driver`\s for
231 outqueue.put('drivers: %s'
232 % ', '.join([driver.name for driver in hooke.drivers]))
233 # hooke.playlists contains a
234 # :class:`hooke.playlist.NoteIndexList` of
235 # :class:`hooke.playlist.Playlist`\s. Each playlist may
236 # contain several :class:`hooke.curve.Curve`\s representing a
237 # grouped collection of data.
238 playlist = hooke.playlists.current()
241 outqueue.put('current playlist: %s (%d of %d)'
243 hooke.playlists.index(),
244 len(hooke.playlists)))
245 curve = playlist.current()
248 outqueue.put('current curve: %s (%d of %d)'
254 class PointInfoCommand (Command):
255 """Get information about user-selected points.
257 Ordinarily a command that knew it would need user selected points
258 would declare an appropriate argument (see, for example,
259 :class:`hooke.plugin.cut.CutCommand`). However, here we find the
260 points via user-interaction to show how user interaction works.
262 def __init__(self, plugin):
263 super(PointInfoCommand, self).__init__(
267 Argument(name='block', type='int', default=0,
269 Data block that points are selected from. For an approach/retract
270 force curve, `0` selects the approaching curve and `1` selects the
274 help=self.__doc__, plugin=plugin)
276 def _run(self, hooke, inqueue, outqueue, params):
277 data = params['curve'].data[params['block']]
279 # Ask the user to select a point.
280 outqueue.put(PointRequest(
281 msg="Select a point",
282 curve=params['curve'],
283 block=params['block']))
285 # Get the user's response
286 result = inqueue.get()
287 if not isinstance(result, PointResponse):
288 inqueue.put(result) # put the message back in the queue
290 'expected a PointResponse instance but got %s.'
294 # Act on the response
298 for column_name in data.info['columns']:
299 name,unit = split_data_label(column_name)
300 column_index = data.info['columns'].index(column_name)
301 value = data[point,column_index]
302 si_value = ppSI(value, unit, decimals=2)
303 values.append('%s: %s' % (name, si_value))
305 outqueue.put('selected point %d: %s'
306 % (point, ', '.join(values)))