Initial work on hooke.command_stack and hooke.playlist integration.
[hooke.git] / hooke / playlist.py
index bba2990604927b1fbcdc624dfe7992a94e678295..906cb061a7c089220cfb49e07b679dd880c12b8a 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Lesser General Public License for more details.
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
@@ -24,6 +24,7 @@
 import copy
 import hashlib
 import os.path
+import types
 import xml.dom.minidom
 
 from . import curve as curve
@@ -44,13 +45,27 @@ class NoteIndexList (list):
         self._index = 0
 
     def __str__(self):
-        return '<%s %s>' % (self.__class__.__name__, self.name)
+        return str(self.__unicode__())
+
+    def __unicode__(self):
+        return u'<%s %s>' % (self.__class__.__name__, self.name)
+
+    def __repr__(self):
+        return self.__str__()
 
     def _setup_item(self, item):
         """Perform any required initialization before returning an item.
         """
         pass
 
+    def index(self, value=None, *args, **kwargs):
+        """Extend `list.index`, returning the current index if `value`
+        is `None`.
+        """
+        if value == None:
+            return self._index
+        return super(NoteIndexList, self).index(value, *args, **kwargs)
+
     def current(self):
         if len(self) == 0:
             return None
@@ -70,10 +85,20 @@ class NoteIndexList (list):
     def previous(self):
         self.jump(self._index - 1)
 
+    def items(self, reverse=False):
+        """Iterate through `self` calling `_setup_item` on each item
+        before yielding.
+        """
+        items = self
+        if reverse == True:
+            items = reversed(self)
+        for item in items:
+            self._setup_item(item)
+            yield item
+
     def filter(self, keeper_fn=lambda item:True, *args, **kwargs):
         c = copy.deepcopy(self)
-        for item in reversed(c):
-            c._setup_item(item)
+        for item in c.items(reverse=True):
             if keeper_fn(item, *args, **kwargs) != True:
                 c.remove(item)
         try: # attempt to maintain the same current item
@@ -82,6 +107,7 @@ class NoteIndexList (list):
             c._index = 0
         return c
 
+
 class Playlist (NoteIndexList):
     """A :class:`NoteIndexList` of :class:`hooke.curve.Curve`\s.
 
@@ -92,10 +118,10 @@ class Playlist (NoteIndexList):
         self.drivers = drivers
         self._loaded = [] # List of loaded curves, see :meth:`._setup_item`.
         self._max_loaded = 100 # curves to hold in memory simultaneously.
+        self._curve_load_args = ()
+        self._curve_load_kwargs = {}
 
     def append_curve_by_path(self, path, info=None, identify=True):
-        if self.path != None:
-            path = os.path.join(os.path.dirname(self.path), path)
         path = os.path.normpath(path)
         c = curve.Curve(path, info=info)
         if identify == True:
@@ -103,6 +129,10 @@ class Playlist (NoteIndexList):
         self.append(c)
         return c
 
+    def set_curve_load_args(self, *args, **kwargs):
+        self._curve_load_args = args
+        self._curve_load_kwargs = kwargs
+
     def _setup_item(self, curve):
         if curve != None and curve not in self._loaded:
             if curve not in self:
@@ -110,17 +140,19 @@ class Playlist (NoteIndexList):
             if curve.driver == None:
                 c.identify(self.drivers)
             if curve.data == None:
-                curve.load()
+                curve.load(*self._curve_load_args, **self._curve_load_kwargs)
             self._loaded.append(curve)
             if len(self._loaded) > self._max_loaded:
                 oldest = self._loaded.pop(0)
                 oldest.unload()
 
+
 class FilePlaylist (Playlist):
     version = '0.1'
 
     def __init__(self, drivers, name=None, path=None):
         super(FilePlaylist, self).__init__(drivers, name)
+        self.path = None
         self.set_path(path)
         self._digest = None
         self._ignored_keys = [
@@ -135,6 +167,11 @@ class FilePlaylist (Playlist):
             if self.name == None:
                 self.name = os.path.basename(path)
 
+    def append_curve_by_path(self, path, *args, **kwargs):
+        if self.path != None:
+            path = os.path.join(os.path.dirname(self.path), path)
+        super(FilePlaylist, self).append_curve_by_path(path, *args, **kwargs)
+
     def is_saved(self):
         return self.digest() == self._digest
 
@@ -231,6 +268,9 @@ class FilePlaylist (Playlist):
         root.setAttribute('version', self.version) # store playlist version
         root.setAttribute('index', str(self._index))
         for key,value in self.info.items(): # save info variables
+            if (key in self._ignored_keys
+                or not isinstance(value, types.StringTypes)):
+                continue
             root.setAttribute(self._clean_key(key), str(value))
         for curve in self: # save curves and their attributes
             curve_element = doc.createElement('curve')
@@ -244,7 +284,8 @@ class FilePlaylist (Playlist):
                             os.path.expanduser(self.path))))
             curve_element.setAttribute('path', path)
             for key,value in curve.info.items():
-                if key in self._ignored_keys:
+                if (key in self._ignored_keys
+                    or not isinstance(value, types.StringTypes)):
                     continue
                 curve_element.setAttribute(self._clean_key(key), str(value))
         string = doc.toprettyxml(encoding='utf-8')
@@ -315,6 +356,6 @@ class FilePlaylist (Playlist):
         """Saves the playlist in a XML file.
         """
         self.set_path(path)
-        f = file(self.path, 'w')
-        f.write(self.flatten())
-        f.close()
+        with open(self.path, 'w') as f:
+            f.write(self.flatten())
+            self._digest = self.digest()