Added BoolProperty and StringProperty
[hooke.git] / hooke / playlist.py
index 87fc073a517402e782c7fb49b61542aeb100d0da..8efac4770568a3a6923c83a138317349494ad7d8 100644 (file)
@@ -1,4 +1,20 @@
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# 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 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
+# <http://www.gnu.org/licenses/>.
 
 """The `playlist` module provides a :class:`Playlist` and its subclass
 :class:`FilePlaylist` for manipulating lists of
@@ -26,10 +42,20 @@ class NoteIndexList (list):
         self.info = {}
         self._index = 0
 
+    def __str__(self):
+        return '<%s %s>' % (self.__class__.__name__, self.name)
+
+    def _setup_item(self, item):
+        """Perform any required initialization before returning an item.
+        """
+        pass
+
     def current(self):
         if len(self) == 0:
             return None
-        return self[self._index]
+        item = self[self._index]
+        self._setup_item(item)
+        return item
 
     def jump(self, index):
         if len(self) == 0:
@@ -43,10 +69,11 @@ class NoteIndexList (list):
     def previous(self):
         self.jump(self._index - 1)
 
-    def filter(self, keeper_fn=lambda item:True):
+    def filter(self, keeper_fn=lambda item:True, *args, **kwargs):
         c = copy.deepcopy(self)
         for item in reversed(c):
-            if keeper_fn(item) != True:
+            c._setup_item(item)
+            if keeper_fn(item, *args, **kwargs) != True:
                 c.remove(item)
         try: # attempt to maintain the same current item
             c._index = c.index(self.current())
@@ -62,10 +89,12 @@ class Playlist (NoteIndexList):
     def __init__(self, drivers, name=None):
         super(Playlist, self).__init__(name=name)
         self.drivers = drivers
+        self._loaded = [] # List of loaded curves, see :meth:`._setup_item`.
+        self._max_loaded = 100 # curves to hold in memory simultaneously.
 
     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.join(os.path.dirname(self.path), path)
         path = os.path.normpath(path)
         c = curve.Curve(path, info=info)
         if identify == True:
@@ -73,6 +102,19 @@ class Playlist (NoteIndexList):
         self.append(c)
         return c
 
+    def _setup_item(self, curve):
+        if curve != None and curve not in self._loaded:
+            if curve not in self:
+                self.append(curve)
+            if curve.driver == None:
+                c.identify(self.drivers)
+            if curve.data == None:
+                curve.load()
+            self._loaded.append(curve)
+            if len(self._loaded) > self._max_loaded:
+                oldest = self._loaded.pop(0)
+                oldest.unload()
+
 class FilePlaylist (Playlist):
     version = '0.1'
 
@@ -80,6 +122,9 @@ class FilePlaylist (Playlist):
         super(FilePlaylist, self).__init__(drivers, name)
         self.set_path(path)
         self._digest = None
+        self._ignored_keys = [
+            'experiment',  # class instance, not very exciting.
+            ]
 
     def set_path(self, path):
         if path != None:
@@ -110,7 +155,7 @@ class FilePlaylist (Playlist):
         >>> 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"
+        '\\\x14\x87\x88*q\xf8\xaa\xa7\x84f\x82\xa1S>\xfd3+\xd0o'
         """
         string = self.flatten()
         return hashlib.sha1(string).digest()
@@ -145,8 +190,8 @@ class FilePlaylist (Playlist):
         >>> print p.flatten() # doctest: +NORMALIZE_WHITESPACE +REPORT_UDIFF
         <?xml version="1.0" encoding="utf-8"?>
         <playlist index="0" note="An example playlist" version="0.1">
-            <curve note="The first curve" path="../curve/one"/>
-            <curve note="The second curve" path="../curve/two"/>
+            <curve note="The first curve" path="curve/one"/>
+            <curve note="The second curve" path="curve/two"/>
         </playlist>
         <BLANKLINE>
         >>> print p.flatten(absolute_paths=True) # doctest: +NORMALIZE_WHITESPACE +REPORT_UDIFF
@@ -172,15 +217,19 @@ class FilePlaylist (Playlist):
             if absolute_paths == False:
                 path = os.path.relpath(
                     path,
-                    os.path.abspath(os.path.expanduser(self.path)))
+                    os.path.dirname(
+                        os.path.abspath(
+                            os.path.expanduser(self.path))))
             curve_element.setAttribute('path', path)
             for key,value in curve.info.items():
+                if key in self._ignored_keys:
+                    continue
                 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):
+    def _from_xml_doc(self, doc, identify=True):
         """Load a playlist from an :class:`xml.dom.minidom.Document`
         instance.
         """
@@ -198,10 +247,10 @@ class FilePlaylist (Playlist):
             path = curve_element.getAttribute('path')
             info = dict(curve_element.attributes.items())
             info.pop('path')
-            self.append_curve_by_path(path, info, identify=False)
+            self.append_curve_by_path(path, info, identify=identify)
         self.jump(self._index) # ensure valid index
 
-    def from_string(self, string):
+    def from_string(self, string, identify=True):
         """Load a playlist from a string.
 
         Examples
@@ -214,8 +263,8 @@ class FilePlaylist (Playlist):
         ... </playlist>
         ... '''
         >>> p = FilePlaylist(drivers=[],
-        ...                  path=os.path.join('path', 'to','playlist'))
-        >>> p.from_string(string)
+        ...                  path=os.path.join('path', 'to', 'my', 'playlist'))
+        >>> p.from_string(string, identify=False)
         >>> p._index
         1
         >>> p.info
@@ -226,14 +275,14 @@ class FilePlaylist (Playlist):
         path/to/curve/two
         """
         doc = xml.dom.minidom.parseString(string)
-        self._from_xml_doc(doc)
+        self._from_xml_doc(doc, identify=identify)
 
-    def load(self, path=None):
+    def load(self, path=None, identify=True):
         """Load a playlist from a file.
         """
         self.set_path(path)
         doc = xml.dom.minidom.parse(self.path)
-        self._from_xml_doc(doc)
+        self._from_xml_doc(doc, identify=identify)
         self._digest = self.digest()
 
     def save(self, path=None):