From 5ba82c34a522c41e0ef93b61cafa6f608a94d651 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 9 May 2010 09:29:39 -0400 Subject: [PATCH] Moved hooke.playlist -> hooke.plugin.playlist and added hooke.plugin.Builtin. Highlights: * Cleaner Playlist, with more consistent file format. * New "Builtin" class for required (core) commands. * Moved NotRecognized from hooke.driver -> hooke.curve to avoid import-order issues. Now hooke.curve can be fully loaded before hooke.driver. * Moved hooke.curve.Data.notes -> hooke.curve.Data.info['note'] for better consistency with Curve and Playlist. * Started cleaning up plugin loading into flexible functions. --- hooke/curve.py | 15 +- hooke/driver/__init__.py | 5 - hooke/hooke.py | 39 +++- hooke/playlist.py | 197 ---------------- hooke/plugin/__init__.py | 123 +++++++--- hooke/plugin/playlist.py | 410 ++++++++++++++++++++++++++++++++++ hooke/ui/gui/hookeplaylist.py | 2 +- 7 files changed, 547 insertions(+), 244 deletions(-) delete mode 100644 hooke/playlist.py create mode 100644 hooke/plugin/playlist.py diff --git a/hooke/curve.py b/hooke/curve.py index 5aeaeaa..0be10bc 100644 --- a/hooke/curve.py +++ b/hooke/curve.py @@ -5,8 +5,12 @@ force curves. import os.path import numpy -from .driver import NotRecognized +class NotRecognized (ValueError): + def __init__(self, curve): + msg = 'Not a recognizable curve format: %s' % curve.path + ValueError.__init__(self, msg) + self.curve = curve class Data (numpy.ndarray): """Stores a single, continuous data set. @@ -61,14 +65,15 @@ class Curve (object): Hooke commands could like to know if they're looking at force clamp data, regardless of their origin. """ - def __init__(self, path): + def __init__(self, path, info=None): #the data dictionary contains: {name of data: list of data sets [{[x], [y]}] self.path = path self.driver = None self.data = [] - self.info = None + if info == None: + info = {} + self.info = info self.name = os.path.basename(path) - self.notes = '' def identify(self, drivers): """Identify the appropriate :class:`hooke.driver.Driver` for @@ -78,7 +83,7 @@ class Curve (object): if driver.is_me(self.path): self.driver = driver # remember the working driver return - raise NotRecognized(self.path) + raise NotRecognized(self) def load(self): """Use the driver to read the curve into memory. diff --git a/hooke/driver/__init__.py b/hooke/driver/__init__.py index ea4a9c9..ff90910 100644 --- a/hooke/driver/__init__.py +++ b/hooke/driver/__init__.py @@ -26,11 +26,6 @@ DRIVER_MODULES = [ default. TODO: autodiscovery """ -class NotRecognized (ValueError): - def __init__(self, path): - msg = 'Not a recognizable curve format: %s' % self.path - ValueError.__init__(self, msg) - self.path = path class Driver(object): """Base class for file format drivers. diff --git a/hooke/hooke.py b/hooke/hooke.py index 7a3f692..923f6cb 100644 --- a/hooke/hooke.py +++ b/hooke/hooke.py @@ -43,12 +43,24 @@ class Hooke (object): default_settings=default_settings) config.read() self.config = config + self.load_builtins() self.load_plugins() self.load_drivers() + self.setup_commands() + + def load_builtins(self): + self.builtins = [] + for builtin in plugin_mod.BUILTINS.values(): + builtin = plugin_mod.BUILTINS[builtin_name] + try: + builtin.config = dict( + self.config.items(builtin.setting_section)) + except configparser.NoSectionError: + pass + self.builtins.append(plugin_mod.BUILTINS[builtin_name]) def load_plugins(self): self.plugins = [] - self.commands = [] for plugin_name,include in self.config.items('plugins'): if include == 'True': plugin = plugin_mod.PLUGINS[plugin_name] @@ -58,7 +70,6 @@ class Hooke (object): except configparser.NoSectionError: pass self.plugins.append(plugin_mod.PLUGINS[plugin_name]) - self.commands.extend(plugin.commands()) def load_drivers(self): self.drivers = [] @@ -72,10 +83,22 @@ class Hooke (object): pass self.drivers.append(driver_mod.DRIVERS[driver_name]) + def setup_commands(self): + self.commands = [] + for plugin in self.builtins + self.plugins: + self.commands.extend(plugin.commands()) + def close(self): if self.config.changed: self.config.write() # Does not preserve original comments + def playlist_status(self, playlist): + if playlist.has_curves() + return '%s (%s/%s)' % (playlist.name, playlist._index + 1, + len(playlist)) + return 'The playlist %s does not contain any valid force curve data.' \ + % self.name + # def _GetActiveCurveIndex(self): # playlist = self.GetActivePlaylist() # #get the selected item from the tree @@ -138,7 +161,7 @@ class Hooke (object): # if files: # playlist = Playlist.Playlist(self.drivers) # for item in files: -# playlist.add_curve(item) +# playlist.append_curve_by_path(item) # if playlist.count > 0: # playlist.name = self._GetUniquePlaylistName(name) # playlist.reset() @@ -365,7 +388,7 @@ class Hooke (object): # def GetActiveCurve(self): # playlist = self.GetActivePlaylist() # if playlist is not None: -# return playlist.get_active_curve() +# return playlist.active_curve() # return None # # def GetActivePlaylist(self): @@ -569,7 +592,7 @@ class Hooke (object): # self.plotNotebook.SetSelection(index) # #if a curve was double-clicked # item = self.panelPlaylists.PlaylistsTree.GetSelection() -# #TODO: fix with get_active_curve +# #TODO: fix with active_curve # if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(item): # index = self._GetActiveCurveIndex() # else: @@ -592,7 +615,7 @@ class Hooke (object): # #if a curve was clicked # item = self.panelPlaylists.PlaylistsTree.GetSelection() # if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(item): -# #TODO: fix with get_active_curve +# #TODO: fix with active_curve # index = self._GetActiveCurveIndex() # if index >= 0: # #playlist = self.playlists[playlist_name][0] @@ -847,7 +870,7 @@ class Hooke (object): # #self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) # playlist = playlist.Playlist(self.drivers) # for item in files: -# curve = playlist.add_curve(item) +# curve = playlist.append_curve_by_path(item) # plot = copy.deepcopy(curve.plots[0]) # #add the 'raw' data # curve.add_data('raw', plot.vectors[0][0], plot.vectors[0][1], color=plot.colors[0], style='plot') @@ -944,7 +967,7 @@ class Hooke (object): # playlist_name = self._GetActivePlaylistName() # index = self._GetActiveCurveIndex() # playlist = self.playlists[playlist_name][0] -# curve = playlist.get_active_curve() +# curve = playlist.active_curve() # plot = playlist.get_active_plot() # figure = self.playlists[playlist_name][1] # diff --git a/hooke/playlist.py b/hooke/playlist.py deleted file mode 100644 index 26ab8dc..0000000 --- a/hooke/playlist.py +++ /dev/null @@ -1,197 +0,0 @@ -import copy -import os -import os.path -import xml.dom.minidom - -from . import hooke as hooke -from . import curve as lhc -from . import libhooke as lh - -class Playlist(object): - def __init__(self, drivers): - self._saved = False - self.count = 0 - self.curves = [] - self.drivers = drivers - self.path = '' - self.genericsDict = {} - self.hiddenAttributes = ['curve', 'driver', 'name', 'plots'] - self.index = -1 - self.name = 'Untitled' - self.plotPanel = None - self.plotTab = None - self.xml = None - - def add_curve(self, path, attributes={}): - curve = lhc.HookeCurve(path) - for key,value in attribures.items(): - setattr(curve, key, value) - curve.identify(self.drivers) - curve.plots = curve.driver.default_plots() - self.curves.append(curve) - self._saved = False - self.count = len(self.curves) - return curve - - def close_curve(self, index): - if index >= 0 and index < self.count: - self.curves.remove(index) - - def filter_curves(self, keeper_fn=labmda curve:True): - playlist = copy.deepcopy(self) - for curve in reversed(playlist.curves): - if not keeper_fn(curve): - playlist.curves.remove(curve) - try: # attempt to maintain the same active curve - playlist.index = playlist.curves.index(self.get_active_curve()) - except ValueError: - playlist.index = 0 - playlist._saved = False - playlist.count = len(playlist.curves) - return playlist - - def get_active_curve(self): - return self.curves[self.index] - - #TODO: do we need this? - def get_active_plot(self): - return self.curves[self.index].plots[0] - - def get_status_string(self): - if self.has_curves() - return '%s (%s/%s)' % (self.name, self.index + 1, self.count) - return 'The file %s does not contain any valid force curve data.' \ - % self.name - - def has_curves(self): - if self.count > 0: - return True - return False - - def is_saved(self): - return self._saved - - def load(self, path): - ''' - loads a playlist file - ''' - self.path = path - self.name = os.path.basename(path) - playlist = lh.delete_empty_lines_from_xmlfile(path) - self.xml = xml.dom.minidom.parse(path) - # Strip blank spaces: - self._removeWhitespaceNodes() - - generics_list = self.xml.getElementsByTagName('generics') - curve_list = self.xml.getElementsByTagName('curve') - self._loadGenerics(generics_list) - self._loadCurves(curve_list) - self._saved = True - - def _removeWhitespaceNodes(self, root_node=None): - if root_node == None: - root_node = self.xml - for node in root_node.childNodes: - if node.nodeType == node.TEXT_NODE and node.data.strip() == '': - root_node.removeChild(node) # drop this whitespace node - else: - _removeWhitespaceNodes(root_node=node) # recurse down a level - - def _loadGenerics(self, generics_list, clear=True): - if clear: - self.genericsDict = {} - #populate generics - generics_list = self.xml.getElementsByTagName('generics') - for generics in generics_list: - for attribute in generics.attributes.keys(): - self.genericsDict[attribute] = generics_list[0].getAttribute(attribute) - if self.genericsDict.has_key('pointer'): - index = int(self.genericsDict['pointer']) - if index >= 0 and index < len(self.curves): - self.index = index - else: - index = 0 - - def _loadCurves(self, curve_list, clear=True): - if clear: - self.curves = [] - #populate playlist with curves - for curve in curve_list: - #rebuild a data structure from the xml attributes - curve_path = lh.get_file_path(element.getAttribute('path')) - #extract attributes for the single curve - attributes = dict([(k,curve.getAttribute(k)) - for k in curve.attributes.keys()]) - attributes.pop('path') - curve = self.add_curve(os.path.join(path, curve_path), attributes) - if curve is not None: - for plot in curve.plots: - curve.add_data('raw', plot.vectors[0][0], plot.vectors[0][1], color=plot.colors[0], style='plot') - curve.add_data('raw', plot.vectors[1][0], plot.vectors[1][1], color=plot.colors[1], style='plot') - - def next(self): - self.index += 1 - if self.index > self.count - 1: - self.index = 0 - - def previous(self): - self.index -= 1 - if self.index < 0: - self.index = self.count - 1 - - def reset(self): - if self.has_curves(): - self.index = 0 - else: - self.index = None - - def save(self, path): - ''' - saves the playlist in a XML file. - ''' - try: - output_file = file(path, 'w') - except IOError, e: - #TODO: send message - print 'Cannot save playlist: %s' % e - return - self.xml.writexml(output_file, indent='\n') - output_file.close() - self._saved = True - - def set_XML(self): - ''' - Creates an initial playlist from a list of files. - A playlist is an XML document with the following syntax: - - - - - - - Relative paths are interpreted relative to the location of the - playlist file. - ''' - #create the output playlist, a simple XML document - implementation = xml.dom.minidom.getDOMImplementation() - #create the document DOM object and the root element - self.xml = implementation.createDocument(None, 'playlist', None) - root = self.xml.documentElement - - #save generics variables - playlist_generics = self.xml.createElement('generics') - root.appendChild(playlist_generics) - self.genericsDict['pointer'] = self.index - for key in self.genericsDict.keys(): - self.xml.createAttribute(key) - playlist_generics.setAttribute(key, str(self.genericsDict[key])) - - #save curves and their attributes - for item in self.curves: - playlist_curve = self.xml.createElement('curve') - root.appendChild(playlist_curve) - for key in item.__dict__: - if not (key in self.hiddenAttributes): - self.xml.createAttribute(key) - playlist_curve.setAttribute(key, str(item.__dict__[key])) - self._saved = False diff --git a/hooke/plugin/__init__.py b/hooke/plugin/__init__.py index 2bf05e8..f56644b 100644 --- a/hooke/plugin/__init__.py +++ b/hooke/plugin/__init__.py @@ -36,10 +36,17 @@ PLUGIN_MODULES = [ default. TODO: autodiscovery """ +BUILTIN_MODULES = [ + 'playlist', + ] +"""List of builtin modules. TODO: autodiscovery +""" + + # Plugins and settings class Plugin (object): - """The pluggable collection of Hooke commands. + """A pluggable collection of Hooke commands. Fulfills the same role for Hooke that a software package does for an operating system. @@ -67,7 +74,13 @@ class Plugin (object): """Return a list of :class:`Commands` provided.""" return [] +class Builtin (Plugin): + """A required collection of Hooke commands. + These "core" plugins provide essential administrative commands + (playlist handling, etc.). + """ + pass # Commands and arguments @@ -85,12 +98,12 @@ class Command (object): """One-line command description here. >>> c = Command(name='test', help='An example Command.') - >>> status = c.run(NullQueue(), PrintQueue(), help=True) + >>> status = c.run(NullQueue(), PrintQueue(), help=True) # doctest: +REPORT_UDIFF ITEM: Command: test Arguments: - help HELP (bool) Print a help message. + help BOOL (bool) Print a help message. An example Command. ITEM: @@ -256,40 +269,94 @@ class PrintQueue (NullQueue): print 'ITEM:\n%s' % item -# Construct plugin dependency graph and load default plugins. +# Construct plugin dependency graph and load plugin instances. -PLUGINS = {} -"""(name,instance) :class:`dict` of all possible :class:`Plugin`\s. -""" +def construct_graph(this_modname, submodnames, class_selector, + assert_name_match=True): + """Search the submodules `submodnames` of a module `this_modname` + for class objects for which `class_selector(class)` returns + `True`. These classes are instantiated, and the `instance.name` + is compared to the `submodname` (if `assert_name_match` is + `True`). -for plugin_modname,default_include in PLUGIN_MODULES: - assert len([mod_name for mod_name,di in PLUGIN_MODULES]) == 1, \ - 'Multiple %s entries in PLUGIN_MODULES' % mod_name - this_mod = __import__(__name__, fromlist=[plugin_modname]) - plugin_mod = getattr(this_mod, plugin_modname) - for objname in dir(plugin_mod): - obj = getattr(plugin_mod, objname) + The instances are further arranged into a dependency + :class:`hooke.util.graph.Graph` according to their + `instance.dependencies()` values. The topologically sorted graph + is returned. + """ + instances = {} + for submodname in submodnames: + count = len([s for s in submodnames if s == submodname]) + assert count > 0, 'No %s entries: %s' % (submodname, submodnames) + assert count == 1, 'Multiple (%d) %s entries: %s' \ + % (count, submodname, submodnames) + this_mod = __import__(this_modname, fromlist=[submodname]) + submod = getattr(this_mod, submodname) + for objname in dir(submod): + obj = getattr(submod, objname) + if class_selector(obj): + instance = obj() + if assert_name_match == True and instance.name != submodname: + raise Exception( + 'Instance name %s does not match module name %s' + % (instance.name, submodname)) + instances[instance.name] = instance + graph = Graph([Node([instances[name] for name in i.dependencies()], + data=i) + for i in instances.values()]) + graph.topological_sort() + return graph + +class IsSubclass (object): + """A safe subclass comparator. + + Examples + -------- + + >>> class A (object): + ... pass + >>> class B (A): + ... pass + >>> C = 5 + >>> is_subclass = IsSubclass(A) + >>> is_subclass(A) + True + >>> is_subclass = IsSubclass(A, blacklist=[A]) + >>> is_subclass(A) + False + >>> is_subclass(B) + True + >>> is_subclass(C) + False + """ + def __init__(self, base_class, blacklist=None): + self.base_class = base_class + if blacklist == None: + blacklist = [] + self.blacklist = blacklist + def __call__(self, other): try: - subclass = issubclass(obj, Plugin) + subclass = issubclass(other, self.base_class) except TypeError: - continue - if subclass == True and obj != Plugin: - p = obj() - if p.name != plugin_modname: - raise Exception('Plugin name %s does not match module name %s' - % (p.name, plugin_modname)) - PLUGINS[p.name] = p - -PLUGIN_GRAPH = Graph([Node([PLUGINS[name] for name in p.dependencies()], - data=p) - for p in PLUGINS.values()]) -PLUGIN_GRAPH.topological_sort() - + return False + if other in self.blacklist: + return False + return subclass + +PLUGIN_GRAPH = construct_graph( + this_modname=__name__, + submodnames=[name for name,include in PLUGIN_MODULES] + BUILTIN_MODULES, + class_selector=IsSubclass(Plugin, blacklist=[Plugin, Builtin])) +"""Topologically sorted list of all possible :class:`Plugin`\s and +:class:`Builtin`\s. +""" def default_settings(): settings = [Setting( 'plugins', help='Enable/disable default plugins.')] for pnode in PLUGIN_GRAPH: + if pnode.name in BUILTIN_MODULES: + continue # builtin inclusion is not optional plugin = pnode.data default_include = [di for mod_name,di in PLUGIN_MODULES if mod_name == plugin.name][0] diff --git a/hooke/plugin/playlist.py b/hooke/plugin/playlist.py new file mode 100644 index 0000000..58832d3 --- /dev/null +++ b/hooke/plugin/playlist.py @@ -0,0 +1,410 @@ +"""Defines :class:`PlaylistPlugin` several associated +:class:`hooke.plugin.Command`\s. +""" + +import copy +import hashlib +import os +import os.path +import xml.dom.minidom + + +from .. import curve as curve +from ..plugin import Plugin, Command, Argument + +class PlaylistPlugin (Plugin): + def __init__(self): + super(PlaylistPlugin, self).__init__(name='playlist') + + def commands(self): + return [NextCommand(), PreviousCommand(), JumpCommand(), + SaveCommand(), LoadCommand(), + AddCommand(), RemoveCommand(), FilterCommand()] + +class Playlist (list): + """A list of :class:`hooke.curve.Curve`\s. + + Keeps a list of :attr:`drivers` for loading curves, the + :attr:`index` (i.e. "bookmark") of the currently active curve, and + a :class:`dict` of additional informtion (:attr:`info`). + """ + def __init__(self, drivers, name=None): + super(Playlist, self).__init__() + self.drivers = drivers + self.name = name + self.info = {} + self._index = 0 + + def append_curve_by_path(self, path, info=None, identify=True): + if self.path != None: + path = os.path.join(self.path, path) + path = os.path.normpath(path) + c = curve.Curve(path, info=info) + if identify == True: + c.identify(self.drivers) + self.append(c) + return c + + def active_curve(self): + return self[self._index] + + def has_curves(self): + return len(self) > 0 + + def jump(self, index): + if len(self) == 0: + self._index = 0 + else: + self._index = index % len(self) + + def next(self): + self.jump(self._index + 1) + + def previous(self): + self.jump(self._index - 1) + + def filter(self, keeper_fn=lambda curve:True): + playlist = copy.deepcopy(self) + for curve in reversed(playlist.curves): + if keeper_fn(curve) != True: + playlist.curves.remove(curve) + try: # attempt to maintain the same active curve + playlist._index = playlist.index(self.active_curve()) + except ValueError: + playlist._index = 0 + return playlist + +class FilePlaylist (Playlist): + version = '0.1' + + def __init__(self, drivers, name=None, path=None): + if name == None and path != None: + name = os.path.basename(path) + super(FilePlaylist, self).__init__(drivers, name) + self.path = path + self._digest = None + + def is_saved(self): + return self.digest() == self._digest + + def digest(self): + r"""Compute the sha1 digest of the flattened playlist + representation. + + Examples + -------- + + >>> root_path = os.path.sep + 'path' + >>> p = FilePlaylist(drivers=[], + ... path=os.path.join(root_path, 'to','playlist')) + >>> p.info['note'] = 'An example playlist' + >>> c = curve.Curve(os.path.join(root_path, 'to', 'curve', 'one')) + >>> c.info['note'] = 'The first curve' + >>> p.append(c) + >>> c = curve.Curve(os.path.join(root_path, 'to', 'curve', 'two')) + >>> c.info['note'] = 'The second curve' + >>> p.append(c) + >>> p.digest() + "\xa1\x99\x8a\x99\xed\xad\x13'\xa7w\x12\x00\x07Z\xb3\xd0zN\xa2\xe1" + """ + string = self.flatten() + return hashlib.sha1(string).digest() + + def flatten(self, absolute_paths=False): + """Create a string representation of the playlist. + + A playlist is an XML document with the following syntax:: + + + + + + + + Relative paths are interpreted relative to the location of the + playlist file. + + Examples + -------- + + >>> root_path = os.path.sep + 'path' + >>> p = FilePlaylist(drivers=[], + ... path=os.path.join(root_path, 'to','playlist')) + >>> p.info['note'] = 'An example playlist' + >>> c = curve.Curve(os.path.join(root_path, 'to', 'curve', 'one')) + >>> c.info['note'] = 'The first curve' + >>> p.append(c) + >>> c = curve.Curve(os.path.join(root_path, 'to', 'curve', 'two')) + >>> c.info['note'] = 'The second curve' + >>> p.append(c) + >>> print p.flatten() # doctest: +NORMALIZE_WHITESPACE +REPORT_UDIFF + + + + + + + >>> print p.flatten(absolute_paths=True) # doctest: +NORMALIZE_WHITESPACE +REPORT_UDIFF + + + + + + + """ + implementation = xml.dom.minidom.getDOMImplementation() + # create the document DOM object and the root element + doc = implementation.createDocument(None, 'playlist', None) + root = doc.documentElement + root.setAttribute('version', self.version) # store playlist version + root.setAttribute('index', str(self._index)) + for key,value in self.info.items(): # save info variables + root.setAttribute(key, str(value)) + for curve in self: # save curves and their attributes + curve_element = doc.createElement('curve') + root.appendChild(curve_element) + path = os.path.abspath(os.path.expanduser(curve.path)) + if absolute_paths == False: + path = os.path.relpath( + path, + os.path.abspath(os.path.expanduser(self.path))) + curve_element.setAttribute('path', path) + for key,value in curve.info.items(): + curve_element.setAttribute(key, str(value)) + string = doc.toprettyxml(encoding='utf-8') + root.unlink() # break circular references for garbage collection + return string + + def _from_xml_doc(self, doc): + """Load a playlist from an :class:`xml.dom.minidom.Document` + instance. + """ + root = doc.documentElement + for attribute,value in root.attributes.items(): + if attribute == 'version': + assert value == self.version, \ + 'Cannot read v%s playlist with a v%s reader' \ + % (value, self.version) + elif attribute == 'index': + self._index = int(value) + else: + self.info[attribute] = value + for curve_element in doc.getElementsByTagName('curve'): + path = curve_element.getAttribute('path') + info = dict(curve_element.attributes.items()) + info.pop('path') + self.append_curve_by_path(path, info, identify=False) + self.jump(self._index) # ensure valid index + + def from_string(self, string): + """Load a playlist from a string. + + Examples + -------- + + >>> string = ''' + ... + ... + ... + ... + ... ''' + >>> p = FilePlaylist(drivers=[], + ... path=os.path.join('path', 'to','playlist')) + >>> p.from_string(string) + >>> p._index + 1 + >>> p.info + {u'note': u'An example playlist'} + >>> for curve in p: + ... print curve.path + path/to/curve/one + path/to/curve/two + """ + doc = xml.dom.minidom.parseString(string) + return self._from_xml_doc(doc) + + def load(self, path=None): + """Load a playlist from a file. + """ + if path != None: + self.path = path + if self.name == None: + self.name = os.path.basename(self.path) + doc = xml.dom.minidom.parse(path) + return self._from_xml_doc(doc) + + def save(self, path): + """Saves the playlist in a XML file. + """ + f = file(path, 'w') + f.write(self.flatten()) + f.close() + +class NextCommand (Command): + """Move playlist to the next curve. + """ + def __init__(self): + super(NextCommand, self).__init__( + name='next curve', + arguments=[ + Argument(name='playlist', type='playlist', optional=False, + help=""" +:class:``hooke.plugin.playlist.Playlist`` to act on. +""".strip()), + ], + help=self.__doc__) + + def _run(inqueue, outqueue, params): + params['playlist'].next() + +class PreviousCommand (Command): + """Move playlist to the previous curve. + """ + def __init__(self): + super(PreviousCommand, self).__init__( + name='previous curve', + arguments=[ + Argument(name='playlist', type='playlist', optional=False, + help=""" +:class:``hooke.plugin.playlist.Playlist`` to act on. +""".strip()), + ], + help=self.__doc__) + + def _run(inqueue, outqueue, params): + params['playlist'].previous() + +class JumpCommand (Command): + """Move playlist to a given curve. + """ + def __init__(self): + super(JumpCommand, self).__init__( + name='jump to curve', + arguments=[ + Argument(name='playlist', type='playlist', optional=False, + help=""" +:class:``hooke.plugin.playlist.Playlist`` to act on. +""".strip()), + Argument(name='index', type='int', optional=False, help=""" +Index of target curve. +""".strip()), + ], + help=self.__doc__) + + def _run(inqueue, outqueue, params): + params['playlist'].jump(params['index']) + +class SaveCommand (Command): + """Save a playlist. + """ + def __init__(self): + super(SaveCommand, self).__init__( + name='save playlist', + arguments=[ + Argument(name='playlist', type='playlist', optional=False, + help=""" +:class:``hooke.plugin.playlist.Playlist`` to act on. +""".strip()), + Argument(name='output', type='file', + help=""" +File name for the output playlist. Defaults to overwring the input +playlist. +""".strip()), + ], + help=self.__doc__) + + def _run(inqueue, outqueue, params): + params['playlist'].save(params['output']) + +class LoadCommand (Command): + """Load a playlist. + """ + def __init__(self): + super(LoadCommand, self).__init__( + name='load playlist', + arguments=[ + Argument(name='input', type='file', optional=False, + help=""" +File name for the input playlist. +""".strip()), + Argument(name='digests', type='digest', optional=False, + count=-1, + help=""" +Digests for loading curves. +""".strip()), + ], + help=self.__doc__) + + def _run(inqueue, outqueue, params): + p = FilePlaylist(drivers=params['drivers'], path=params['input']) + p.load() + outqueue.put(p) + +class AddCommand (Command): + """Add a curve to a playlist. + """ + def __init__(self): + super(AddCommand, self).__init__( + name='add curve to playlist', + arguments=[ + Argument(name='playlist', type='playlist', optional=False, + help=""" +:class:``hooke.plugin.playlist.Playlist`` to act on. +""".strip()), + Argument(name='input', type='file', optional=False, + help=""" +File name for the input :class:`hooke.curve.Curve`. +""".strip()), + Argument(name='info', type='dict', optional=True, + help=""" +Additional information for the input :class:`hooke.curve.Curve`. +""".strip()), + ], + help=self.__doc__) + + def _run(inqueue, outqueue, params): + params['playlist'].append_curve_by_path(params['input'], + params['info']) + +class RemoveCommand (Command): + """Remove a curve from a playlist. + """ + def __init__(self): + super(RemoveCommand, self).__init__( + name='remove curve from playlist', + arguments=[ + Argument(name='playlist', type='playlist', optional=False, + help=""" +:class:``hooke.plugin.playlist.Playlist`` to act on. +""".strip()), + Argument(name='index', type='int', optional=False, help=""" +Index of target curve. +""".strip()), + ], + help=self.__doc__) + + def _run(inqueue, outqueue, params): + params['playlist'].pop(params['index']) + params['playlist'].jump(params._index) + +class FilterCommand (Command): + """Create a subset playlist via a selection function. + """ + def __init__(self): + super(FilterCommand, self).__init__( + name='filter playlist', + arguments=[ + Argument(name='playlist', type='playlist', optional=False, + help=""" +:class:``hooke.plugin.playlist.Playlist`` to act on. +""".strip()), + Argument(name='filter', type='function', optional=False, + help=""" +Function returning `True` for "good" curves. +""".strip()), + ], + help=self.__doc__) + + def _run(inqueue, outqueue, params): + p = params['playlist'].filter(params['filter']) + outqueue.put(p) diff --git a/hooke/ui/gui/hookeplaylist.py b/hooke/ui/gui/hookeplaylist.py index 5d731ae..2646316 100644 --- a/hooke/ui/gui/hookeplaylist.py +++ b/hooke/ui/gui/hookeplaylist.py @@ -30,7 +30,7 @@ class Playlists(wx.Panel): #if files: #playlist = hookeplaylist.Playlist(self.drivers) #for item in files: - #playlist.add_curve(item) + #playlist.append_curve_by_path(item) #if playlist.count > 0: #playlist_name = name #count = 1 -- 2.26.2