=== modified file 'xlgui/__init__.py'--- xlgui/__init__.py 2010-07-26 20:26:14 +0000+++ xlgui/__init__.py 2010-08-10 22:30:44 +0000@@ -61,7 +61,7 @@ @param exaile: The Exaile instance """ from xlgui import icons, main, panel, tray, progress- from xlgui.panel import collection, radio, playlists, files+ from xlgui.panel import collection, radio, playlists, files, playlist2 gtk.gdk.set_program_class("Exaile")@@ -102,8 +102,9 @@ self.panels['playlists'] = playlists.PlaylistsPanel(self.main.window, exaile.playlists, exaile.smart_playlists, exaile.collection) self.panels['files'] = files.FilesPanel(self.main.window, exaile.collection)+ self.panels['test'] = playlist2.TestPanel(self.main.window)- for panel in ('collection', 'radio', 'playlists', 'files'):+ for panel in ('collection', 'radio', 'playlists', 'files', 'test'): self.add_panel(*self.panels[panel].get_panel()) # add the device panels=== added file 'xlgui/panel/playlist2.py'--- xlgui/panel/playlist2.py 1970-01-01 00:00:00 +0000+++ xlgui/panel/playlist2.py 2010-08-13 01:29:37 +0000@@ -0,0 +1,352 @@+# Copyright (C) 2008-2010 Adam Olsen+#+# This program is free software; you can redistribute it and/or modify+# it under the terms of the GNU General Public License as published by+# the Free Software Foundation; either version 2, or (at your option)+# any later version.+#+# This program 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 General Public License for more details.+#+# You should have received a copy of the GNU General Public License+# along with this program; if not, write to the Free Software+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.+#+#+# The developers of the Exaile media player hereby grant permission+# for non-GPL compatible GStreamer and Exaile plugins to be used and+# distributed together with GStreamer and Exaile. This permission is+# above and beyond the permissions granted by the GPL license by which+# Exaile is covered. If you modify this code, you may extend this+# exception to your version of the code, but you are not obligated to+# do so. If you do not wish to do so, delete this exception statement+# from your version.++import glib+import gobject+import gtk+import pango++from xl import common, settings++from xlgui import panel, icons++import logging+logger = logging.getLogger(__name__)++class BaseFolderTreeNode(gobject.GObject):+ __gsignals__ = {+ 'activated': (+ gobject.SIGNAL_RUN_LAST,+ gobject.TYPE_NONE,+ tuple()+ ),+ 'children-changed': (+ gobject.SIGNAL_RUN_LAST,+ gobject.TYPE_NONE,+ tuple()+ ),+ 'icon-changed': (+ gobject.SIGNAL_RUN_LAST,+ gobject.TYPE_NONE,+ (gtk.gdk.Pixbuf,)+ ),+ 'title-changed': (+ gobject.SIGNAL_RUN_LAST,+ gobject.TYPE_NONE,+ (str,)+ )+ }++ def __init__(self, icon, title, data):+ """+ icon: icon for the node+ title: display name for the node+ data: arbitrary data associated with the node+ """+ gobject.GObject.__init__(self)+ self._icon = icon+ self._title = title+ self.data = data+ self.draggable = False+ self.expand_by_default = False+ self.context_menu = None++ def get_icon(self):+ return self._icon++ def set_icon(self, icon):+ self._icon = icon+ self.emit('icon-changed', icon)++ icon = property(get_icon, set_icon)++ def get_title(self):+ return self._title++ def set_title(self, title):+ self._title = title+ self.emit('title-changed', title)++ title = property(get_title, set_title)++ def has_children(self):+ raise NotImplementedError++ def get_children(self):+ raise NotImplementedError++ def activate(self):+ self.emit('activated')++class FolderTreeNode(BaseFolderTreeNode):+ def __init__(self, icon, title, data):+ BaseFolderTreeNode.__init__(self, icon, title, data)+ self._children = []++ def append_child(self, child):+ self._children.append(child)+ self.emit('children-changed')++ def insert_child(self, index, child):+ self._children.insert(index, child)+ self.emit('children-changed')++ def remove_child(self, child):+ self._children.remove(child)+ self.emit('children-changed')++ def has_children(self):+ return len(self._children) > 0++ def get_children(self):+ return self._children[:]++class TracksFolderTreeNode(FolderTreeNode):+ def __init__(self, icon, title, data):+ FolderTreeNode.__init__(self, icon, title, data)++ def get_tracks(self):+ raise NotImplementedError++class FolderTree(gtk.TreeView):+ store_columns = (gtk.gdk.Pixbuf, gobject.TYPE_STRING, object)+ def __init__(self):+ gtk.TreeView.__init__(self)+ self.model = None+ self.root = FolderTreeNode(None, None, None)+ self.rebuild()++ pb = gtk.CellRendererPixbuf()+ cell = gtk.CellRendererText()+ if settings.get_option('gui/ellipsize_text_in_panels', False):+ cell.set_property('ellipsize-set', True)+ cell.set_property('ellipsize', pango.ELLIPSIZE_END)+ col = gtk.TreeViewColumn('Text')+ col.pack_start(pb, False)+ col.pack_start(cell, True)+ col.set_attributes(pb, pixbuf=0)+ col.set_attributes(cell, text=1)+ self.append_column(col)++ self.set_headers_visible(False)+ self.connect('row-expanded', self.on_row_expanded)+ self.connect('row-activated', self.on_row_activated)++ gobject.add_emission_hook(BaseFolderTreeNode, 'title-changed',+ self.on_node_title_changed)+ gobject.add_emission_hook(BaseFolderTreeNode, 'icon-changed',+ self.on_node_icon_changed)+ gobject.add_emission_hook(BaseFolderTreeNode, 'children-changed',+ self.on_node_children_changed)++ self.loading_icon = icons.MANAGER.pixbuf_from_icon_name('reload')+ self.error_icon = icons.MANAGER.pixbuf_from_icon_name('error')++ def rebuild(self):+ """+ Rebuilds the TreeStore from the root node.+ """+ model = gtk.TreeStore(*self.store_columns)+ self.model = model+ self.set_model(model)+ self._add_children(model, self.root, None)+++ def _add_children(self, model, node, parent):+ """+ Adds the children of the given node to the TreeStore,+ removing placeholders as needed.++ Generally should not be called directly - on_row_expanded+ will call this automatically as needed.+ """+ if parent:+ parent_path = model.get_path(parent)+ else:+ parent_path = None+ current_child = model.iter_children(parent)+ if current_child is not None:+ if model.get_value(current_child, 2) is not None:+ raise Exception, "Cannot add children to node %s: it already has children." % repr(node)+ model.set_value(current_child, 0, self.loading_icon)+ model.set_value(current_child, 1, "Loading...")+ model.set_value(current_child, 2, "Dummy string so we don't load twice")++ self.__add_children_getter(model, node, parent_path)++ @common.threaded+ def __add_children_getter(self, model, node, parent_path):+ try:+ children = node.get_children()+ except:+ # TODO: allow get_children() to raise a particular type of+ # exception that contains a message we can display to the+ # user, in the same place where Loading... is.+ common.log_exception(log=logger)+ if parent_path:+ parent = model.get_iter(parent_path)+ else:+ parent = None+ current_child = model.iter_children(parent)+ if current_child is not None:+ model.set_value(current_child, 0, self.error_icon)+ model.set_value(current_child, 1, "Failed to load.")+ model.set_value(current_child, 2, None)+ else:+ glib.idle_add(self.__add_children, model, node, parent_path, children)++ def __add_children(self, model, node, parent_path, children):+ if parent_path:+ parent = model.get_iter(parent_path)+ else:+ parent = None+ prev = None+ remove_first = False+ current_child = model.iter_children(parent)+ if current_child is not None:+ prev = current_child+ remove_first = True+ for child in children:+ iter = model.insert_after(parent, prev,+ row=[child.icon, child.title, child])+ if child.has_children():+ model.insert(iter, 0, row=[None, "<FIXME>", None])+ if child.expand_by_default:+ self.expand_row(model.get_path(iter), open_all=False)+ prev = iter+ if remove_first:+ model.remove(model.iter_children(parent))++ def __get_node_iter_filter(self, model, path, iter, res):+ if model.get_value(iter, 2) == res[0]:+ res.append(iter)+ return True++ # FIXME: this is really inefficient - if we keep parent refs in+ # Nodes, then we can fix this to walk up the Node tree, then back+ # down the Gtk tree, which should be rather faster on large trees.+ def get_node_iter(self, node):+ res = [node]+ self.model.foreach(self.__get_node_iter_filter, res)+ if len(res) == 2:+ return res[1]+ return None++ def on_node_title_changed(self, node, title):+ iter = self.get_node_iter(node)+ if iter:+ self.model.set_value(iter, 1, title)+ return True++ def on_node_icon_changed(self, node, icon):+ iter = self.get_node_iter(node)+ if iter:+ self.model.set_value(iter, 0, icon)+ return True++ def on_node_children_changed(self, node):+ if node == self.root:+ self.rebuild()+ iter = self.get_node_iter(node)+ if iter:+ child = self.model.iter_children(iter)+ if child is not None:+ while self.model.remove(child):+ pass+ self._add_children(self.model, node, iter)+ return True++ def on_row_expanded(self, view, iter, path):+ """+ Called when a row is expanded in the tree, used to lazily+ load additional nodes into the tree.+ """+ model = self.model+ child = model.iter_children(iter)+ if model.get_value(child, 2) == None:+ parent_node = model.get_value(iter, 2)+ self._add_children(model, parent_node, iter)++ def on_row_activated(self, view, path, column):+ """+ Called when a row is activated in the tree.+ """+ iter = self.model.get_iter(path)+ node = self.model.get_value(iter, 2)+ if not isinstance(node, BaseFolderTreeNode):+ return+ node.activate()+++class TestPanel(panel.Panel):+ ui_info = ('playlists_panel.ui', 'PlaylistsPanelWindow')+ def __init__(self, parent):+ panel.Panel.__init__(self, parent)+ self.box = self.builder.get_object('playlists_box')+ self.tree = FolderTree()+++ foldericon = icons.MANAGER.pixbuf_from_icon_name('folder')+ documenticon = icons.MANAGER.pixbuf_from_icon_name('txt')+ t1 = FolderTreeNode(foldericon, "This is a test", None)+ t1.expand_by_default = True+ t1c1 = FolderTreeNode(documenticon, "Activate test.", None)+ def activate_cb(node):+ node.title = "Passed!"+ node.icon = icons.MANAGER.pixbuf_from_icon_name('gtk-apply')+ if node.get_children():+ node.remove_child(node.get_children()[0])+ else:+ t1c1c1 = FolderTreeNode(documenticon, "Passed!", None)+ node.append_child(t1c1c1)+ t1c1.connect('activated', activate_cb)+ t1.append_child(t1c1)+ t1c2 = FolderTreeNode(foldericon, "Asyncronous Load Test", None)+ t1c2c1 = FolderTreeNode(documenticon, "Passed!", None)+ t1c2.append_child(t1c2c1)+ getter = t1c2.get_children+ def delayed_getter(*args):+ import time+ time.sleep(2)+ return getter(*args)+ t1c2.get_children = delayed_getter+ t1.append_child(t1c2)+ t1c3 = FolderTreeNode(foldericon, "Load Failure Test", None)+ t1c3c1 = FolderTreeNode(documenticon, "Failed!", None)+ t1c3.append_child(t1c3c1)+ def error_getter(*args):+ raise ValueError, "Test!"+ t1c3.get_children = error_getter+ t1.append_child(t1c3)+ self.tree.root.append_child(t1)+ t2 = FolderTreeNode(documenticon, "Test part 2", None)+ self.tree.root.append_child(t2)+++ self.box.pack_start(self.tree)+ self.box.show_all()++