? plugins/magnatune/Makefile
? plugins/magnatune/Makefile.in
? plugins/magnatune/magnatune.rb-plugin
? plugins/magnatune/magnatune/Makefile
? plugins/magnatune/magnatune/Makefile.in
Index: bindings/python/rb.defs
===================================================================
RCS file: /cvs/gnome/rhythmbox/bindings/python/rb.defs,v
retrieving revision 1.33
diff -p -u -r1.33 rb.defs
--- bindings/python/rb.defs	30 Oct 2006 13:51:09 -0000	1.33
+++ bindings/python/rb.defs	29 Nov 2006 17:28:57 -0000
@@ -111,18 +111,6 @@
   )
 )
 
-(define-enum SourceListGroup
-  (in-module "RB")
-  (c-name "RBSourceListGroup")
-  (gtype-id "RB_TYPE_SOURCELIST_GROUP")
-  (values
-    '("fixed" "RB_SOURCELIST_GROUP_FIXED")
-    '("persistant" "RB_SOURCELIST_GROUP_PERSISTANT")
-    '("removable" "RB_SOURCELIST_GROUP_REMOVABLE")
-    '("transient" "RB_SOURCELIST_GROUP_TRANSIENT")
-  )
-)
-
 (define-enum EntryViewColumn
   (in-module "RB")
   (c-name "RBEntryViewColumn")
@@ -609,6 +597,50 @@
   )
 )
 
+
+;; From ../../sources/rb-source-group.h
+
+(define-enum SourceGroupCategory
+  (in-module "RB")
+  (c-name "RBSourceGroupCategory")
+  (gtype-id "RB_TYPE_SOURCE_GROUP_CATEGORY")
+  (values
+    '("fixed" "RB_SOURCE_GROUP_CATEGORY_FIXED")
+    '("persistant" "RB_SOURCE_GROUP_CATEGORY_PERSISTANT")
+    '("removable" "RB_SOURCE_GROUP_CATEGORY_REMOVABLE")
+    '("transient" "RB_SOURCE_GROUP_CATEGORY_TRANSIENT")
+  )
+)
+
+(define-boxed SourceGroup
+  (in-module "RB")
+  (c-name "RBSourceGroup")
+  (gtype-id "RB_TYPE_SOURCE_GROUP")
+  (fields
+    '("char*" "name" (access readwrite))
+    '("char*" "display_name" (access readwrite))
+    '("RBSourceGroupCategory" "category" (access readwrite))
+  )
+)
+
+(define-function rb_source_group_get_by_name
+  (c-name "rb_source_group_get_by_name")
+  (return-type "RBSourceGroup*")
+  (parameters
+    '("const-char*" "name")
+  )
+)
+
+(define-function rb_source_group_register
+  (c-name "rb_source_group_register")
+  (return-type "RBSourceGroup*")
+  (parameters
+    '("const-char*" "name")
+    '("const-char*" "display_name")
+    '("RBSourceGroupCategory" "category")
+  )
+)
+
 ;; From ../../sources/rb-source.h
 
 (define-function rb_source_get_type
@@ -624,8 +656,8 @@
   (properties
     '("name" (argname "name") (optional))
     '("icon" (argname "icon") (optional))
-    '("query-model" (argname "query-model") (optional))
-    '("sourcelist-group" (argname "sourcelist-group") (optional))
+    '("query-model" (argname "query_model") (optional))
+    '("source-group" (argname "source_group") (optional))
   )
 )
 
@@ -1540,14 +1572,14 @@
   (c-name "rb_browser_source_new")
   (return-type "RBBrowserSource*")
   (properties
-    '("entry-type" (argname "entry-type") (optional))
-    '("sorting-key" (argname "sorting-key") (optional))
+    '("entry-type" (argname "entry_type") (optional))
+    '("sorting-key" (argname "sorting_key") (optional))
 
     ; from RBSource
     '("name" (argname "name") (optional))
     '("icon" (argname "icon") (optional))
-    '("query-model" (argname "query-model") (optional))
-    '("sourcelist-group" (argname "sourcelist-group") (optional))
+    '("query-model" (argname "query_model") (optional))
+    '("source-group" (argname "source_group") (optional))
   )
 )
 
Index: bindings/python/rhythmdb.override
===================================================================
RCS file: /cvs/gnome/rhythmbox/bindings/python/rhythmdb.override,v
retrieving revision 1.17
diff -p -u -r1.17 rhythmdb.override
--- bindings/python/rhythmdb.override	23 Nov 2006 12:51:28 -0000	1.17
+++ bindings/python/rhythmdb.override	29 Nov 2006 17:28:57 -0000
@@ -212,16 +212,18 @@ _rhythmdb_entry_foreach_func (RhythmDBEn
 	PyObject *py_entry;
 
 	py_entry = pyg_boxed_new (RHYTHMDB_TYPE_ENTRY, entry, TRUE, TRUE);
-	if (data->data)
+	if (data->data) {
 		args = Py_BuildValue ("(OO)", py_entry, data->data);
-	else
+	} else {
 		args = Py_BuildValue ("(O)", py_entry);
+	}
 
 	result = PyEval_CallObject (data->func, args);
 	Py_DECREF (py_entry);
 	Py_DECREF (args);
-	if (result != NULL)
+	if (result != NULL) {
 		Py_DECREF (result);
+	}
 }
 
 static PyObject*
Index: plugins/audioscrobbler/rb-lastfm-source.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/plugins/audioscrobbler/rb-lastfm-source.c,v
retrieving revision 1.5
diff -p -u -r1.5 rb-lastfm-source.c
--- plugins/audioscrobbler/rb-lastfm-source.c	12 Nov 2006 07:41:15 -0000	1.5
+++ plugins/audioscrobbler/rb-lastfm-source.c	29 Nov 2006 17:28:57 -0000
@@ -525,6 +525,7 @@ rb_lastfm_source_new (RBShell *shell)
 					  "shell", shell,
 					  "entry-type", entry_type,
 					  "proxy-config", proxy_config,
+					  "source-group", RB_SOURCE_GROUP_LIBRARY,
 					  NULL));
 	rb_shell_register_entry_type_for_source (shell, source, entry_type);
 
Index: plugins/generic-player/rb-generic-player-source.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/plugins/generic-player/rb-generic-player-source.c,v
retrieving revision 1.20
diff -p -u -r1.20 rb-generic-player-source.c
--- plugins/generic-player/rb-generic-player-source.c	6 Nov 2006 22:04:25 -0000	1.20
+++ plugins/generic-player/rb-generic-player-source.c	29 Nov 2006 17:28:57 -0000
@@ -302,7 +302,7 @@ rb_generic_player_source_new (RBShell *s
 							 "entry-type", entry_type,
 							 "volume", volume,
 							 "shell", shell,
-							 "sourcelist-group", RB_SOURCELIST_GROUP_REMOVABLE,
+							 "source-group", RB_SOURCE_GROUP_DEVICES,
 							 NULL));
 
 	rb_generic_player_source_get_device_info (source);
Index: plugins/generic-player/rb-nokia770-source.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/plugins/generic-player/rb-nokia770-source.c,v
retrieving revision 1.12
diff -p -u -r1.12 rb-nokia770-source.c
--- plugins/generic-player/rb-nokia770-source.c	10 Nov 2006 07:25:51 -0000	1.12
+++ plugins/generic-player/rb-nokia770-source.c	29 Nov 2006 17:28:57 -0000
@@ -90,7 +90,7 @@ rb_nokia770_source_new (RBShell *shell, 
 						   "entry-type", entry_type,
 						   "volume", volume,
 						   "shell", shell,
-						   "sourcelist-group", RB_SOURCELIST_GROUP_REMOVABLE,
+						   "source-group", RB_SOURCE_GROUP_DEVICES,
 						   NULL));
 
 	rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
Index: plugins/generic-player/rb-psp-source.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/plugins/generic-player/rb-psp-source.c,v
retrieving revision 1.13
diff -p -u -r1.13 rb-psp-source.c
--- plugins/generic-player/rb-psp-source.c	10 Nov 2006 07:25:51 -0000	1.13
+++ plugins/generic-player/rb-psp-source.c	29 Nov 2006 17:28:57 -0000
@@ -94,7 +94,7 @@ rb_psp_source_new (RBShell *shell, Gnome
 					  "entry-type", entry_type,
 					  "volume", volume,
 					  "shell", shell,
-					  "sourcelist-group", RB_SOURCELIST_GROUP_REMOVABLE,
+					  "source-group", RB_SOURCE_GROUP_DEVICES,
 					  NULL));
 
 	rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
Index: plugins/magnatune/magnatune/BuyAlbumHandler.py
===================================================================
RCS file: /cvs/gnome/rhythmbox/plugins/magnatune/magnatune/BuyAlbumHandler.py,v
retrieving revision 1.1
diff -p -u -r1.1 BuyAlbumHandler.py
--- plugins/magnatune/magnatune/BuyAlbumHandler.py	6 Oct 2006 10:14:38 -0000	1.1
+++ plugins/magnatune/magnatune/BuyAlbumHandler.py	29 Nov 2006 17:28:57 -0000
@@ -1,8 +1,27 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2006 Adam Zimmerman  <adam_zimmerman@sfu.ca>
+# Copyright (C) 2006 James Livingston  <doclivingston@gmail.com>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+
 import gnomevfs
 import xml.sax, xml.sax.handler
 
 class BuyAlbumHandler(xml.sax.handler.ContentHandler): # Class to download the track, etc.
-	
+
 	format_map =	{
 			'ogg'		:	'URL_OGGZIP',
 			'flac'		:	'URL_FLACZIP',
@@ -10,14 +29,14 @@ class BuyAlbumHandler(xml.sax.handler.Co
 			'mp3-cbr'	:	'URL_128KMP3ZIP',
 			'mp3-vbr'	:	'URL_VBRZIP'
 			}
-	
+
 	def __init__(self, format):
 		xml.sax.handler.ContentHandler.__init__(self)
 		self._format_tag = self.format_map[format] # format of audio to download
-	
+
 	def startElement(self, name, attrs):
 		self._text = ""
-	
+
 	def endElement(self, name):
 		if name == "ERROR": # Something went wrong. Display error message to user.
 			raise MagnatunePurchaseError(self._text)
@@ -27,7 +46,7 @@ class BuyAlbumHandler(xml.sax.handler.Co
 			self.password = self._text
 		elif name == self._format_tag:
 			self.url = self._text
-	
+
 	def characters(self, content):
 		self._text = self._text + content
 
Index: plugins/magnatune/magnatune/MagnatuneSource.py
===================================================================
RCS file: /cvs/gnome/rhythmbox/plugins/magnatune/magnatune/MagnatuneSource.py,v
retrieving revision 1.5
diff -p -u -r1.5 MagnatuneSource.py
--- plugins/magnatune/magnatune/MagnatuneSource.py	26 Nov 2006 13:19:35 -0000	1.5
+++ plugins/magnatune/magnatune/MagnatuneSource.py	29 Nov 2006 17:28:57 -0000
@@ -1,3 +1,22 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2006 Adam Zimmerman  <adam_zimmerman@sfu.ca>
+# Copyright (C) 2006 James Livingston  <doclivingston@gmail.com>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+
 import rb, rhythmdb
 from TrackListHandler import TrackListHandler
 from BuyAlbumHandler import BuyAlbumHandler, MagnatunePurchaseError
@@ -16,15 +35,13 @@ magnatune_song_info_uri = gnomevfs.URI("
 local_song_info_uri = gnomevfs.URI(magnatune_dir + "song_info.xml")
 local_song_info_temp_uri = gnomevfs.URI(magnatune_dir + "song_info.xml.zip.tmp")
 
-
 class MagnatuneSource(rb.BrowserSource):
 	__gproperties__ = {
 		'plugin': (rb.Plugin, 'plugin', 'plugin', gobject.PARAM_WRITABLE|gobject.PARAM_CONSTRUCT_ONLY),
 	}
 
 	__client = gconf.client_get_default()
-	
-	
+
 	def __init__(self):
 
 		rb.BrowserSource.__init__(self, name=_("Magnatune"))
@@ -44,12 +61,12 @@ class MagnatuneSource(rb.BrowserSource):
 		self.__load_handle = None
 		self.__load_current_size = 0
 		self.__load_total_size = 1
-		
+
 		self.__downloads = {} # keeps track of amount downloaded for each file
 		self.__downloading = False # keeps track of whether we are currently downloading an album
 		self.__download_progress = 0.0 # progress of current download(s)
 		self.purchase_filesize = 0 # total amount of bytes to download
-	
+
 	def do_set_property(self, property, value):
 		if property.name == 'plugin':
 			self.__plugin = value
@@ -63,7 +80,6 @@ class MagnatuneSource(rb.BrowserSource):
 		else:
 			raise AttributeError, 'unknown property %s' % property.name
 
-
 	#
 	# RBSource methods
 	#
@@ -103,17 +119,17 @@ class MagnatuneSource(rb.BrowserSource):
 			# start our catalogue updates
 			self.__update_id = gobject.timeout_add(6 * 60 * 60 * 1000, self.__update_catalogue)
 			self.__update_catalogue()
-			
+
 			self.get_entry_view().set_sorting_type(self.__client.get_string("/apps/rhythmbox/plugins/magnatune/sorting"))
 
 		rb.BrowserSource.do_impl_activate (self)
-	
+
 #	def do_impl_get_browser_key (self):
 #		return "/apps/rhythmbox/plugins/magnatune/show_browser"
 #
 #	def do_impl_get_paned_key (self):
 #		return "/apps/rhythmbox/plugins/magnatune/paned_position"
-	
+
 	def do_impl_delete_thyself(self):
 		if self.__update_id != 0:
 			gobject.source_remove (self.__update_id)
@@ -126,17 +142,15 @@ class MagnatuneSource(rb.BrowserSource):
 		if self.__xfer_handle is not None:
 			self.__xfer_handle.close(lambda handle, exc: None) #FIXME: report it?
 			self.__xfer_handle = None
-		
+
 		self.__client.set_string("/apps/rhythmbox/plugins/magnatune/sorting", self.get_entry_view().get_sorting_type())
-		
-		rb.BrowserSource.do_impl_delete_thyself (self)
 
+		rb.BrowserSource.do_impl_delete_thyself (self)
 
 	#
 	# methods for use by plugin and UI
 	#
 
-
 	def display_artist_info(self):
 		tracks = self.get_entry_view().get_selected_entries()
 		urls = set([])
@@ -157,7 +171,7 @@ class MagnatuneSource(rb.BrowserSource):
 				continue
 			skus.append(sku)
 			artist = self.__db.entry_get(track, rhythmdb.PROP_ARTIST)
-			album = self.__db.entry_get(track, rhythmdb.PROP_ALBUM)	
+			album = self.__db.entry_get(track, rhythmdb.PROP_ALBUM)
 
 			gladexml = gtk.glade.XML(self.__plugin.find_file("magnatune-purchase.glade"))
 			pay_combo = gladexml.get_widget("pay_combobox")
@@ -167,7 +181,7 @@ class MagnatuneSource(rb.BrowserSource):
 			pay_combo.set_active(self.__client.get_int(self.__plugin.gconf_keys['pay']) - 5)
 			format_combo.set_active(self.__plugin.format_list.index(self.__client.get_string(self.__plugin.gconf_keys['format'])))
 			text_label.set_text(_("Would you like to purchase the album '%s' by '%s'.") % (album, artist))
-			
+
 			if self.__client.get_bool(self.__plugin.gconf_keys['forget']):
 				gladexml.get_widget("name_label").visible = True
 				gladexml.get_widget("name_entry").visible = True
@@ -185,7 +199,7 @@ class MagnatuneSource(rb.BrowserSource):
 				gladexml.get_widget("mm_combobox").show()
 				gladexml.get_widget("yy_label").show()
 				gladexml.get_widget("yy_entry").show()
-			
+
 			window = gladexml.get_widget("purchase_dialog")
 			if window.run() == gtk.RESPONSE_ACCEPT:
 				if self.__client.get_bool(self.__plugin.gconf_keys['forget']):
@@ -206,7 +220,7 @@ class MagnatuneSource(rb.BrowserSource):
 				# successfully loaded
 				gtk.gdk.threads_enter()
 				self.__show_loading_screen (False)
-				
+
 				in_progress_dir = gnomevfs.DirectoryHandle(gnomevfs.URI(magnatune_dir))
 				in_progress = in_progress_dir.next()
 				while True:
@@ -224,7 +238,7 @@ class MagnatuneSource(rb.BrowserSource):
 			else:
 				# error reading file
 				raise exc_type
-			
+
 			parser.close()
 			handle.close(lambda handle, exc: None) # FIXME: report it?
 			self.__load_handle = None
@@ -241,7 +255,7 @@ class MagnatuneSource(rb.BrowserSource):
 		if exc_type:
 			self.__load_handle = None
 			self.__notify_status_changed()
-			
+
 			if gnomevfs.exists(local_song_info_uri):
 				raise exc_type
 			else:
@@ -255,7 +269,6 @@ class MagnatuneSource(rb.BrowserSource):
 		self.__notify_status_changed()
 		self.__load_handle = gnomevfs.async.open (local_song_info_uri, self.__load_catalogue_open_cb)
 
-
 	def __download_update_cb (self, _reserved, info, moving):
 		if info.phase == gnomevfs.XFER_PHASE_COMPLETED:
 			# done downloading, unzip to real location
@@ -350,7 +363,6 @@ class MagnatuneSource(rb.BrowserSource):
 		if self.__notify_id == 0:
 			self.__notify_id = gobject.idle_add(change_idle_cb)
 
-
 	#
 	# internal purchasing code
 	#
@@ -360,7 +372,7 @@ class MagnatuneSource(rb.BrowserSource):
 		ccmonth = self.__client.get_string(self.__plugin.gconf_keys['ccmonth'])
 		name = self.__client.get_string(self.__plugin.gconf_keys['ccname'])
 		email = self.__client.get_string(self.__plugin.gconf_keys['email'])
-		
+
 		if self.__client.get_bool(self.__plugin.gconf_keys['forget']):
 			ccnumber = self.__tmp_cc
 			ccyear = self.__tmp_yy
@@ -530,8 +542,6 @@ def create_if_needed(uri, mode):
 	else:
 		out = gnomevfs.open(uri, open_mode=mode)
 	return out
-
-
 
 class URIIterator:
 	def __init__(self, uri):
Index: plugins/magnatune/magnatune/TrackListHandler.py
===================================================================
RCS file: /cvs/gnome/rhythmbox/plugins/magnatune/magnatune/TrackListHandler.py,v
retrieving revision 1.1
diff -p -u -r1.1 TrackListHandler.py
--- plugins/magnatune/magnatune/TrackListHandler.py	6 Oct 2006 10:14:38 -0000	1.1
+++ plugins/magnatune/magnatune/TrackListHandler.py	29 Nov 2006 17:28:57 -0000
@@ -1,9 +1,28 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2006 Adam Zimmerman  <adam_zimmerman@sfu.ca>
+# Copyright (C) 2006 James Livingston  <doclivingston@gmail.com>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+
 import rhythmdb
 import xml.sax, xml.sax.handler
 import datetime
 
 class TrackListHandler(xml.sax.handler.ContentHandler):
-	
+
 	def __init__(self, db, entry_type, sku_dict, home_dict):
 		xml.sax.handler.ContentHandler.__init__(self)
 		self.__db = db
@@ -11,10 +30,10 @@ class TrackListHandler(xml.sax.handler.C
 		self.__sku_dict = sku_dict
 		self.__home_dict = home_dict
 		self.__track = {}
-	
+
 	def startElement(self, name, attrs):
 		self.__text = ""
-	
+
 	def endElement(self, name):
 		if name == "Track":
 			try:
@@ -24,7 +43,7 @@ class TrackListHandler(xml.sax.handler.C
 					entry = self.__db.entry_new(self.__entry_type, self.__track['url'])
 
 				date = datetime.date(int(self.__track['launchdate'][0:4]), 1, 1).toordinal() # year is sometimes 0, so we use launchdate
-				
+
 				self.__db.set(entry, rhythmdb.PROP_ARTIST, self.__track['artist'])
 				self.__db.set(entry, rhythmdb.PROP_ALBUM, self.__track['albumname'])
 				self.__db.set(entry, rhythmdb.PROP_TITLE, self.__track['trackname'])
@@ -34,16 +53,16 @@ class TrackListHandler(xml.sax.handler.C
 				self.__db.set(entry, rhythmdb.PROP_DURATION, int(self.__track['seconds']))
 				self.__sku_dict[self.__track['url']] = self.__track['albumsku']
 				self.__home_dict[self.__track['url']] = self.__track['home']
-				
+
 				self.__db.commit()
 			except Exception,e: # This happens on duplicate uris being added
 				print "Couldn't add %s - %s" % (self.__track['artist'], self.__track['trackname']), e
-				
+
 			self.__track = {}
 		elif name == "AllSongs":
 			pass # end of the file
 		else:
 			self.__track[name] = self.__text
-	
+
 	def characters(self, content):
 		self.__text = self.__text + content
Index: plugins/magnatune/magnatune/__init__.py
===================================================================
RCS file: /cvs/gnome/rhythmbox/plugins/magnatune/magnatune/__init__.py,v
retrieving revision 1.2
diff -p -u -r1.2 __init__.py
--- plugins/magnatune/magnatune/__init__.py	11 Nov 2006 01:08:20 -0000	1.2
+++ plugins/magnatune/magnatune/__init__.py	29 Nov 2006 17:28:57 -0000
@@ -1,3 +1,22 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2006 Adam Zimmerman  <adam_zimmerman@sfu.ca>
+# Copyright (C) 2006 James Livingston  <doclivingston@gmail.com>
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+
 import rhythmdb, rb
 import gobject
 import gtk, gtk.glade
@@ -11,7 +30,6 @@ import datetime
 
 from MagnatuneSource import MagnatuneSource
 
-
 popup_ui = """
 <ui>
   <popup name="MagnatuneSourceViewPopup">
@@ -29,7 +47,6 @@ popup_ui = """
 </ui>
 """
 
-
 class Magnatune(rb.Plugin):
 	client = gconf.client_get_default()
 
@@ -46,27 +63,36 @@ class Magnatune(rb.Plugin):
 		'forget': "/apps/rhythmbox/plugins/magnatune/forget"
 	}
 
-
 	#
 	# Core methods
 	#
-	
+
 	def __init__(self):
 		rb.Plugin.__init__(self)
-		
+
 	def activate(self, shell):
 		self.shell = shell # so buy_track can update the progress bar
 		self.db = shell.get_property("db")
 
+		group = rb.rb_source_group_get_by_name ("stores")
+		if not group:
+			group = rb.rb_source_group_register ("stores",
+							     _("Stores"),
+							     rb.SOURCE_GROUP_CATEGORY_PERSISTANT)
+
 		self.entry_type = self.db.entry_register_type("MagnatuneEntryType")
 		# allow changes which don't do anything
 		self.entry_type.can_sync_metadata = True
 		self.entry_type.sync_metadata = None
 
-		self.source = gobject.new (MagnatuneSource, shell=shell, entry_type=self.entry_type, plugin=self)
+		self.source = gobject.new (MagnatuneSource,
+					   shell=shell,
+					   entry_type=self.entry_type,
+					   source_group=group,
+					   plugin=self)
 		shell.register_entry_type_for_source(self.source, self.entry_type)
 		shell.append_source(self.source, None) # Add the source to the list
-		
+
 		manager = shell.get_player().get_property('ui-manager')
 		# Add the popup menu actions
 		action = gtk.Action('MagnatunePurchaseAlbum', _('Purchase Album'),
@@ -90,7 +116,7 @@ class Magnatune(rb.Plugin):
 		manager.insert_action_group(self.action_group, 0)
 		self.ui_id = manager.add_ui_from_string(popup_ui)
 		manager.ensure_update()
-	
+
 	def deactivate(self, shell):
 		manager = shell.get_player().get_property('ui-manager')
 		manager.remove_ui (self.ui_id)
@@ -104,10 +130,10 @@ class Magnatune(rb.Plugin):
 		self.source.delete_thyself()
 		self.source = None
 		self.shell = None
-	
+
 	def create_configure_dialog(self, dialog=None):
 		if dialog == None:
-			
+
 			def ignore_checkbox_toggled(button):
 				if button.get_active():
 					gladexml.get_widget("name_entry").set_sensitive(False)
@@ -125,7 +151,7 @@ class Magnatune(rb.Plugin):
 					gladexml.get_widget("mm_entry").set_sensitive(True)
 					gladexml.get_widget("yy_entry").set_sensitive(True)
 					self.client.set_bool(self.gconf_keys['forget'], False)
-			
+
 			def yy_entry_changed(entry):
 				self.client.set_string(self.gconf_keys['ccyear'], entry.get_text())
 				try:
@@ -141,7 +167,7 @@ class Magnatune(rb.Plugin):
 						gladexml.get_widget("cc_expired_label").hide()
 				except Exception,e:
 					print e
-			
+
 			def mm_entry_changed(entry):
 				self.client.set_string(self.gconf_keys['ccmonth'], entry.get_active_text())
 				try:
@@ -157,7 +183,7 @@ class Magnatune(rb.Plugin):
 						gladexml.get_widget("cc_expired_label").hide()
 				except Exception,e:
 					print e
-			
+
 			self.configure_callback_dic = {
 				"rb_magnatune_name_entry_changed_cb" : lambda w: self.client.set_string(self.gconf_keys['ccname'], w.get_text()),
 				"rb_magnatune_email_entry_changed_cb" : lambda w: self.client.set_string(self.gconf_keys['email'], w.get_text()),
@@ -175,7 +201,7 @@ class Magnatune(rb.Plugin):
 			# FIXME this bit should be in glade too
 			dialog = gladexml.get_widget('preferences_dialog')
 			dialog.connect("response", lambda w, r: w.hide())
-			
+
 			gladexml.get_widget("name_entry").set_text(self.client.get_string(self.gconf_keys['ccname']))
 			gladexml.get_widget("email_entry").set_text(self.client.get_string(self.gconf_keys['email']))
 			gladexml.get_widget("cc_entry").set_text(self.client.get_string(self.gconf_keys['ccnumber']))
Index: shell/rb-playlist-manager.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/shell/rb-playlist-manager.c,v
retrieving revision 1.116
diff -p -u -r1.116 rb-playlist-manager.c
--- shell/rb-playlist-manager.c	22 Nov 2006 03:06:43 -0000	1.116
+++ shell/rb-playlist-manager.c	29 Nov 2006 17:28:58 -0000
@@ -788,6 +788,38 @@ rb_playlist_manager_save_data (struct RB
 	return NULL;
 }
 
+static gboolean
+model_foreach_func (GtkTreeModel *model,
+		    GtkTreePath  *path,
+		    GtkTreeIter  *iter,
+		    xmlNodePtr    root)
+{
+	RBSource *source;
+	gboolean  local;
+
+	gtk_tree_model_get (model,
+			    iter,
+			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source,
+			    -1);
+	if (source == NULL) {
+		goto out;
+	}
+	if (RB_IS_PLAYLIST_SOURCE (source) == FALSE) {
+		goto out;
+	}
+
+	g_object_get (source, "is-local", &local, NULL);
+	if (local) {
+		rb_playlist_source_save_to_xml (RB_PLAYLIST_SOURCE (source), root);
+	}
+ out:
+	if (source != NULL) {
+		g_object_unref (source);
+	}
+
+	return FALSE;
+}
+
 /**
  * rb_playlist_manager_save_playlists
  * @mgr: the #RBPlaylistManager
@@ -806,7 +838,6 @@ rb_playlist_manager_save_playlists (RBPl
 {
 	xmlNodePtr root;
 	struct RBPlaylistManagerSaveData *data;
-	GtkTreeIter iter;
 	GtkTreeModel *fmodel;
 	GtkTreeModel *model;
 
@@ -832,31 +863,7 @@ rb_playlist_manager_save_playlists (RBPl
 	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (fmodel));
 	g_object_unref (fmodel);
 
-	if (gtk_tree_model_get_iter_first (model, &iter)) {
-		do {
-			RBSource *source;
-			gboolean local;
-
-			gtk_tree_model_get (model,
-					    &iter,
-					    RB_SOURCELIST_MODEL_COLUMN_SOURCE,
-					    &source,
-					    -1);
-			if (source == NULL) {
-				continue;
-			}
-			if (RB_IS_PLAYLIST_SOURCE (source) == FALSE) {
-				g_object_unref (source);
-				continue;
-			}
-
-			g_object_get (source, "is-local", &local, NULL);
-			if (local)
-				rb_playlist_source_save_to_xml (RB_PLAYLIST_SOURCE (source), root);
-
-			g_object_unref (source);
-		} while (gtk_tree_model_iter_next (model, &iter));
-	}
+	gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc)model_foreach_func, root);
 
 	/* mark clean here.  if the save fails, we'll mark it dirty again */
 	rb_playlist_manager_set_dirty (data->mgr, FALSE);
Index: shell/rb-shell.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/shell/rb-shell.c,v
retrieving revision 1.484
diff -p -u -r1.484 rb-shell.c
--- shell/rb-shell.c	28 Nov 2006 13:48:53 -0000	1.484
+++ shell/rb-shell.c	29 Nov 2006 17:28:58 -0000
@@ -1166,9 +1166,9 @@ construct_sources (RBShell *shell)
 	shell->priv->podcast_source = RB_PODCAST_SOURCE (rb_podcast_source_new (shell));
 	rb_shell_append_source (shell, RB_SOURCE (shell->priv->podcast_source), NULL);
 	shell->priv->missing_files_source = rb_missing_files_source_new (shell, shell->priv->library_source);
-	rb_shell_append_source (shell, shell->priv->missing_files_source, RB_SOURCE (shell->priv->library_source));
+	rb_shell_append_source (shell, shell->priv->missing_files_source, NULL);
 	shell->priv->import_errors_source = rb_import_errors_source_new (shell, shell->priv->library_source);
-	rb_shell_append_source (shell, shell->priv->import_errors_source, RB_SOURCE (shell->priv->library_source));
+	rb_shell_append_source (shell, shell->priv->import_errors_source, NULL);
 
 	/* Initialize playlist manager */
 	rb_debug ("shell: creating playlist manager");
@@ -1299,6 +1299,7 @@ rb_shell_constructor (GType type,
 					     shell);
 
 	construct_db (shell);
+	rb_source_group_init ();
 
 	/* initialize shell services */
 	construct_widgets (shell);
Index: sources/Makefile.am
===================================================================
RCS file: /cvs/gnome/rhythmbox/sources/Makefile.am,v
retrieving revision 1.48
diff -p -u -r1.48 Makefile.am
--- sources/Makefile.am	9 Nov 2006 11:47:52 -0000	1.48
+++ sources/Makefile.am	29 Nov 2006 17:28:58 -0000
@@ -1,11 +1,15 @@
-## arch-tag: Automake rules for source object library
+NULL =
+
 noinst_LTLIBRARIES = libsources.la libsourcesimpl.la
 
 libsources_la_SOURCES = 		\
 	rb-source.c 			\
 	rb-source.h			\
 	rb-streaming-source.c		\
-	rb-streaming-source.h
+	rb-streaming-source.h		\
+	rb-source-group.c		\
+	rb-source-group.h		\
+	$(NULL)
 
 libsourcesimpl_la_SOURCES =		\
 	rb-sourcelist.c			\
@@ -32,7 +36,8 @@ libsourcesimpl_la_SOURCES =		\
 	rb-missing-files-source.c	\
 	rb-missing-files-source.h	\
 	rb-import-errors-source.c	\
-	rb-import-errors-source.h
+	rb-import-errors-source.h	\
+	$(NULL)
 
 INCLUDES =						\
         -DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
Index: sources/rb-audiocd-source.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/sources/rb-audiocd-source.c,v
retrieving revision 1.47
diff -p -u -r1.47 rb-audiocd-source.c
--- sources/rb-audiocd-source.c	30 Oct 2006 11:33:37 -0000	1.47
+++ sources/rb-audiocd-source.c	29 Nov 2006 17:28:58 -0000
@@ -182,7 +182,7 @@ rb_audiocd_source_new (RBPlugin *plugin,
 			       "volume", volume,
 			       "shell", shell,
 			       "sorting-key", NULL,
-			       "sourcelist-group", RB_SOURCELIST_GROUP_REMOVABLE,
+			       "source-group", RB_SOURCE_GROUP_DEVICES,
 			       "plugin", plugin,
 			       NULL);
 
Index: sources/rb-auto-playlist-source.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/sources/rb-auto-playlist-source.c,v
retrieving revision 1.30
diff -p -u -r1.30 rb-auto-playlist-source.c
--- sources/rb-auto-playlist-source.c	22 Nov 2006 03:06:44 -0000	1.30
+++ sources/rb-auto-playlist-source.c	29 Nov 2006 17:28:58 -0000
@@ -252,7 +252,7 @@ rb_auto_playlist_source_new (RBShell *sh
 					"shell", shell,
 					"is-local", local,
 					"entry-type", RHYTHMDB_ENTRY_TYPE_SONG,
-					"sourcelist-group", RB_SOURCELIST_GROUP_PERSISTANT,
+					"source-group", RB_SOURCE_GROUP_PLAYLISTS,
 					NULL));
 }
 
Index: sources/rb-daap-source.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/sources/rb-daap-source.c,v
retrieving revision 1.47
diff -p -u -r1.47 rb-daap-source.c
--- sources/rb-daap-source.c	9 Nov 2006 11:47:52 -0000	1.47
+++ sources/rb-daap-source.c	29 Nov 2006 17:28:58 -0000
@@ -289,7 +289,7 @@ rb_daap_source_new (RBShell *shell,
 					  "visibility", TRUE,
 					  "sorting-key", CONF_STATE_SORTING,
 					  "password-protected", password_protected,
-					  "sourcelist-group", RB_SOURCELIST_GROUP_TRANSIENT,
+					  "source-group", RB_SOURCE_GROUP_SHARED,
 					  "plugin", RB_PLUGIN (plugin),
 					  NULL));
 
Index: sources/rb-ipod-source.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/sources/rb-ipod-source.c,v
retrieving revision 1.70
diff -p -u -r1.70 rb-ipod-source.c
--- sources/rb-ipod-source.c	19 Nov 2006 09:19:41 -0000	1.70
+++ sources/rb-ipod-source.c	29 Nov 2006 17:28:58 -0000
@@ -221,11 +221,11 @@ rb_ipod_source_new (RBShell *shell,
 	g_object_unref (db);
 
 	source = RB_IPOD_SOURCE (g_object_new (RB_TYPE_IPOD_SOURCE,
-					  "entry-type", entry_type,
-					  "volume", volume,
-					  "shell", shell,
-					  "sourcelist-group", RB_SOURCELIST_GROUP_REMOVABLE,
-					  NULL));
+                                               "entry-type", entry_type,
+                                               "volume", volume,
+                                               "shell", shell,
+                                               "source-group", RB_SOURCE_GROUP_DEVICES,
+                                               NULL));
 
 	rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
 
Index: sources/rb-iradio-source.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/sources/rb-iradio-source.c,v
retrieving revision 1.133
diff -p -u -r1.133 rb-iradio-source.c
--- sources/rb-iradio-source.c	30 Oct 2006 13:51:11 -0000	1.133
+++ sources/rb-iradio-source.c	29 Nov 2006 17:28:58 -0000
@@ -442,6 +442,7 @@ rb_iradio_source_new (RBShell *shell)
 					  "name", _("Radio"),
 					  "shell", shell,
 					  "entry-type", entry_type,
+					  "source-group", RB_SOURCE_GROUP_LIBRARY,
 					  NULL));
 	rb_shell_register_entry_type_for_source (shell, source, entry_type);
 	return source;
Index: sources/rb-library-source.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/sources/rb-library-source.c,v
retrieving revision 1.186
diff -p -u -r1.186 rb-library-source.c
--- sources/rb-library-source.c	20 Nov 2006 10:35:10 -0000	1.186
+++ sources/rb-library-source.c	29 Nov 2006 17:28:58 -0000
@@ -308,12 +308,13 @@ rb_library_source_new (RBShell *shell)
 
 	gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &size, NULL);
 	icon = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
-					 "stock_music-library",
+					 "audio-x-generic",
 					 size,
 					 0, NULL);
 	source = RB_SOURCE (g_object_new (RB_TYPE_LIBRARY_SOURCE,
-					  "name", _("Library"),
+					  "name", _("Music"),
 					  "entry-type", entry_type,
+					  "source-group", RB_SOURCE_GROUP_LIBRARY,
 					  "sorting-key", CONF_STATE_LIBRARY_SORTING,
 					  "shell", shell,
 					  "icon", icon,
Index: sources/rb-play-queue-source.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/sources/rb-play-queue-source.c,v
retrieving revision 1.20
diff -p -u -r1.20 rb-play-queue-source.c
--- sources/rb-play-queue-source.c	14 Aug 2006 09:03:25 -0000	1.20
+++ sources/rb-play-queue-source.c	29 Nov 2006 17:28:58 -0000
@@ -244,6 +244,7 @@ rb_play_queue_source_new (RBShell *shell
 					"shell", shell,
 					"is-local", TRUE,
 					"entry-type", RHYTHMDB_ENTRY_TYPE_INVALID,
+					"source-group", RB_SOURCE_GROUP_LIBRARY,
 					NULL));
 }
 
Index: sources/rb-podcast-source.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/sources/rb-podcast-source.c,v
retrieving revision 1.81
diff -p -u -r1.81 rb-podcast-source.c
--- sources/rb-podcast-source.c	30 Oct 2006 13:51:11 -0000	1.81
+++ sources/rb-podcast-source.c	29 Nov 2006 17:28:58 -0000
@@ -812,6 +812,7 @@ rb_podcast_source_new (RBShell *shell)
 					  "name", _("Podcasts"),
 					  "shell", shell,
 					  "entry-type", RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
+					  "source-group", RB_SOURCE_GROUP_LIBRARY,
 					  NULL));
 
 	rb_shell_register_entry_type_for_source (shell, source,
Index: sources/rb-source-group.c
===================================================================
RCS file: sources/rb-source-group.c
diff -N sources/rb-source-group.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ sources/rb-source-group.c	29 Nov 2006 17:28:58 -0000
@@ -0,0 +1,157 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2006  Jonathan Matthew <jonathan@kaolin.wh9.net>
+ *  Copyright (C) 2006  William Jon McCann <mccann@jhu.edu>
+ *
+ *  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 of the License, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "rb-util.h"
+
+#include "rb-source-group.h"
+
+G_LOCK_DEFINE_STATIC (source_groups);
+
+static GHashTable *source_groups_map;
+
+static RBSourceGroup *library_group = NULL;
+static RBSourceGroup *playlists_group = NULL;
+static RBSourceGroup *devices_group = NULL;
+static RBSourceGroup *shared_group = NULL;
+
+static void
+register_core_source_groups (void)
+{
+	library_group = rb_source_group_register ("library", _("Library"), RB_SOURCE_GROUP_CATEGORY_FIXED);
+	playlists_group = rb_source_group_register ("playlists", _("Playlists"), RB_SOURCE_GROUP_CATEGORY_PERSISTANT);
+	devices_group = rb_source_group_register ("devices", _("Devices"), RB_SOURCE_GROUP_CATEGORY_REMOVABLE);
+	shared_group = rb_source_group_register ("shared", _("Shared"), RB_SOURCE_GROUP_CATEGORY_TRANSIENT);
+}
+
+void
+rb_source_group_init (void)
+{
+	G_LOCK (source_groups);
+	if (source_groups_map == NULL) {
+		source_groups_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	}
+	G_UNLOCK (source_groups);
+
+	register_core_source_groups ();
+}
+
+GType
+rb_source_group_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		type = g_boxed_type_register_static ("RBSourceGroup",
+						     (GBoxedCopyFunc)rb_copy_function,
+						     (GBoxedFreeFunc)rb_null_function);
+	}
+
+	return type;
+}
+
+#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
+GType
+rb_source_group_category_get_type (void)
+{
+	static GType etype = 0;
+
+	if (etype == 0) {
+		static const GEnumValue values[] = {
+			ENUM_ENTRY (RB_SOURCE_GROUP_CATEGORY_FIXED, "Fixed single instance source"),
+			ENUM_ENTRY (RB_SOURCE_GROUP_CATEGORY_PERSISTANT, "Persistant multiple-instance source"),
+			ENUM_ENTRY (RB_SOURCE_GROUP_CATEGORY_REMOVABLE, "Source representing a removable device"),
+			ENUM_ENTRY (RB_SOURCE_GROUP_CATEGORY_TRANSIENT, "Transient source (eg. network shares)"),
+			{ 0, 0, 0 }
+		};
+
+		etype = g_enum_register_static ("RBSourcelistGroupType", values);
+	}
+
+	return etype;
+}
+
+RBSourceGroup *
+rb_source_group_get_by_name (const char *name)
+{
+	RBSourceGroup *group;
+
+	group = NULL;
+
+	G_LOCK (source_groups);
+	if (source_groups_map) {
+		group = g_hash_table_lookup (source_groups_map, name);
+	}
+	G_UNLOCK (source_groups);
+
+	return group;
+}
+
+RBSourceGroup *
+rb_source_group_register (const char           *name,
+			  const char           *display_name,
+			  RBSourceGroupCategory category)
+{
+	RBSourceGroup *group;
+
+	if (name == NULL) {
+		return NULL;
+	}
+
+	group = g_new0 (RBSourceGroup, 1);
+	group->name = g_strdup (name);
+	group->display_name = g_strdup (display_name);
+	group->category = category;
+	G_LOCK (source_groups);
+	g_hash_table_insert (source_groups_map, g_strdup (group->name), group);
+	G_UNLOCK (source_groups);
+
+	return group;
+}
+
+RBSourceGroup *
+rb_source_group_library_get_type (void)
+{
+	return library_group;
+}
+
+RBSourceGroup *
+rb_source_group_playlists_get_type (void)
+{
+	return playlists_group;
+}
+
+RBSourceGroup *
+rb_source_group_devices_get_type (void)
+{
+	return devices_group;
+}
+
+RBSourceGroup *
+rb_source_group_shared_get_type (void)
+{
+	return shared_group;
+}
Index: sources/rb-source-group.h
===================================================================
RCS file: sources/rb-source-group.h
diff -N sources/rb-source-group.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ sources/rb-source-group.h	29 Nov 2006 17:28:58 -0000
@@ -0,0 +1,75 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2006  Jonathan Matthew <jonathan@kaolin.wh9.net>
+ *  Copyright (C) 2006  William Jon McCann <mccann@jhu.edu>
+ *
+ *  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 of the License, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#ifndef __RB_SOURCE_GROUP_H
+#define __RB_SOURCE_GROUP_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+	RB_SOURCE_GROUP_CATEGORY_FIXED = 0,	/* library, iradio, podcast, queue */
+	RB_SOURCE_GROUP_CATEGORY_PERSISTANT,	/* playlists */
+	RB_SOURCE_GROUP_CATEGORY_REMOVABLE,	/* ipod, generic audio player, audio CD */
+	RB_SOURCE_GROUP_CATEGORY_TRANSIENT,	/* DAAP shares */
+	RB_SOURCE_GROUP_CATEGORY_LAST
+} RBSourceGroupCategory;
+
+GType rb_source_group_category_get_type (void);
+
+#define RB_TYPE_SOURCE_GROUP_CATEGORY (rb_source_group_category_get_type())
+
+
+struct RBSourceGroup
+{
+	char                 *name;
+	char                 *display_name;
+	RBSourceGroupCategory category;
+};
+
+typedef struct RBSourceGroup RBSourceGroup;
+
+GType          rb_source_group_get_type    (void);
+#define RB_TYPE_SOURCE_GROUP	(rb_source_group_get_type ())
+#define RB_SOURCE_GROUP(o)	(G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_SOURCE_GROUP, RBSourceGroup))
+#define RB_IS_SOURCE_GROUP(o)	(G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_SOURCE_GROUP))
+
+#define RB_SOURCE_GROUP_LIBRARY           (rb_source_group_library_get_type ())
+#define RB_SOURCE_GROUP_PLAYLISTS         (rb_source_group_playlists_get_type ())
+#define RB_SOURCE_GROUP_DEVICES           (rb_source_group_devices_get_type ())
+#define RB_SOURCE_GROUP_SHARED            (rb_source_group_shared_get_type ())
+
+void                  rb_source_group_init             (void);
+
+RBSourceGroup        *rb_source_group_get_by_name      (const char           *name);
+RBSourceGroup        *rb_source_group_register         (const char           *name,
+							const char           *display_name,
+							RBSourceGroupCategory category);
+
+RBSourceGroup        *rb_source_group_library_get_type   (void);
+RBSourceGroup        *rb_source_group_playlists_get_type (void);
+RBSourceGroup        *rb_source_group_devices_get_type   (void);
+RBSourceGroup        *rb_source_group_shared_get_type    (void);
+
+G_END_DECLS
+
+#endif /* __RB_SOURCE_GROUP_H */
Index: sources/rb-source.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/sources/rb-source.c,v
retrieving revision 1.84
diff -p -u -r1.84 rb-source.c
--- sources/rb-source.c	30 Oct 2006 11:33:37 -0000	1.84
+++ sources/rb-source.c	29 Nov 2006 17:28:59 -0000
@@ -36,6 +36,7 @@
 #include "rb-source.h"
 #include "rb-util.h"
 #include "rb-static-playlist-source.h"
+#include "rb-source-group.h"
 #include "rb-plugin.h"
 
 static void rb_source_class_init (RBSourceClass *klass);
@@ -88,11 +89,11 @@ struct _RBSourcePrivate
 	gboolean visible;
 	RhythmDBQueryModel *query_model;
 	GdkPixbuf *pixbuf;
-	RBSourceListGroup sourcelist_group;
 	guint hidden_when_empty : 1;
 	guint update_visibility_id;
 	guint update_status_id;
 	RhythmDBEntryType entry_type;
+	RBSourceGroup *source_group;
 	RBPlugin *plugin;
 };
 
@@ -106,7 +107,7 @@ enum
 	PROP_VISIBLE,
 	PROP_QUERY_MODEL,
 	PROP_HIDDEN_WHEN_EMPTY,
-	PROP_SOURCELIST_GROUP,
+	PROP_SOURCE_GROUP,
 	PROP_ENTRY_TYPE,
 	PROP_PLUGIN
 };
@@ -222,14 +223,12 @@ rb_source_class_init (RBSourceClass *kla
 							      RHYTHMDB_TYPE_QUERY_MODEL,
 							      G_PARAM_READWRITE));
 	g_object_class_install_property (object_class,
-					 PROP_SOURCELIST_GROUP,
-					 g_param_spec_enum ("sourcelist-group",
-						 	    "sourcelist group",
-							    "sourcelist group",
-							    RB_TYPE_SOURCELIST_GROUP,
-							    RB_SOURCELIST_GROUP_FIXED,
-							    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-
+					 PROP_SOURCE_GROUP,
+					 g_param_spec_boxed ("source-group",
+							     "Source group",
+							     "Source group",
+							     RB_TYPE_SOURCE_GROUP,
+							     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 	g_object_class_install_property (object_class,
 					 PROP_ENTRY_TYPE,
 					 g_param_spec_boxed ("entry-type",
@@ -477,8 +476,8 @@ rb_source_set_property (GObject *object,
 	case PROP_QUERY_MODEL:
 		rb_source_set_query_model_internal (source, g_value_get_object (value));
 		break;
-	case PROP_SOURCELIST_GROUP:
-		priv->sourcelist_group = g_value_get_enum (value);
+	case PROP_SOURCE_GROUP:
+		priv->source_group = g_value_get_boxed (value);
 		break;
 	case PROP_ENTRY_TYPE:
 		priv->entry_type = g_value_get_boxed (value);
@@ -526,8 +525,8 @@ rb_source_get_property (GObject *object,
 	case PROP_QUERY_MODEL:
 		g_value_set_object (value, priv->query_model);
 		break;
-	case PROP_SOURCELIST_GROUP:
-		g_value_set_enum (value, priv->sourcelist_group);
+	case PROP_SOURCE_GROUP:
+		g_value_set_boxed (value, priv->source_group);
 		break;
 	case PROP_ENTRY_TYPE:
 		g_value_set_boxed (value, priv->entry_type);
@@ -1227,26 +1226,6 @@ rb_source_eof_type_get_type (void)
 		};
 
 		etype = g_enum_register_static ("RBSourceEOFType", values);
-	}
-
-	return etype;
-}
-
-GType
-rb_sourcelist_group_get_type (void)
-{
-	static GType etype = 0;
-
-	if (etype == 0) {
-		static const GEnumValue values[] = {
-			ENUM_ENTRY (RB_SOURCELIST_GROUP_FIXED, "Fixed single instance source"),
-			ENUM_ENTRY (RB_SOURCELIST_GROUP_PERSISTANT, "Persistant multiple-instance source"),
-			ENUM_ENTRY (RB_SOURCELIST_GROUP_REMOVABLE, "Source representing a removable device"),
-			ENUM_ENTRY (RB_SOURCELIST_GROUP_TRANSIENT, "Transient source (eg. network shares)"),
-			{ 0, 0, 0 }
-		};
-
-		etype = g_enum_register_static ("RBSourcelistGroupType", values);
 	}
 
 	return etype;
Index: sources/rb-source.h
===================================================================
RCS file: /cvs/gnome/rhythmbox/sources/rb-source.h,v
retrieving revision 1.50
diff -p -u -r1.50 rb-source.h
--- sources/rb-source.h	16 Oct 2006 22:02:47 -0000	1.50
+++ sources/rb-source.h	29 Nov 2006 17:28:59 -0000
@@ -26,6 +26,7 @@
 #include <gtk/gtkhbox.h>
 #include <gtk/gtkactiongroup.h>
 
+#include "rb-source-group.h"
 #include "rb-entry-view.h"
 #include "rb-shell-preferences.h"
 
@@ -38,21 +39,8 @@ typedef enum {
 	RB_SOURCE_EOF_NEXT,
 } RBSourceEOFType;
 
-/* groupings in the source list */
-typedef enum {
-	RB_SOURCELIST_GROUP_FIXED,	/* library, iradio, podcast, queue */
-	RB_SOURCELIST_GROUP_PERSISTANT,	/* playlists */
-	RB_SOURCELIST_GROUP_REMOVABLE,	/* ipod, generic audio player, audio CD */
-	RB_SOURCELIST_GROUP_TRANSIENT,	/* DAAP shares */
-
-	RB_SOURCELIST_GROUP_LAST
-} RBSourceListGroup;
-
 GType rb_source_eof_type_get_type (void);
 #define RB_TYPE_SOURCE_EOF_TYPE	(rb_source_eof_type_get_type())
-
-GType rb_sourcelist_group_get_type (void);
-#define RB_TYPE_SOURCELIST_GROUP (rb_sourcelist_group_get_type())
 
 #define RB_TYPE_SOURCE         (rb_source_get_type ())
 #define RB_SOURCE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_SOURCE, RBSource))
Index: sources/rb-sourcelist-model.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/sources/rb-sourcelist-model.c,v
retrieving revision 1.34
diff -p -u -r1.34 rb-sourcelist-model.c
--- sources/rb-sourcelist-model.c	28 Nov 2006 13:48:54 -0000	1.34
+++ sources/rb-sourcelist-model.c	29 Nov 2006 17:28:59 -0000
@@ -37,7 +37,6 @@
 
 struct RBSourceListModelPrivate
 {
-	GtkTreeRowReference *groups[RB_SOURCELIST_GROUP_LAST];
 };
 
 #define RB_SOURCELIST_MODEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SOURCELIST_MODEL, RBSourceListModelPrivate))
@@ -192,15 +191,10 @@ static void
 rb_sourcelist_model_finalize (GObject *object)
 {
 	RBSourceListModel *model;
-	int i;
 
 	g_return_if_fail (RB_IS_SOURCELIST_MODEL (object));
 	model = RB_SOURCELIST_MODEL (object);
 
-	for (i = 0; i < RB_SOURCELIST_GROUP_LAST; i++) {
-		gtk_tree_row_reference_free (model->priv->groups[i]);
-	}
-
 	G_OBJECT_CLASS (rb_sourcelist_model_parent_class)->finalize (object);
 }
 
@@ -209,7 +203,6 @@ rb_sourcelist_model_new (void)
 {
 	RBSourceListModel *model;
 	GtkTreeStore *store;
-	int i;
  	GType *column_types = g_new (GType, RB_SOURCELIST_MODEL_N_COLUMNS);
 
 	column_types[RB_SOURCELIST_MODEL_COLUMN_PLAYING] = G_TYPE_BOOLEAN;
@@ -218,6 +211,8 @@ rb_sourcelist_model_new (void)
 	column_types[RB_SOURCELIST_MODEL_COLUMN_SOURCE] = G_TYPE_OBJECT;
 	column_types[RB_SOURCELIST_MODEL_COLUMN_ATTRIBUTES] = PANGO_TYPE_ATTR_LIST;
 	column_types[RB_SOURCELIST_MODEL_COLUMN_VISIBILITY] = G_TYPE_BOOLEAN;
+	column_types[RB_SOURCELIST_MODEL_COLUMN_IS_GROUP] = G_TYPE_BOOLEAN;
+	column_types[RB_SOURCELIST_MODEL_COLUMN_GROUP_CATEGORY] = RB_TYPE_SOURCE_GROUP_CATEGORY;
 	store = gtk_tree_store_newv (RB_SOURCELIST_MODEL_N_COLUMNS,
 				     column_types);
 
@@ -227,23 +222,6 @@ rb_sourcelist_model_new (void)
 						   NULL));
 	g_object_unref (store);
 
-	/* create marker rows used to separate source groups */
-	for (i = 0; i < RB_SOURCELIST_GROUP_LAST; i++) {
-		GtkTreeIter iter;
-		GtkTreePath *path;
-
-		gtk_tree_store_append (store, &iter, NULL);
-		gtk_tree_store_set (store, &iter,
-				    RB_SOURCELIST_MODEL_COLUMN_NAME, "",
-				    RB_SOURCELIST_MODEL_COLUMN_SOURCE, NULL,
-				    RB_SOURCELIST_MODEL_COLUMN_VISIBILITY, FALSE,
-				    -1);
-
-		path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
-		model->priv->groups[i] = gtk_tree_row_reference_new (GTK_TREE_MODEL (store), path);
-		gtk_tree_path_free (path);
-	}
-
 	/* ensure the group markers get updated as sources are added and removed */
 	g_signal_connect_object (G_OBJECT (store), "row-inserted",
 				 G_CALLBACK (rb_sourcelist_model_row_inserted_cb),
@@ -262,56 +240,17 @@ rb_sourcelist_model_new (void)
 }
 
 static gboolean
-real_row_is_separator (RBSourceListModel *model, GtkTreeIter *iter)
-{
-	int i;
-	GtkTreePath *path;
-	GtkTreeModel *real_model;
-	GtkTreePath *group_path;
-
-	real_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model));
-	path = gtk_tree_model_get_path (real_model, iter);
-
-	/* -1 here because the last group marker is always the last row in
-	 * the model, and therefore can never be shown.
-	 */
-	for (i = 0; i < RB_SOURCELIST_GROUP_LAST-1; i++) {
-		group_path = rb_sourcelist_model_get_group_path (model, i);
-
-		if (gtk_tree_path_compare (path, group_path) == 0) {
-			/* okay, we know this is a group marker.
-			 * if the next row is not a group marker,
-			 * then we should show this row as a separator.
-			 */
-			gboolean separator = FALSE;
-
-			gtk_tree_path_next (path);
-
-			gtk_tree_path_free (group_path);
-			group_path = rb_sourcelist_model_get_group_path (model, i+1);
-			separator = (gtk_tree_path_compare (path, group_path) != 0);
-
-			gtk_tree_path_free (group_path);
-			gtk_tree_path_free (path);
-			return separator;
-		}
-		gtk_tree_path_free (group_path);
-	}
-
-	gtk_tree_path_free (path);
-
-	return FALSE;
-}
-
-static gboolean
 rb_sourcelist_model_is_row_visible (GtkTreeModel *model,
 				    GtkTreeIter *iter,
 				    RBSourceListModel *sourcelist)
 {
 	RBSource *source;
+	gboolean  visibility;
 
 	gtk_tree_model_get (GTK_TREE_MODEL (model), iter,
-			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source, -1);
+			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source,
+			    RB_SOURCELIST_MODEL_COLUMN_VISIBILITY, &visibility,
+			    -1);
 
 	if (source != NULL) {
 		gboolean visible;
@@ -321,51 +260,33 @@ rb_sourcelist_model_is_row_visible (GtkT
 
 		return visible;
 	} else {
-		return real_row_is_separator (sourcelist, iter);
+		return visibility;
 	}
-}
-
-gboolean
-rb_sourcelist_model_row_is_separator (GtkTreeModel *model,
-				      GtkTreeIter *iter,
-				      RBSourceListModel *sourcelist)
-{
-	GtkTreeIter real_iter;
-	RBSource *source;
 
-	/* rows with actual sources are never separators */
-	gtk_tree_model_get (GTK_TREE_MODEL (model), iter,
-			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source, -1);
-	if (source != NULL) {
-		g_object_unref (source);
-		return FALSE;
-	}
-
-	gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (sourcelist),
-							  &real_iter,
-							  iter);
-	return real_row_is_separator (sourcelist, &real_iter);
+	return TRUE;
 }
 
-static int
-get_group_for_path (RBSourceListModel *model, GtkTreePath *path)
+static RBSourceGroup *
+get_group_for_path (RBSourceListModel *model,
+		    GtkTreePath       *path)
 {
-	GtkTreePath *group_path;
-	gboolean found = FALSE;
-	int i;
+	RBSource      *source;
+	RBSourceGroup *group;
+	GtkTreeIter    iter;
+
+	group = NULL;
 
-	for (i=0; i < RB_SOURCELIST_GROUP_LAST; i++) {
-		group_path = rb_sourcelist_model_get_group_path (model, i);
-		g_assert (group_path);
+	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path)) {
 
-		found = (gtk_tree_path_compare (group_path, path) == 1);
-		gtk_tree_path_free (group_path);
+		gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
+				    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source,
+				    -1);
 
-		if (found)
-			return i;
+		g_object_get (source, "source-group", &group, NULL);
+		g_object_unref (source);
 	}
 
-	g_assert_not_reached ();
+	return group;
 }
 
 static gboolean
@@ -411,18 +332,19 @@ rb_sourcelist_model_drag_data_received (
         }
 
 	if (selection_data->type == gdk_atom_intern ("application/x-rhythmbox-source", TRUE)) {
-		GtkTreePath *path;
-		GtkTreePath *real_dest;
-		char *path_str;
-		GtkTreeIter iter, real_iter;
-		GtkTreeIter real_dest_iter;
+		GtkTreePath  *path;
+		GtkTreePath  *real_dest;
+		char         *path_str;
+		GtkTreeIter   iter, real_iter;
+		GtkTreeIter   real_dest_iter;
 		GtkTreeModel *real_model;
-		RBSource *source;
-		RBSourceListGroup group;
-		int dest_group;
+		RBSource      *source;
+		RBSourceGroup *group;
+		RBSourceGroup *dest_group;
 
-		if (!dest)
+		if (! dest) {
 			return FALSE;
+		}
 
 		real_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model));
 
@@ -434,37 +356,36 @@ rb_sourcelist_model_drag_data_received (
 		gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model),
 								  &real_iter, &iter);
 
-		real_dest =
-			gtk_tree_model_filter_convert_path_to_child_path (GTK_TREE_MODEL_FILTER (model),
-									  dest);
+		real_dest = gtk_tree_model_filter_convert_path_to_child_path (GTK_TREE_MODEL_FILTER (model),
+									      dest);
 
 		gtk_tree_model_get (GTK_TREE_MODEL (real_model), &real_iter,
 				    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source,
 				    -1);
-		g_object_get (source, "sourcelist-group", &group, NULL);
+		g_object_get (source, "source-group", &group, NULL);
 		g_object_unref (source);
 
 		/* restrict sources to within their group */
 		dest_group = get_group_for_path (model, real_dest);
-		if (dest_group < group) {
-			gtk_tree_path_free (real_dest);
-			real_dest = rb_sourcelist_model_get_group_path (model, group-1);
-			pos = GTK_TREE_VIEW_DROP_AFTER;
-		} else if (dest_group > group) {
-			gtk_tree_path_free (real_dest);
-			real_dest = rb_sourcelist_model_get_group_path (model, group);
-			pos = GTK_TREE_VIEW_DROP_BEFORE;
+		if (dest_group == NULL
+		    || group == NULL
+		    || strcmp (dest_group->name, group->name) != 0) {
+			/* bail */
+			return FALSE;
 		}
 
+
+		/* FIXME: this won't work with a sorted model */
 		if (gtk_tree_model_get_iter (GTK_TREE_MODEL (real_model),
 					     &real_dest_iter, real_dest)) {
 
-			if (pos == GTK_TREE_VIEW_DROP_AFTER)
+			if (pos == GTK_TREE_VIEW_DROP_AFTER) {
 				gtk_tree_store_move_after (GTK_TREE_STORE (real_model),
 							   &real_iter, &real_dest_iter);
-			else
+			} else {
 				gtk_tree_store_move_before (GTK_TREE_STORE (real_model),
 							    &real_iter, &real_dest_iter);
+			}
 
 		} else {
 			gtk_tree_store_move_before (GTK_TREE_STORE (real_model),
@@ -530,8 +451,8 @@ path_is_reorderable (RBSourceListModel *
 	GtkTreeIter iter;
 
 	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, dest)) {
-		RBSource *source;
-		RBSourceListGroup group;
+		RBSource      *source;
+		RBSourceGroup *group;
 
 		gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
 				    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source, -1);
@@ -539,12 +460,12 @@ path_is_reorderable (RBSourceListModel *
 		if (source == NULL)
 			return FALSE;
 
-		g_object_get (source, "sourcelist-group", &group, NULL);
+		g_object_get (source, "source-group", &group, NULL);
 		g_object_unref (source);
 
 		/* fixed and transient sources are not reorderable, everything else is */
-		return (group != RB_SOURCELIST_GROUP_FIXED &&
-			group != RB_SOURCELIST_GROUP_TRANSIENT);
+		return (group->category != RB_SOURCE_GROUP_CATEGORY_FIXED &&
+			group->category != RB_SOURCE_GROUP_CATEGORY_TRANSIENT);
 	}
 
 	return FALSE;
@@ -644,17 +565,20 @@ rb_sourcelist_model_row_draggable (RbTre
 	path = gtk_tree_row_reference_get_path (path_list->data);
 
 	if (path && gtk_tree_model_get_iter (model, &iter, path)) {
-		RBSource *source;
-		RBSourceListGroup group;
+		RBSource      *source;
+		RBSourceGroup *group;
+		gboolean       can;
 
 		gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
 				    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source, -1);
 
-		g_object_get (source, "sourcelist-group", &group, NULL);
+		g_object_get (source, "source-group", &group, NULL);
 		g_object_unref (source);
 
-		return (group != RB_SOURCELIST_GROUP_FIXED &&
-			group != RB_SOURCELIST_GROUP_TRANSIENT);
+		can = (group->category != RB_SOURCE_GROUP_CATEGORY_FIXED &&
+		       group->category != RB_SOURCE_GROUP_CATEGORY_TRANSIENT);
+
+		return can;
 	}
 
 	return FALSE;
@@ -773,11 +697,4 @@ rb_sourcelist_model_row_deleted_cb (GtkT
 				    RBSourceListModel *sourcelist)
 {
 	gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (sourcelist));
-}
-
-GtkTreePath *
-rb_sourcelist_model_get_group_path (RBSourceListModel *sourcelist,
-				    RBSourceListGroup group)
-{
-	return gtk_tree_row_reference_get_path (sourcelist->priv->groups[group]);
 }
Index: sources/rb-sourcelist-model.h
===================================================================
RCS file: /cvs/gnome/rhythmbox/sources/rb-sourcelist-model.h,v
retrieving revision 1.9
diff -p -u -r1.9 rb-sourcelist-model.h
--- sources/rb-sourcelist-model.h	4 Jun 2006 10:19:22 -0000	1.9
+++ sources/rb-sourcelist-model.h	29 Nov 2006 17:28:59 -0000
@@ -61,6 +61,8 @@ enum
 	RB_SOURCELIST_MODEL_COLUMN_SOURCE,
 	RB_SOURCELIST_MODEL_COLUMN_ATTRIBUTES,
 	RB_SOURCELIST_MODEL_COLUMN_VISIBILITY,
+	RB_SOURCELIST_MODEL_COLUMN_IS_GROUP,
+	RB_SOURCELIST_MODEL_COLUMN_GROUP_CATEGORY,
 	RB_SOURCELIST_MODEL_N_COLUMNS
 };
 
@@ -70,11 +72,6 @@ GtkTreeModel *	rb_sourcelist_model_new		
 
 void		rb_sourcelist_model_set_dnd_targets (RBSourceListModel *sourcelist,
 						     GtkTreeView *treeview);
-GtkTreePath *	rb_sourcelist_model_get_group_path (RBSourceListModel *sourcelist,
-						    RBSourceListGroup group);
-gboolean	rb_sourcelist_model_row_is_separator (GtkTreeModel *model,
-						      GtkTreeIter *iter,
-						      RBSourceListModel *sourcelist);
 
 G_END_DECLS
 
Index: sources/rb-sourcelist.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/sources/rb-sourcelist.c,v
retrieving revision 1.43
diff -p -u -r1.43 rb-sourcelist.c
--- sources/rb-sourcelist.c	13 Sep 2006 22:21:47 -0000	1.43
+++ sources/rb-sourcelist.c	29 Nov 2006 17:28:59 -0000
@@ -37,8 +37,11 @@
 #include "rb-stock-icons.h"
 #include "rb-marshal.h"
 #include "rb-cell-renderer-pixbuf.h"
+#include "gossip-cell-renderer-expander.h"
 #include "rb-tree-dnd.h"
 #include "rb-util.h"
+#include "rb-auto-playlist-source.h"
+#include "rb-static-playlist-source.h"
 
 struct RBSourceListPrivate
 {
@@ -51,7 +54,6 @@ struct RBSourceListPrivate
 
 	RBSource *playing_source;
 	int child_source_count;
-	GtkTreeViewColumn *hidden_column;
 	GtkTreeViewColumn *main_column;
 
 	RBShell *shell;
@@ -94,26 +96,8 @@ static gboolean rb_sourcelist_visible_so
 						      GtkTreeIter *iter);
 static void rb_sourcelist_selection_changed_cb (GtkTreeSelection *selection,
 						RBSourceList *sourcelist);
-static void drop_received_cb (RBSourceListModel *model, RBSource *target, GtkTreeViewDropPosition pos,
-			      GtkSelectionData *data, RBSourceList *sourcelist);
-static void row_activated_cb (GtkTreeView *treeview, GtkTreePath *path,
-			      GtkTreeViewColumn *column, RBSourceList *sourcelist);
-static gboolean button_press_cb (GtkTreeView *treeview,
-				 GdkEventButton *event,
-				 RBSourceList *sourcelist);
-static gboolean key_release_cb (GtkTreeView *treeview,
-				GdkEventKey *event,
-				RBSourceList *sourcelist);
-static gboolean popup_menu_cb (GtkTreeView *treeview, RBSourceList *sourcelist);
-static void name_notify_cb (GObject *obj, GParamSpec *pspec, gpointer data);
-static void visibility_notify_cb (GObject *obj, GParamSpec *pspec,
-				  gpointer data);
 static void source_name_edited_cb (GtkCellRendererText *renderer, const char *pathstr,
 				   const char *text, RBSourceList *sourcelist);
-static void rb_sourcelist_title_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
-						GtkTreeModel *tree_model, GtkTreeIter *iter,
-						RBSourceList *sourcelist);
-static void rb_sourcelist_update_expander_visibility (RBSourceList *sourcelist);
 
 static guint rb_sourcelist_signals[LAST_SIGNAL] = { 0 };
 
@@ -195,6 +179,406 @@ rb_sourcelist_class_init (RBSourceListCl
 }
 
 static void
+rb_sourcelist_cell_set_background (RBSourceList       *list,
+				   GtkCellRenderer    *cell,
+				   gboolean            is_group,
+				   gboolean            is_active)
+{
+	GdkColor  color;
+	GtkStyle *style;
+
+	g_return_if_fail (list != NULL);
+	g_return_if_fail (cell != NULL);
+
+	style = gtk_widget_get_style (GTK_WIDGET (list));
+
+	if (!is_group) {
+		if (is_active) {
+			color = style->bg[GTK_STATE_SELECTED];
+
+			/* Here we take the current theme colour and add it to
+			 * the colour for white and average the two. This
+			 * gives a colour which is inline with the theme but
+			 * slightly whiter.
+			 */
+			color.red = (color.red + (style->white).red) / 2;
+			color.green = (color.green + (style->white).green) / 2;
+			color.blue = (color.blue + (style->white).blue) / 2;
+
+			g_object_set (cell,
+				      "cell-background-gdk", &color,
+				      NULL);
+		} else {
+			g_object_set (cell,
+				      "cell-background-gdk", NULL,
+				      NULL);
+		}
+	} else {
+		color = style->text_aa[GTK_STATE_INSENSITIVE];
+
+		color.red = (color.red + (style->white).red) / 2;
+		color.green = (color.green + (style->white).green) / 2;
+		color.blue = (color.blue + (style->white).blue) / 2;
+
+		g_object_set (cell,
+			      "cell-background-gdk", &color,
+			      NULL);
+	}
+}
+
+static void
+sourcelist_indent_level1_cell_data_func (GtkTreeViewColumn *tree_column,
+					 GtkCellRenderer   *cell,
+					 GtkTreeModel      *model,
+					 GtkTreeIter       *iter,
+					 RBSourceList      *sourcelist)
+{
+	GtkTreePath *path;
+	int          depth;
+
+	path = gtk_tree_model_get_path (model, iter);
+	depth = gtk_tree_path_get_depth (path);
+	gtk_tree_path_free (path);
+	g_object_set (cell,
+		      "text", " ",
+		      "visible", depth > 1,
+		      NULL);
+}
+
+static void
+sourcelist_indent_level2_cell_data_func (GtkTreeViewColumn *tree_column,
+					 GtkCellRenderer   *cell,
+					 GtkTreeModel      *model,
+					 GtkTreeIter       *iter,
+					 RBSourceList      *sourcelist)
+{
+	GtkTreePath *path;
+	int          depth;
+
+	path = gtk_tree_model_get_path (model, iter);
+	depth = gtk_tree_path_get_depth (path);
+	gtk_tree_path_free (path);
+	g_object_set (cell,
+		      "text", " ",
+		      "visible", depth > 2,
+		      NULL);
+}
+
+static void
+sourcelist_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
+				  GtkCellRenderer   *cell,
+				  GtkTreeModel      *model,
+				  GtkTreeIter       *iter,
+				  RBSourceList      *sourcelist)
+{
+	GdkPixbuf *pixbuf;
+	gboolean   is_group;
+	gboolean   is_active;
+
+	gtk_tree_model_get (model, iter,
+			    RB_SOURCELIST_MODEL_COLUMN_IS_GROUP, &is_group,
+			    RB_SOURCELIST_MODEL_COLUMN_PIXBUF, &pixbuf,
+			    -1);
+
+	g_object_set (cell,
+		      "visible", !is_group,
+		      "pixbuf", pixbuf,
+		      NULL);
+
+	if (pixbuf != NULL) {
+		g_object_unref (pixbuf);
+	}
+
+	is_active = FALSE;
+	rb_sourcelist_cell_set_background (sourcelist, cell, is_group, is_active);
+}
+
+static void
+rb_sourcelist_title_cell_data_func (GtkTreeViewColumn *column,
+				    GtkCellRenderer   *renderer,
+				    GtkTreeModel      *tree_model,
+				    GtkTreeIter       *iter,
+				    RBSourceList      *sourcelist)
+{
+	char    *str;
+	gboolean playing;
+	gboolean is_group;
+	gboolean is_active;
+
+	gtk_tree_model_get (GTK_TREE_MODEL (sourcelist->priv->filter_model), iter,
+			    RB_SOURCELIST_MODEL_COLUMN_NAME, &str,
+			    RB_SOURCELIST_MODEL_COLUMN_PLAYING, &playing,
+			    RB_SOURCELIST_MODEL_COLUMN_IS_GROUP, &is_group,
+			    -1);
+
+	g_object_set (renderer,
+		      "text", str,
+		      "weight", playing ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
+		      NULL);
+
+	is_active = FALSE;
+	rb_sourcelist_cell_set_background (sourcelist, renderer, is_group, is_active);
+
+	g_free (str);
+}
+
+static int
+compare_rows (GtkTreeModel *model,
+	      GtkTreeIter  *a,
+	      GtkTreeIter  *b,
+	      gpointer      user_data)
+{
+	char *a_name;
+	char *b_name;
+	gboolean a_is_group;
+	gboolean b_is_group;
+	RBSourceGroupCategory a_category;
+	RBSourceGroupCategory b_category;
+	RBSource             *a_source;
+	RBSource             *b_source;
+	int ret;
+
+	gtk_tree_model_get (model, a,
+			    RB_SOURCELIST_MODEL_COLUMN_NAME, &a_name,
+			    RB_SOURCELIST_MODEL_COLUMN_IS_GROUP, &a_is_group,
+			    RB_SOURCELIST_MODEL_COLUMN_GROUP_CATEGORY, &a_category,
+			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &a_source,
+			    -1);
+	gtk_tree_model_get (model, b,
+			    RB_SOURCELIST_MODEL_COLUMN_NAME, &b_name,
+			    RB_SOURCELIST_MODEL_COLUMN_IS_GROUP, &b_is_group,
+			    RB_SOURCELIST_MODEL_COLUMN_GROUP_CATEGORY, &b_category,
+			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &b_source,
+			    -1);
+
+	if (a_is_group && a_is_group) {
+		if (a_category < b_category) {
+			ret = -1;
+		} else if (a_category > b_category) {
+			ret = 1;
+		} else {
+			ret = g_utf8_collate (a_name, b_name);
+		}
+	} else {
+		/* sort by name */
+		switch (a_category) {
+		case RB_SOURCE_GROUP_CATEGORY_FIXED:
+			ret = -1;
+			break;
+		case RB_SOURCE_GROUP_CATEGORY_PERSISTANT:
+			if (RB_IS_AUTO_PLAYLIST_SOURCE (a_source)
+			    && RB_IS_AUTO_PLAYLIST_SOURCE (b_source)) {
+				ret = g_utf8_collate (a_name, b_name);
+			} else if (RB_IS_STATIC_PLAYLIST_SOURCE (a_source)
+				   && RB_IS_STATIC_PLAYLIST_SOURCE (b_source)) {
+				ret = g_utf8_collate (a_name, b_name);
+			} else if (RB_IS_AUTO_PLAYLIST_SOURCE (a_source)) {
+				ret = -1;
+			} else {
+				ret = 1;
+			}
+
+			break;
+		case RB_SOURCE_GROUP_CATEGORY_REMOVABLE:
+		case RB_SOURCE_GROUP_CATEGORY_TRANSIENT:
+			ret = g_utf8_collate (a_name, b_name);
+			break;
+		default:
+			g_assert_not_reached ();
+			break;
+		}
+	}
+
+	g_object_unref (a_source);
+	g_object_unref (b_source);
+	g_free (a_name);
+	g_free (b_name);
+
+	return ret;
+}
+
+static void
+rb_sourcelist_expander_cell_data_func (GtkTreeViewColumn *column,
+				       GtkCellRenderer   *cell,
+				       GtkTreeModel      *model,
+				       GtkTreeIter       *iter,
+				       RBSourceList      *sourcelist)
+{
+	gboolean is_group;
+	gboolean is_active;
+
+	gtk_tree_model_get (model, iter,
+			    RB_SOURCELIST_MODEL_COLUMN_IS_GROUP, &is_group,
+			    -1);
+
+	if (gtk_tree_model_iter_has_child (model, iter)) {
+		GtkTreePath *path;
+		gboolean     row_expanded;
+
+		path = gtk_tree_model_get_path (model, iter);
+		row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
+		gtk_tree_path_free (path);
+
+		g_object_set (cell,
+			      "visible", TRUE,
+			      "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
+			      NULL);
+	} else {
+		g_object_set (cell, "visible", FALSE, NULL);
+	}
+
+	is_active = FALSE;
+	rb_sourcelist_cell_set_background (sourcelist, cell, is_group, is_active);
+}
+
+static void
+row_activated_cb (GtkTreeView       *treeview,
+		  GtkTreePath       *path,
+		  GtkTreeViewColumn *column,
+		  RBSourceList      *sourcelist)
+{
+	GtkTreeModel *model;
+	GtkTreeIter   iter;
+	RBSource     *target;
+	gboolean      is_group;
+
+	model = gtk_tree_view_get_model (treeview);
+
+	g_return_if_fail (gtk_tree_model_get_iter (model, &iter, path));
+
+	gtk_tree_model_get (model, &iter,
+			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &target,
+			    RB_SOURCELIST_MODEL_COLUMN_IS_GROUP, &is_group,
+			    -1);
+
+	if (target != NULL) {
+		g_signal_emit (sourcelist, rb_sourcelist_signals[SOURCE_ACTIVATED], 0, target);
+		g_object_unref (target);
+	}
+}
+
+static void
+drop_received_cb (RBSourceListModel      *model,
+		  RBSource               *target,
+		  GtkTreeViewDropPosition pos,
+		  GtkSelectionData       *data,
+		  RBSourceList           *sourcelist)
+{
+	rb_debug ("drop recieved");
+	/* Proxy the signal. */
+	g_signal_emit (sourcelist, rb_sourcelist_signals[DROP_RECEIVED], 0, target, data);
+}
+
+static gboolean
+emit_show_popup (GtkTreeView  *treeview,
+		 RBSourceList *sourcelist)
+{
+	GtkTreeIter iter;
+	RBSource   *target;
+	gboolean    ret;
+
+	if (!gtk_tree_selection_get_selected (gtk_tree_view_get_selection (treeview),
+					      NULL, &iter))
+		return FALSE;
+
+	gtk_tree_model_get (sourcelist->priv->filter_model, &iter,
+			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &target, -1);
+	if (target == NULL)
+		return FALSE;
+
+	g_return_val_if_fail (RB_IS_SOURCE (target), FALSE);
+
+	g_signal_emit (sourcelist, rb_sourcelist_signals[SHOW_POPUP], 0, target, &ret);
+
+	if (target != NULL) {
+		g_object_unref (target);
+	}
+
+	return ret;
+}
+
+static gboolean
+button_press_cb (GtkTreeView    *treeview,
+		 GdkEventButton *event,
+		 RBSourceList   *sourcelist)
+{
+	GtkTreeIter  iter;
+	GtkTreePath *path;
+	gboolean     res;
+
+	if (event->button != 3) {
+		return FALSE;
+	}
+
+	res = gtk_tree_view_get_path_at_pos (treeview,
+					     event->x,
+					     event->y,
+					     &path,
+					     NULL,
+					     NULL,
+					     NULL);
+	if (! res) {
+		/* pointer is over empty space */
+		GtkUIManager *uimanager;
+		g_object_get (sourcelist->priv->shell, "ui-manager", &uimanager, NULL);
+		rb_gtk_action_popup_menu (uimanager, "/SourceListPopup");
+		g_object_unref (uimanager);
+		return TRUE;
+	}
+
+	res = gtk_tree_model_get_iter (GTK_TREE_MODEL (sourcelist->priv->filter_model), &iter, path);
+	if (res) {
+		gtk_tree_selection_select_iter (gtk_tree_view_get_selection (treeview), &iter);
+	}
+
+	return emit_show_popup (treeview, sourcelist);
+}
+
+static gboolean
+key_release_cb (GtkTreeView  *treeview,
+		GdkEventKey  *event,
+		RBSourceList *sourcelist)
+{
+	GtkTreeIter iter;
+	RBSource   *target;
+	gboolean    res;
+
+	/* F2 = rename playlist */
+	if (event->keyval != GDK_F2) {
+		return FALSE;
+	}
+
+	if (!gtk_tree_selection_get_selected (sourcelist->priv->selection, NULL, &iter)) {
+		return FALSE;
+	}
+
+	gtk_tree_model_get (sourcelist->priv->filter_model,
+			    &iter,
+			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &target,
+			    -1);
+	if (target == NULL) {
+		return FALSE;
+	}
+
+	res = FALSE;
+	if (rb_source_can_rename (target)) {
+		rb_sourcelist_edit_source_name (sourcelist, target);
+		res = TRUE;
+	}
+
+	g_object_unref (target);
+
+	return res;
+}
+
+static gboolean
+popup_menu_cb (GtkTreeView  *treeview,
+	       RBSourceList *sourcelist)
+{
+	return emit_show_popup (treeview, sourcelist);
+}
+
+static void
 rb_sourcelist_init (RBSourceList *sourcelist)
 {
 	GtkCellRenderer *renderer;
@@ -203,78 +587,130 @@ rb_sourcelist_init (RBSourceList *source
 
 	sourcelist->priv->filter_model = rb_sourcelist_model_new ();
 	sourcelist->priv->real_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (sourcelist->priv->filter_model));
-
-	g_signal_connect_object (G_OBJECT (sourcelist->priv->filter_model),
+	g_signal_connect_object (sourcelist->priv->filter_model,
 				 "drop_received",
 				 G_CALLBACK (drop_received_cb),
 				 sourcelist, 0);
 
 	sourcelist->priv->treeview = gtk_tree_view_new_with_model (sourcelist->priv->filter_model);
+
+	g_object_set (sourcelist->priv->treeview,
+		      "headers-visible", FALSE,
+		      "reorderable", TRUE,
+		      "show-expanders", FALSE,
+		      NULL);
+
 	gtk_tree_view_set_enable_search (GTK_TREE_VIEW (sourcelist->priv->treeview), FALSE);
 	rb_sourcelist_model_set_dnd_targets (RB_SOURCELIST_MODEL (sourcelist->priv->filter_model),
 					     GTK_TREE_VIEW (sourcelist->priv->treeview));
-	gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (sourcelist->priv->treeview),
-					      (GtkTreeViewRowSeparatorFunc) rb_sourcelist_model_row_is_separator,
-					      sourcelist->priv->filter_model, NULL);
 
-	g_signal_connect_object (G_OBJECT (sourcelist->priv->treeview),
+	g_signal_connect_object (sourcelist->priv->treeview,
 				 "row_activated",
 				 G_CALLBACK (row_activated_cb),
 				 sourcelist, 0);
 
-	g_signal_connect_object (G_OBJECT (sourcelist->priv->treeview),
+	g_signal_connect_object (sourcelist->priv->treeview,
 				 "button_press_event",
 				 G_CALLBACK (button_press_cb),
 				 sourcelist, 0);
-	g_signal_connect_object (G_OBJECT (sourcelist->priv->treeview),
+	g_signal_connect_object (sourcelist->priv->treeview,
 				 "key_release_event",
 				 G_CALLBACK (key_release_cb),
 				 sourcelist, 0);
 
-	g_signal_connect_object (G_OBJECT (sourcelist->priv->treeview),
+	g_signal_connect_object (sourcelist->priv->treeview,
 				 "popup_menu",
 				 G_CALLBACK (popup_menu_cb),
 				 sourcelist, 0);
 
-	sourcelist->priv->hidden_column = gtk_tree_view_column_new ();
-	gtk_tree_view_append_column (GTK_TREE_VIEW (sourcelist->priv->treeview),
-				     sourcelist->priv->hidden_column);
-	gtk_tree_view_column_set_visible (sourcelist->priv->hidden_column, FALSE);
-	gtk_tree_view_set_expander_column (GTK_TREE_VIEW (sourcelist->priv->treeview),
-				     sourcelist->priv->hidden_column);
-
 	sourcelist->priv->main_column = gtk_tree_view_column_new ();
 	gtk_tree_view_column_set_title (sourcelist->priv->main_column, _("S_ource"));
 	gtk_tree_view_column_set_clickable (sourcelist->priv->main_column, FALSE);
+
+        gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sourcelist->priv->real_model),
+                                         RB_SOURCELIST_MODEL_COLUMN_GROUP_CATEGORY,
+					 compare_rows,
+                                         NULL, NULL);
+        gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sourcelist->priv->real_model),
+                                              RB_SOURCELIST_MODEL_COLUMN_GROUP_CATEGORY,
+                                              GTK_SORT_ASCENDING);
+
 	gtk_tree_view_append_column (GTK_TREE_VIEW (sourcelist->priv->treeview), sourcelist->priv->main_column);
 
+	/* Set up the indent level1 column */
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_column_pack_start (sourcelist->priv->main_column, renderer, FALSE);
+	gtk_tree_view_column_set_cell_data_func (sourcelist->priv->main_column,
+						 renderer,
+						 (GtkTreeCellDataFunc) sourcelist_indent_level1_cell_data_func,
+						 sourcelist,
+						 NULL);
+
+	g_object_set (renderer,
+		      "xpad", 0,
+		      "visible", FALSE,
+		      NULL);
+
+	/* Set up the indent level2 column */
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_column_pack_start (sourcelist->priv->main_column, renderer, FALSE);
+	gtk_tree_view_column_set_cell_data_func (sourcelist->priv->main_column,
+						 renderer,
+						 (GtkTreeCellDataFunc) sourcelist_indent_level2_cell_data_func,
+						 sourcelist,
+						 NULL);
+
+	g_object_set (renderer,
+		      "xpad", 0,
+		      "visible", FALSE,
+		      NULL);
+
 	/* Set up the pixbuf column */
 	renderer = gtk_cell_renderer_pixbuf_new ();
 	gtk_tree_view_column_pack_start (sourcelist->priv->main_column, renderer, FALSE);
-	gtk_tree_view_column_set_attributes (sourcelist->priv->main_column, renderer,
-				             "pixbuf", RB_SOURCELIST_MODEL_COLUMN_PIXBUF,
-					     NULL);
+	gtk_tree_view_column_set_cell_data_func (sourcelist->priv->main_column,
+						 renderer,
+						 (GtkTreeCellDataFunc) sourcelist_pixbuf_cell_data_func,
+						 sourcelist,
+						 NULL);
+
+	g_object_set (renderer,
+		      "xpad", 8,
+		      "ypad", 1,
+		      "visible", FALSE,
+		      NULL);
+
 
 	/* Set up the name column */
 	sourcelist->priv->title_renderer = renderer = gtk_cell_renderer_text_new ();
-	g_object_set (G_OBJECT (sourcelist->priv->title_renderer),
+	g_object_set (sourcelist->priv->title_renderer,
 		      "ellipsize", PANGO_ELLIPSIZE_END,
 		      NULL);
 	gtk_tree_view_column_pack_start (sourcelist->priv->main_column, renderer, TRUE);
-	gtk_tree_view_column_set_cell_data_func (sourcelist->priv->main_column, renderer,
-						 (GtkTreeCellDataFunc)
-						 rb_sourcelist_title_cell_data_func,
-						 sourcelist, NULL);
+	gtk_tree_view_column_set_cell_data_func (sourcelist->priv->main_column,
+						 renderer,
+						 (GtkTreeCellDataFunc) rb_sourcelist_title_cell_data_func,
+						 sourcelist,
+						 NULL);
 	g_signal_connect_object (renderer, "edited", G_CALLBACK (source_name_edited_cb), sourcelist, 0);
 
+
+	/* Expander */
+	renderer = gossip_cell_renderer_expander_new ();
+	gtk_tree_view_column_pack_end (sourcelist->priv->main_column, renderer, FALSE);
+	gtk_tree_view_column_set_cell_data_func (sourcelist->priv->main_column,
+						 renderer,
+						 (GtkTreeCellDataFunc) rb_sourcelist_expander_cell_data_func,
+						 sourcelist,
+						 NULL);
+
+
 	sourcelist->priv->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (sourcelist->priv->treeview));
-	g_signal_connect_object (G_OBJECT (sourcelist->priv->selection),
+	g_signal_connect_object (sourcelist->priv->selection,
 			         "changed",
 			         G_CALLBACK (rb_sourcelist_selection_changed_cb),
 			         sourcelist,
 				 0);
-
-	rb_sourcelist_update_expander_visibility (sourcelist);
 }
 
 static void
@@ -290,10 +726,10 @@ rb_sourcelist_finalize (GObject *object)
 }
 
 static void
-rb_sourcelist_set_property (GObject *object,
-			    guint prop_id,
+rb_sourcelist_set_property (GObject      *object,
+			    guint         prop_id,
 			    const GValue *value,
-			    GParamSpec *pspec)
+			    GParamSpec   *pspec)
 {
 	RBSourceList *sourcelist = RB_SOURCELIST (object);
 	switch (prop_id)
@@ -308,9 +744,9 @@ rb_sourcelist_set_property (GObject *obj
 }
 
 static void
-rb_sourcelist_get_property (GObject *object,
-			    guint prop_id,
-			    GValue *value,
+rb_sourcelist_get_property (GObject    *object,
+			    guint       prop_id,
+			    GValue     *value,
 			    GParamSpec *pspec)
 {
 	RBSourceList *sourcelist = RB_SOURCELIST (object);
@@ -349,8 +785,8 @@ rb_sourcelist_new (RBShell *shell)
 }
 
 static void
-icon_notify_cb (RBSource *source,
-		GParamSpec *pspec,
+icon_notify_cb (RBSource     *source,
+		GParamSpec   *pspec,
 		RBSourceList *sourcelist)
 {
 	GtkTreeIter iter;
@@ -370,88 +806,233 @@ icon_notify_cb (RBSource *source,
 	gtk_tree_view_columns_autosize (GTK_TREE_VIEW (sourcelist->priv->treeview));
 }
 
+typedef struct {
+	const char *name;
+	gboolean    found;
+	GtkTreeIter iter;
+} FindGroup;
+
+/* adapted from gossip */
+static gboolean
+sourcelist_get_group_foreach (GtkTreeModel *model,
+			      GtkTreePath  *path,
+			      GtkTreeIter  *iter,
+			      FindGroup    *fg)
+{
+	char     *str;
+	gboolean  is_group;
+
+	/* Groups are only at the top level. */
+	if (gtk_tree_path_get_depth (path) != 1) {
+		return FALSE;
+	}
+
+	gtk_tree_model_get (model, iter,
+			    RB_SOURCELIST_MODEL_COLUMN_NAME, &str,
+			    RB_SOURCELIST_MODEL_COLUMN_IS_GROUP, &is_group,
+			    -1);
+	if (is_group && strcmp (str, fg->name) == 0) {
+		fg->found = TRUE;
+		fg->iter = *iter;
+	}
+
+	g_free (str);
+
+	return fg->found;
+}
+
+static gboolean
+rb_sourcelist_find_group_iter (RBSourceList  *sourcelist,
+			       RBSourceGroup *group,
+			       GtkTreeIter   *iter)
+{
+	FindGroup fg;
+
+	memset (&fg, 0, sizeof (fg));
+
+	fg.name = group->display_name;
+
+	gtk_tree_model_foreach (sourcelist->priv->real_model,
+				(GtkTreeModelForeachFunc) sourcelist_get_group_foreach,
+				&fg);
+
+	if (iter != NULL) {
+		*iter = fg.iter;
+	}
+
+	return fg.found;
+}
+
+static void
+sourcelist_get_group (RBSourceList  *sourcelist,
+		      RBSourceGroup *group,
+		      GtkTreeIter   *iter,
+		      gboolean      *created)
+{
+	gboolean found;
+
+	found = rb_sourcelist_find_group_iter (sourcelist,
+					       group,
+					       iter);
+	if (! found) {
+		if (created != NULL) {
+			*created = TRUE;
+		}
+
+		gtk_tree_store_append (GTK_TREE_STORE (sourcelist->priv->real_model), iter, NULL);
+		gtk_tree_store_set (GTK_TREE_STORE (sourcelist->priv->real_model), iter,
+				    RB_SOURCELIST_MODEL_COLUMN_PIXBUF, NULL,
+				    RB_SOURCELIST_MODEL_COLUMN_NAME, group->display_name,
+				    RB_SOURCELIST_MODEL_COLUMN_SOURCE, NULL,
+				    RB_SOURCELIST_MODEL_COLUMN_ATTRIBUTES, NULL,
+				    RB_SOURCELIST_MODEL_COLUMN_VISIBILITY, TRUE,
+				    RB_SOURCELIST_MODEL_COLUMN_IS_GROUP, TRUE,
+				    RB_SOURCELIST_MODEL_COLUMN_GROUP_CATEGORY, group->category,
+				    -1);
+	} else {
+		if (created != NULL) {
+			*created = FALSE;
+		}
+	}
+}
+
+static void
+name_notify_cb (GObject    *obj,
+		GParamSpec *pspec,
+		gpointer    data)
+{
+	RBSourceList *sourcelist = RB_SOURCELIST (data);
+	RBSource *source = RB_SOURCE (obj);
+	GtkTreeIter iter;
+	char *name;
+
+	if (rb_sourcelist_source_to_iter (sourcelist, source, &iter)) {
+		g_object_get (obj, "name", &name, NULL);
+		gtk_tree_store_set (GTK_TREE_STORE (sourcelist->priv->real_model),
+				    &iter,
+				    RB_SOURCELIST_MODEL_COLUMN_NAME, name, -1);
+		g_free (name);
+	}
+
+	gtk_tree_view_columns_autosize (GTK_TREE_VIEW (sourcelist->priv->treeview));
+}
+
+static void
+visibility_notify_cb (GObject    *obj,
+		      GParamSpec *pspec,
+		      gpointer    data)
+{
+	RBSourceList *sourcelist = RB_SOURCELIST (data);
+	RBSource     *source = RB_SOURCE (obj);
+	GtkTreeIter   iter;
+	gboolean      old_visibility;
+	gboolean      new_visibility;
+	char         *name;
+
+	g_object_get (obj,
+		      "visibility", &new_visibility,
+		      "name", &name,
+		      NULL);
+	rb_debug ("Source visibility changed: %s", name);
+	g_free (name);
+
+	gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (sourcelist->priv->filter_model));
+
+	g_assert (rb_sourcelist_source_to_iter (sourcelist, source, &iter));
+	if (gtk_tree_store_iter_depth (GTK_TREE_STORE (sourcelist->priv->real_model), &iter) > 0) {
+
+		gtk_tree_model_get (GTK_TREE_MODEL (sourcelist->priv->real_model), &iter,
+				    RB_SOURCELIST_MODEL_COLUMN_VISIBILITY, &old_visibility,
+				    -1);
+
+		if (old_visibility != new_visibility) {
+			gtk_tree_store_set (GTK_TREE_STORE (sourcelist->priv->real_model), &iter,
+					    RB_SOURCELIST_MODEL_COLUMN_VISIBILITY, new_visibility,
+					    -1);
+		}
+	}
+
+	gtk_tree_view_columns_autosize (GTK_TREE_VIEW (sourcelist->priv->treeview));
+}
+
+static void
+rb_sourcelist_group_update_visibility (RBSourceList  *sourcelist,
+				       RBSourceGroup *group)
+{
+	gboolean    found;
+	GtkTreeIter iter;
+
+	found = rb_sourcelist_find_group_iter (sourcelist, group, &iter);
+	if (found) {
+		gboolean has_child;
+
+		has_child = gtk_tree_model_iter_has_child (sourcelist->priv->real_model, &iter);
+		gtk_tree_store_set (GTK_TREE_STORE (sourcelist->priv->real_model), &iter,
+				    RB_SOURCELIST_MODEL_COLUMN_VISIBILITY, has_child,
+				    -1);
+		gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (sourcelist->priv->filter_model));
+	}
+}
+
 void
 rb_sourcelist_append (RBSourceList *sourcelist,
-		      RBSource *source,
-		      RBSource *parent)
+		      RBSource     *source,
+		      RBSource     *parent)
 {
-	GtkTreeIter iter;
+	GtkTreeIter    iter;
 	PangoAttrList *attrs;
-	char *name;
-	GdkPixbuf *pixbuf;
-	gboolean visible;
+	char          *name;
+	GdkPixbuf     *pixbuf;
+	gboolean       visible;
+	RBSourceGroup *group;
+	RBSourceGroupCategory group_category;
+	GtkTreePath   *expand_path;
 
 	g_return_if_fail (RB_IS_SOURCELIST (sourcelist));
 	g_return_if_fail (RB_IS_SOURCE (source));
 
-	g_object_get (source, "name", &name, NULL);
-	g_object_get (source, "icon", &pixbuf, NULL);
-	g_object_get (source, "visibility", &visible, NULL);
+	expand_path = NULL;
+	group_category = RB_SOURCE_GROUP_CATEGORY_FIXED;
+
+	g_object_get (source,
+		      "name", &name,
+		      "icon", &pixbuf,
+		      "visibility", &visible,
+		      "source-group", &group,
+		      NULL);
 
-	if (parent) {
+	if (parent != NULL) {
 		GtkTreeIter parent_iter;
 
-		rb_debug ("inserting source %p with parent", source);
+		rb_debug ("inserting source %p with parent %p", source, parent);
 		g_assert (rb_sourcelist_source_to_iter (sourcelist, parent, &parent_iter));
 		gtk_tree_store_append (GTK_TREE_STORE (sourcelist->priv->real_model), &iter, &parent_iter);
-
-		if (visible) {
-			sourcelist->priv->child_source_count++;
-			rb_sourcelist_update_expander_visibility (sourcelist);
-		}
 	} else {
-		GtkTreePath *group_path;
-		GtkTreePath *prev_group_path = NULL;
-		RBSourceListGroup group;
-		GtkTreeIter group_iter;
-
-		/* get the marker rows before and after the group for this source */
-		g_object_get (G_OBJECT (source), "sourcelist-group", &group, NULL);
-		group_path = rb_sourcelist_model_get_group_path (RB_SOURCELIST_MODEL (sourcelist->priv->filter_model),
-								 group);
-		g_assert (group_path);
-
-		if (group > 0) {
-			/* there's no marker row before the fixed source group, but we
-			 * don't need one anyway.
-			 */
-			prev_group_path = rb_sourcelist_model_get_group_path (RB_SOURCELIST_MODEL (sourcelist->priv->filter_model),
-									      group-1);
-			g_assert (prev_group_path);
+		GtkTreeIter    group_iter;
+		gboolean       created;
+		GtkTreePath   *path;
+
+		if (group == NULL) {
+			g_warning ("source %p has no group", source);
+			group = RB_SOURCE_GROUP_LIBRARY;
 		}
 
-		/* find the location to insert the source */
-		if (group == RB_SOURCELIST_GROUP_TRANSIENT) {
-			char *check_name = NULL;
+		rb_debug ("inserting source %p to group %s", source, group->name);
 
-			rb_debug ("inserting source %p in group %d in sorted order", source, group);
-			g_assert (prev_group_path);
+		sourcelist_get_group (sourcelist, group, &group_iter, &created);
 
-			gtk_tree_model_get_iter (sourcelist->priv->real_model, &group_iter, prev_group_path);
-			do {
-				g_free (check_name);
-
-				if (!gtk_tree_model_iter_next (sourcelist->priv->real_model, &group_iter))
-					break;
-
-				gtk_tree_model_get (sourcelist->priv->real_model,
-						    &group_iter,
-						    RB_SOURCELIST_MODEL_COLUMN_NAME, &check_name,
-						    -1);
-
-			} while (check_name && (strlen (check_name)) > 0 && (g_utf8_collate (name, check_name) > 0));
-
-			g_free (check_name);
+		/* always set group visible when adding a source */
+		gtk_tree_store_set (GTK_TREE_STORE (sourcelist->priv->real_model), &group_iter,
+				    RB_SOURCELIST_MODEL_COLUMN_VISIBILITY, TRUE,
+				    -1);
 
-		} else {
-			rb_debug ("inserting source %p in group %d", source, group);
-			gtk_tree_model_get_iter (sourcelist->priv->real_model, &group_iter, group_path);
-		}
+		group_category = group->category;
 
-		gtk_tree_store_insert_before (GTK_TREE_STORE (sourcelist->priv->real_model),
-					      &iter, NULL, &group_iter);
+		path = gtk_tree_model_get_path (sourcelist->priv->real_model, &group_iter);
+		expand_path = gtk_tree_model_filter_convert_child_path_to_path (GTK_TREE_MODEL_FILTER (sourcelist->priv->filter_model), path);
+		gtk_tree_path_free (path);
 
-		gtk_tree_path_free (group_path);
+		gtk_tree_store_append (GTK_TREE_STORE (sourcelist->priv->real_model), &iter, &group_iter);
 	}
 
 	attrs = pango_attr_list_new ();
@@ -462,6 +1043,8 @@ rb_sourcelist_append (RBSourceList *sour
 			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, source,
 			    RB_SOURCELIST_MODEL_COLUMN_ATTRIBUTES, attrs,
 			    RB_SOURCELIST_MODEL_COLUMN_VISIBILITY, visible,
+			    RB_SOURCELIST_MODEL_COLUMN_IS_GROUP, FALSE,
+			    RB_SOURCELIST_MODEL_COLUMN_GROUP_CATEGORY, group_category,
 			    -1);
 
 	pango_attr_list_unref (attrs);
@@ -471,23 +1054,28 @@ rb_sourcelist_append (RBSourceList *sour
 	}
 	g_free (name);
 
-	g_signal_connect_object (G_OBJECT (source), "notify::name", G_CALLBACK (name_notify_cb), sourcelist, 0);
-	g_signal_connect_object (G_OBJECT (source), "notify::visibility", G_CALLBACK (visibility_notify_cb), sourcelist, 0);
-	g_signal_connect_object (G_OBJECT (source), "notify::icon", G_CALLBACK (icon_notify_cb), sourcelist, 0);
+	g_signal_connect_object (source, "notify::name", G_CALLBACK (name_notify_cb), sourcelist, 0);
+	g_signal_connect_object (source, "notify::visibility", G_CALLBACK (visibility_notify_cb), sourcelist, 0);
+	g_signal_connect_object (source, "notify::icon", G_CALLBACK (icon_notify_cb), sourcelist, 0);
+
+	if (expand_path != NULL) {
+		gtk_tree_view_expand_row (GTK_TREE_VIEW (sourcelist->priv->treeview), expand_path, TRUE);
+		gtk_tree_path_free (expand_path);
+	}
 
 	gtk_tree_view_columns_autosize (GTK_TREE_VIEW (sourcelist->priv->treeview));
 }
 
 typedef struct _SourcePath {
-	RBSource *source;
+	RBSource    *source;
 	GtkTreePath *path;
 } SourcePath;
 
 static gboolean
 match_source_to_iter (GtkTreeModel *model,
-		      GtkTreePath *path,
-		      GtkTreeIter *iter,
-		      SourcePath *sp)
+		      GtkTreePath  *path,
+		      GtkTreeIter  *iter,
+		      SourcePath   *sp)
 {
 	RBSource *target = NULL;
 	gboolean  res;
@@ -509,12 +1097,14 @@ match_source_to_iter (GtkTreeModel *mode
 
 static gboolean
 rb_sourcelist_source_to_iter (RBSourceList *sourcelist,
-			      RBSource *source,
-			      GtkTreeIter *iter)
+			      RBSource     *source,
+			      GtkTreeIter  *iter)
 {
-	SourcePath *sp = g_new0 (SourcePath,1);
-	gboolean ret = FALSE;
+	SourcePath *sp;
+	gboolean    ret;
 
+	ret = FALSE;
+	sp = g_new0 (SourcePath, 1);
 	sp->source = source;
 
 	gtk_tree_model_foreach (sourcelist->priv->real_model, (GtkTreeModelForeachFunc) match_source_to_iter, sp);
@@ -532,12 +1122,14 @@ rb_sourcelist_source_to_iter (RBSourceLi
 
 static gboolean
 rb_sourcelist_visible_source_to_iter (RBSourceList *sourcelist,
-				      RBSource *source,
-				      GtkTreeIter *iter)
+				      RBSource     *source,
+				      GtkTreeIter  *iter)
 {
-	SourcePath *sp = g_new0 (SourcePath,1);
-	gboolean ret = FALSE;
+	SourcePath *sp;
+	gboolean    ret;
 
+	ret = FALSE;
+	sp = g_new0 (SourcePath, 1);
 	sp->source = source;
 
 	gtk_tree_model_foreach (sourcelist->priv->filter_model, (GtkTreeModelForeachFunc) match_source_to_iter, sp);
@@ -555,9 +1147,9 @@ rb_sourcelist_visible_source_to_iter (RB
 
 void
 rb_sourcelist_edit_source_name (RBSourceList *sourcelist,
-				RBSource *source)
+				RBSource     *source)
 {
-	GtkTreeIter iter;
+	GtkTreeIter  iter;
 	GtkTreePath *path;
 
 	g_assert (rb_sourcelist_visible_source_to_iter (sourcelist, source, &iter));
@@ -566,7 +1158,7 @@ rb_sourcelist_edit_source_name (RBSource
 
 	/* Make cell editable just for the moment.
 	   We'll turn it off once editing is done. */
-	g_object_set (G_OBJECT (sourcelist->priv->title_renderer), "editable", TRUE, NULL);
+	g_object_set (sourcelist->priv->title_renderer, "editable", TRUE, NULL);
 
 	gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (sourcelist->priv->treeview),
 					  path, sourcelist->priv->main_column,
@@ -578,8 +1170,8 @@ rb_sourcelist_edit_source_name (RBSource
 
 static void
 set_source_playing (RBSourceList *sourcelist,
-		    RBSource *source,
-		    gboolean playing)
+		    RBSource     *source,
+		    gboolean      playing)
 {
 	GtkTreeIter iter;
 	g_assert (rb_sourcelist_source_to_iter (sourcelist, source, &iter));
@@ -589,7 +1181,7 @@ set_source_playing (RBSourceList *source
 
 void
 rb_sourcelist_set_playing_source (RBSourceList *sourcelist,
-				  RBSource *source)
+				  RBSource     *source)
 {
 	if (sourcelist->priv->playing_source)
 		set_source_playing (sourcelist, sourcelist->priv->playing_source, FALSE);
@@ -601,37 +1193,38 @@ rb_sourcelist_set_playing_source (RBSour
 
 void
 rb_sourcelist_remove (RBSourceList *sourcelist,
-		      RBSource *source)
+		      RBSource     *source)
 {
-	GtkTreeIter iter;
+	GtkTreeIter    iter;
+	RBSourceGroup *group;
 
 	g_assert (rb_sourcelist_source_to_iter (sourcelist, source, &iter));
 
-	if (gtk_tree_store_iter_depth (GTK_TREE_STORE (sourcelist->priv->real_model), &iter) > 0) {
-		gboolean visible;
-
-		g_object_get (source, "visibility", &visible, NULL);
-		if (visible)
-			sourcelist->priv->child_source_count--;
-		rb_sourcelist_update_expander_visibility (sourcelist);
+	if (source == sourcelist->priv->playing_source) {
+		rb_sourcelist_set_playing_source (sourcelist, NULL);
 	}
 
-	if (source == sourcelist->priv->playing_source)
-		rb_sourcelist_set_playing_source (sourcelist, NULL);
+	g_object_get (source,
+		      "source-group", &group,
+		      NULL);
 
 	gtk_tree_store_remove (GTK_TREE_STORE (sourcelist->priv->real_model), &iter);
-	g_signal_handlers_disconnect_by_func (G_OBJECT (source),
+	g_signal_handlers_disconnect_by_func (source,
 					      G_CALLBACK (name_notify_cb), sourcelist);
-        g_signal_handlers_disconnect_by_func (G_OBJECT (source),
+        g_signal_handlers_disconnect_by_func (source,
 					      G_CALLBACK (visibility_notify_cb),
                                              sourcelist);
 
+	if (group != NULL) {
+		rb_sourcelist_group_update_visibility (sourcelist, group);
+	}
+
 	gtk_tree_view_columns_autosize (GTK_TREE_VIEW (sourcelist->priv->treeview));
 }
 
 void
 rb_sourcelist_select (RBSourceList *sourcelist,
-		      RBSource *source)
+		      RBSource     *source)
 {
 	GtkTreeIter iter;
 
@@ -641,12 +1234,12 @@ rb_sourcelist_select (RBSourceList *sour
 
 static void
 rb_sourcelist_selection_changed_cb (GtkTreeSelection *selection,
-				    RBSourceList *sourcelist)
+				    RBSourceList     *sourcelist)
 {
-	GtkTreeIter iter;
+	GtkTreeIter   iter;
 	GtkTreeModel *cindy;
-	gpointer target = NULL;
-	RBSource *source;
+	gpointer      target = NULL;
+	RBSource     *source;
 
 	if (!gtk_tree_selection_get_selected (sourcelist->priv->selection,
 					      &cindy, &iter))
@@ -659,230 +1252,20 @@ rb_sourcelist_selection_changed_cb (GtkT
 
 	g_return_if_fail (RB_IS_SOURCE (target));
 	source = target;
-	g_signal_emit (G_OBJECT (sourcelist), rb_sourcelist_signals[SELECTED], 0, source);
+	g_signal_emit (sourcelist, rb_sourcelist_signals[SELECTED], 0, source);
 
 	g_object_unref (target);
 }
 
 static void
-drop_received_cb (RBSourceListModel *model,
-		  RBSource *target,
-		  GtkTreeViewDropPosition pos,
-		  GtkSelectionData *data,
-		  RBSourceList *sourcelist)
-{
-	rb_debug ("drop recieved");
-	/* Proxy the signal. */
-	g_signal_emit (G_OBJECT (sourcelist), rb_sourcelist_signals[DROP_RECEIVED], 0, target, data);
-}
-
-static void
-row_activated_cb (GtkTreeView *treeview,
-		  GtkTreePath *path,
-		  GtkTreeViewColumn *column,
-		  RBSourceList *sourcelist)
-{
-	GtkTreeModel *model;
-	GtkTreeIter iter;
-	RBSource *target;
-
-	model = gtk_tree_view_get_model (treeview);
-	g_return_if_fail (gtk_tree_model_get_iter (model, &iter, path));
-	gtk_tree_model_get (model, &iter, RB_SOURCELIST_MODEL_COLUMN_SOURCE, &target, -1);
-
-	if (target != NULL) {
-		g_signal_emit (G_OBJECT (sourcelist), rb_sourcelist_signals[SOURCE_ACTIVATED], 0, target);
-		g_object_unref (target);
-	}
-}
-
-static gboolean
-emit_show_popup (GtkTreeView *treeview,
-		 RBSourceList *sourcelist)
-{
-	GtkTreeIter iter;
-	RBSource *target;
-	gboolean ret;
-
-	if (!gtk_tree_selection_get_selected (gtk_tree_view_get_selection (treeview),
-					      NULL, &iter))
-		return FALSE;
-
-	gtk_tree_model_get (sourcelist->priv->filter_model, &iter,
-			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &target, -1);
-	if (target == NULL)
-		return FALSE;
-
-	g_return_val_if_fail (RB_IS_SOURCE (target), FALSE);
-
-	g_signal_emit (G_OBJECT (sourcelist), rb_sourcelist_signals[SHOW_POPUP], 0, target, &ret);
-
-	if (target != NULL) {
-		g_object_unref (target);
-	}
-
-	return ret;
-}
-
-static gboolean
-button_press_cb (GtkTreeView *treeview,
-		 GdkEventButton *event,
-		 RBSourceList *sourcelist)
-{
-	GtkTreeIter iter;
-	GtkTreePath *path;
-
-	if (event->button != 3)
-		return FALSE;
-
-	if (gtk_tree_view_get_path_at_pos (treeview, event->x, event->y,
-					   &path, NULL, NULL, NULL) == FALSE) {
-		/* pointer is over empty space */
-		GtkUIManager *uimanager;
-		g_object_get (sourcelist->priv->shell, "ui-manager", &uimanager, NULL);
-		rb_gtk_action_popup_menu (uimanager, "/SourceListPopup");
-		g_object_unref (uimanager);
-		return TRUE;
-	}
-
-	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (sourcelist->priv->filter_model), &iter, path))
-		gtk_tree_selection_select_iter (gtk_tree_view_get_selection (treeview), &iter);
-
-	return emit_show_popup (treeview, sourcelist);
-}
-
-static gboolean
-key_release_cb (GtkTreeView *treeview,
-		GdkEventKey *event,
-		RBSourceList *sourcelist)
-{
-	GtkTreeIter iter;
-	RBSource *target;
-	gboolean res;
-
-	/* F2 = rename playlist */
-	if (event->keyval != GDK_F2)
-		return FALSE;
-
-	if (!gtk_tree_selection_get_selected (sourcelist->priv->selection, NULL, &iter))
-		return FALSE;
-
-	gtk_tree_model_get (sourcelist->priv->filter_model, &iter,
-			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &target, -1);
-	if (target == NULL)
-		return FALSE;
-
-	res = FALSE;
-	if (rb_source_can_rename (target)) {
-		rb_sourcelist_edit_source_name (sourcelist, target);
-		res = TRUE;
-	}
-
-	g_object_unref (target);
-
-	return res;
-}
-
-static gboolean
-popup_menu_cb (GtkTreeView *treeview,
-	       RBSourceList *sourcelist)
-{
-	return emit_show_popup (treeview, sourcelist);
-}
-
-static void
-name_notify_cb (GObject *obj,
-		GParamSpec *pspec,
-		gpointer data)
-{
-	RBSourceList *sourcelist = RB_SOURCELIST (data);
-	RBSource *source = RB_SOURCE (obj);
-	GtkTreeIter iter;
-	char *name;
-
-	if (rb_sourcelist_source_to_iter (sourcelist, source, &iter)) {
-		g_object_get (obj, "name", &name, NULL);
-		gtk_tree_store_set (GTK_TREE_STORE (sourcelist->priv->real_model),
-				    &iter,
-				    RB_SOURCELIST_MODEL_COLUMN_NAME, name, -1);
-		g_free (name);
-	}
-
-	gtk_tree_view_columns_autosize (GTK_TREE_VIEW (sourcelist->priv->treeview));
-}
-
-static void
-visibility_notify_cb (GObject *obj,
-		      GParamSpec *pspec,
-		      gpointer data)
-{
-	RBSourceList *sourcelist = RB_SOURCELIST (data);
-	RBSource *source = RB_SOURCE (obj);
-	GtkTreeIter iter;
-	gboolean old_visibility;
-	gboolean new_visibility;
-	char *name;
-
-	g_object_get (obj,
-		      "visibility", &new_visibility,
-		      "name", &name,
-		      NULL);
-	rb_debug ("Source visibility changed: %s", name);
-	g_free (name);
-
-	gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (sourcelist->priv->filter_model));
-
-	g_assert (rb_sourcelist_source_to_iter (sourcelist, source, &iter));
-	if (gtk_tree_store_iter_depth (GTK_TREE_STORE (sourcelist->priv->real_model), &iter) > 0) {
-
-		gtk_tree_model_get (GTK_TREE_MODEL (sourcelist->priv->real_model), &iter,
-				    RB_SOURCELIST_MODEL_COLUMN_VISIBILITY, &old_visibility,
-				    -1);
-
-		if (old_visibility != new_visibility) {
-			sourcelist->priv->child_source_count += new_visibility ? 1 : -1;
-			rb_sourcelist_update_expander_visibility (sourcelist);
-
-			gtk_tree_store_set (GTK_TREE_STORE (sourcelist->priv->real_model), &iter,
-					    RB_SOURCELIST_MODEL_COLUMN_VISIBILITY, new_visibility,
-					    -1);
-		}
-	}
-
-	gtk_tree_view_columns_autosize (GTK_TREE_VIEW (sourcelist->priv->treeview));
-}
-
-static void
-rb_sourcelist_title_cell_data_func (GtkTreeViewColumn *column,
-				    GtkCellRenderer *renderer,
-				    GtkTreeModel *tree_model,
-				    GtkTreeIter *iter,
-				    RBSourceList *sourcelist)
-{
-	char *str;
-	gboolean playing;
-
-	gtk_tree_model_get (GTK_TREE_MODEL (sourcelist->priv->filter_model), iter,
-			    RB_SOURCELIST_MODEL_COLUMN_NAME, &str,
-			    RB_SOURCELIST_MODEL_COLUMN_PLAYING, &playing,
-			    -1);
-
-	g_object_set (G_OBJECT (renderer), "text", str,
-		      "weight", playing ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
-		      NULL);
-
-	g_free (str);
-}
-
-static void
 source_name_edited_cb (GtkCellRendererText *renderer,
-		       const char *pathstr,
-		       const char *text,
-		       RBSourceList *sourcelist)
+		       const char          *pathstr,
+		       const char          *text,
+		       RBSourceList        *sourcelist)
 {
 	GtkTreePath *path;
-	GtkTreeIter iter;
-	RBSource *source;
+	GtkTreeIter  iter;
+	RBSource    *source;
 
 	if (text[0] == '\0')
 		return;
@@ -896,31 +1279,13 @@ source_name_edited_cb (GtkCellRendererTe
 	if (source == NULL)
 		return;
 
-	g_object_set (G_OBJECT (source), "name", text, NULL);
+	g_object_set (source, "name", text, NULL);
 
 	gtk_tree_path_free (path);
 
-	g_object_set (G_OBJECT (renderer), "editable", FALSE, NULL);
+	g_object_set (renderer, "editable", FALSE, NULL);
 
 	gtk_tree_view_columns_autosize (GTK_TREE_VIEW (sourcelist->priv->treeview));
 
 	g_object_unref (source);
-}
-
-static void
-rb_sourcelist_update_expander_visibility (RBSourceList *sourcelist)
-{
-	gboolean visible;
-	GtkTreeViewColumn *column;
-
-	g_assert (sourcelist->priv->child_source_count >= 0);
-
-	visible = (sourcelist->priv->child_source_count > 0);
-	if (visible)
-		column = sourcelist->priv->main_column;
-	else
-		column = sourcelist->priv->hidden_column;
-
-	gtk_tree_view_set_expander_column (GTK_TREE_VIEW (sourcelist->priv->treeview),
-					   column);
 }
Index: sources/rb-static-playlist-source.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/sources/rb-static-playlist-source.c,v
retrieving revision 1.33
diff -p -u -r1.33 rb-static-playlist-source.c
--- sources/rb-static-playlist-source.c	22 Nov 2006 03:06:51 -0000	1.33
+++ sources/rb-static-playlist-source.c	29 Nov 2006 17:28:59 -0000
@@ -294,7 +294,7 @@ rb_static_playlist_source_new (RBShell *
 					"shell", shell,
 					"is-local", local,
 					"entry-type", entry_type,
-					"sourcelist-group", RB_SOURCELIST_GROUP_PERSISTANT,
+					"source-group", RB_SOURCE_GROUP_PLAYLISTS,
 					NULL));
 }
 
Index: widgets/Makefile.am
===================================================================
RCS file: /cvs/gnome/rhythmbox/widgets/Makefile.am,v
retrieving revision 1.34
diff -p -u -r1.34 Makefile.am
--- widgets/Makefile.am	10 Jul 2006 04:19:59 -0000	1.34
+++ widgets/Makefile.am	29 Nov 2006 17:28:59 -0000
@@ -28,6 +28,8 @@ librbwidgets_la_SOURCES =				\
 	rb-cell-renderer-pixbuf.h			\
 	rb-cell-renderer-rating.c			\
 	rb-cell-renderer-rating.h			\
+	gossip-cell-renderer-expander.c			\
+	gossip-cell-renderer-expander.h			\
 	rb-song-info.c					\
 	rb-song-info.h					\
 	rb-rating.c					\
Index: widgets/gossip-cell-renderer-expander.c
===================================================================
RCS file: widgets/gossip-cell-renderer-expander.c
diff -N widgets/gossip-cell-renderer-expander.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ widgets/gossip-cell-renderer-expander.c	29 Nov 2006 17:28:59 -0000
@@ -0,0 +1,483 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006  Kristian Rietveld <kris@babi-pangang.org>
+ *
+ * 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 of the
+ * License, 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* To do:
+ *  - should probably cancel animation if model changes
+ *  - need to handle case where node-in-animation is removed
+ *  - it only handles a single animation at a time; but I guess users
+ *    aren't fast enough to trigger two or more animations at once anyway :P
+ *    (could guard for this by just cancelling the "old" animation, and
+ *     start the new one).
+ */
+
+#include <gtk/gtktreeview.h>
+
+#include "gossip-cell-renderer-expander.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CELL_RENDERER_EXPANDER, GossipCellRendererExpanderPriv))
+
+static void     gossip_cell_renderer_expander_init         (GossipCellRendererExpander      *expander);
+static void     gossip_cell_renderer_expander_class_init   (GossipCellRendererExpanderClass *klass);
+static void     gossip_cell_renderer_expander_get_property (GObject                         *object,
+							    guint                            param_id,
+							    GValue                          *value,
+							    GParamSpec                      *pspec);
+static void     gossip_cell_renderer_expander_set_property (GObject                         *object,
+							    guint                            param_id,
+							    const GValue                    *value,
+							    GParamSpec                      *pspec);
+static void     gossip_cell_renderer_expander_finalize     (GObject                         *object);
+static void     gossip_cell_renderer_expander_get_size     (GtkCellRenderer                 *cell,
+							    GtkWidget                       *widget,
+							    GdkRectangle                    *cell_area,
+							    gint                            *x_offset,
+							    gint                            *y_offset,
+							    gint                            *width,
+							    gint                            *height);
+static void     gossip_cell_renderer_expander_render       (GtkCellRenderer                 *cell,
+							    GdkWindow                       *window,
+							    GtkWidget                       *widget,
+							    GdkRectangle                    *background_area,
+							    GdkRectangle                    *cell_area,
+							    GdkRectangle                    *expose_area,
+							    GtkCellRendererState             flags);
+static gboolean gossip_cell_renderer_expander_activate     (GtkCellRenderer                 *cell,
+							    GdkEvent                        *event,
+							    GtkWidget                       *widget,
+							    const gchar                     *path,
+							    GdkRectangle                    *background_area,
+							    GdkRectangle                    *cell_area,
+							    GtkCellRendererState             flags);
+
+/* Properties */
+enum {
+	PROP_0,
+	PROP_EXPANDER_STYLE,
+	PROP_EXPANDER_SIZE,
+	PROP_ACTIVATABLE
+};
+
+typedef struct _GossipCellRendererExpanderPriv GossipCellRendererExpanderPriv;
+
+struct _GossipCellRendererExpanderPriv {
+	GtkExpanderStyle     expander_style;
+	gint                 expander_size;
+
+	GtkTreeView         *animation_view;
+	GtkTreeRowReference *animation_node;
+	GtkExpanderStyle     animation_style;
+	guint                animation_timeout;
+	GdkRectangle         animation_area;
+
+	guint                activatable : 1;
+	guint                animation_expanding : 1;
+};
+
+G_DEFINE_TYPE (GossipCellRendererExpander, gossip_cell_renderer_expander, GTK_TYPE_CELL_RENDERER)
+
+static void
+gossip_cell_renderer_expander_init (GossipCellRendererExpander *expander)
+{
+	GossipCellRendererExpanderPriv *priv;
+
+	priv = GET_PRIV (expander);
+
+	priv->expander_style = GTK_EXPANDER_COLLAPSED;
+	priv->expander_size = 12;
+	priv->activatable = TRUE;
+	priv->animation_node = NULL;
+
+	GTK_CELL_RENDERER (expander)->xpad = 2;
+	GTK_CELL_RENDERER (expander)->ypad = 2;
+	GTK_CELL_RENDERER (expander)->mode = GTK_CELL_RENDERER_MODE_ACTIVATABLE;
+}
+
+static void
+gossip_cell_renderer_expander_class_init (GossipCellRendererExpanderClass *klass)
+{
+	GObjectClass         *object_class;
+	GtkCellRendererClass *cell_class;
+
+	object_class  = G_OBJECT_CLASS (klass);
+	cell_class = GTK_CELL_RENDERER_CLASS (klass);
+
+	object_class->finalize = gossip_cell_renderer_expander_finalize;
+
+	object_class->get_property = gossip_cell_renderer_expander_get_property;
+	object_class->set_property = gossip_cell_renderer_expander_set_property;
+
+	cell_class->get_size = gossip_cell_renderer_expander_get_size;
+	cell_class->render = gossip_cell_renderer_expander_render;
+	cell_class->activate = gossip_cell_renderer_expander_activate;
+
+	g_object_class_install_property (object_class,
+					 PROP_EXPANDER_STYLE,
+					 g_param_spec_enum ("expander-style",
+							    "Expander Style",
+							    "Style to use when painting the expander",
+							    GTK_TYPE_EXPANDER_STYLE,
+							    GTK_EXPANDER_COLLAPSED,
+							    G_PARAM_READWRITE));
+
+	g_object_class_install_property (object_class,
+					 PROP_EXPANDER_SIZE,
+					 g_param_spec_int ("expander-size",
+							   "Expander Size",
+							   "The size of the expander",
+							   0,
+							   G_MAXINT,
+							   12,
+							   G_PARAM_READWRITE));
+
+	g_object_class_install_property (object_class,
+					 PROP_ACTIVATABLE,
+					 g_param_spec_boolean ("activatable",
+							       "Activatable",
+							       "The expander can be activated",
+							       TRUE,
+							       G_PARAM_READWRITE));
+
+	g_type_class_add_private (object_class, sizeof (GossipCellRendererExpanderPriv));
+}
+
+static void
+gossip_cell_renderer_expander_get_property (GObject    *object,
+					    guint       param_id,
+					    GValue     *value,
+					    GParamSpec *pspec)
+{
+	GossipCellRendererExpander     *expander;
+	GossipCellRendererExpanderPriv *priv;
+
+	expander = GOSSIP_CELL_RENDERER_EXPANDER (object);
+	priv = GET_PRIV (expander);
+
+	switch (param_id) {
+	case PROP_EXPANDER_STYLE:
+		g_value_set_enum (value, priv->expander_style);
+		break;
+
+	case PROP_EXPANDER_SIZE:
+		g_value_set_int (value, priv->expander_size);
+		break;
+
+	case PROP_ACTIVATABLE:
+		g_value_set_boolean (value, priv->activatable);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static void
+gossip_cell_renderer_expander_set_property (GObject      *object,
+					    guint         param_id,
+					    const GValue *value,
+					    GParamSpec   *pspec)
+{
+	GossipCellRendererExpander     *expander;
+	GossipCellRendererExpanderPriv *priv;
+
+	expander = GOSSIP_CELL_RENDERER_EXPANDER (object);
+	priv = GET_PRIV (expander);
+
+	switch (param_id) {
+	case PROP_EXPANDER_STYLE:
+		priv->expander_style = g_value_get_enum (value);
+		break;
+
+	case PROP_EXPANDER_SIZE:
+		priv->expander_size = g_value_get_int (value);
+		break;
+
+	case PROP_ACTIVATABLE:
+		priv->activatable = g_value_get_boolean (value);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static void
+gossip_cell_renderer_expander_finalize (GObject *object)
+{
+	GossipCellRendererExpanderPriv *priv;
+
+	priv = GET_PRIV (object);
+
+	if (priv->animation_timeout) {
+		g_source_remove (priv->animation_timeout);
+		priv->animation_timeout = 0;
+	}
+
+	if (priv->animation_node) {
+		gtk_tree_row_reference_free (priv->animation_node);
+	}
+
+	(* G_OBJECT_CLASS (gossip_cell_renderer_expander_parent_class)->finalize) (object);
+}
+
+GtkCellRenderer *
+gossip_cell_renderer_expander_new (void)
+{
+	return g_object_new (GOSSIP_TYPE_CELL_RENDERER_EXPANDER, NULL);
+}
+
+static void
+gossip_cell_renderer_expander_get_size (GtkCellRenderer *cell,
+					GtkWidget       *widget,
+					GdkRectangle    *cell_area,
+					gint            *x_offset,
+					gint            *y_offset,
+					gint            *width,
+					gint            *height)
+{
+	GossipCellRendererExpander     *expander;
+	GossipCellRendererExpanderPriv *priv;
+
+	expander = (GossipCellRendererExpander*) cell;
+	priv = GET_PRIV (expander);
+
+	if (cell_area) {
+		if (x_offset) {
+			*x_offset = cell->xalign * (cell_area->width - (priv->expander_size + (2 * cell->xpad)));
+			*x_offset = MAX (*x_offset, 0);
+		}
+
+		if (y_offset) {
+			*y_offset = cell->yalign * (cell_area->height - (priv->expander_size + (2 * cell->ypad)));
+			*y_offset = MAX (*y_offset, 0);
+		}
+	} else {
+		if (x_offset)
+			*x_offset = 0;
+
+		if (y_offset)
+			*y_offset = 0;
+	}
+
+	if (width)
+		*width = cell->xpad * 2 + priv->expander_size;
+
+	if (height)
+		*height = cell->ypad * 2 + priv->expander_size;
+}
+
+static void
+gossip_cell_renderer_expander_render (GtkCellRenderer      *cell,
+				      GdkWindow            *window,
+				      GtkWidget            *widget,
+				      GdkRectangle         *background_area,
+				      GdkRectangle         *cell_area,
+				      GdkRectangle         *expose_area,
+				      GtkCellRendererState  flags)
+{
+	GossipCellRendererExpander     *expander;
+	GossipCellRendererExpanderPriv *priv;
+	GtkExpanderStyle                expander_style;
+	gint                            x_offset, y_offset;
+
+	expander = (GossipCellRendererExpander*) cell;
+	priv = GET_PRIV (expander);
+
+	if (priv->animation_node) {
+		GtkTreePath *path;
+		GdkRectangle rect;
+
+		/* Not sure if I like this ... */
+		path = gtk_tree_row_reference_get_path (priv->animation_node);
+		gtk_tree_view_get_background_area (priv->animation_view, path,
+						   NULL, &rect);
+		gtk_tree_path_free (path);
+
+		if (background_area->y == rect.y)
+			expander_style = priv->animation_style;
+		else
+			expander_style = priv->expander_style;
+	} else
+		expander_style = priv->expander_style;
+
+	gossip_cell_renderer_expander_get_size (cell, widget, cell_area,
+						&x_offset, &y_offset,
+						NULL, NULL);
+
+	gtk_paint_expander (widget->style,
+			    window,
+			    GTK_STATE_NORMAL,
+			    expose_area,
+			    widget,
+			    "treeview",
+			    cell_area->x + x_offset + cell->xpad + priv->expander_size / 2,
+			    cell_area->y + y_offset + cell->ypad + priv->expander_size / 2,
+			    expander_style);
+}
+
+static void
+invalidate_node (GtkTreeView *tree_view,
+		 GtkTreePath *path)
+{
+       GdkWindow    *bin_window;
+       GdkRectangle  rect;
+
+       bin_window = gtk_tree_view_get_bin_window (tree_view);
+
+       gtk_tree_view_get_background_area (tree_view, path, NULL, &rect);
+
+       rect.x = 0;
+       rect.width = GTK_WIDGET (tree_view)->allocation.width;
+
+       gdk_window_invalidate_rect (bin_window, &rect, TRUE);
+}
+
+static gboolean
+do_animation (GossipCellRendererExpander *expander)
+{
+	GossipCellRendererExpanderPriv *priv;
+	GtkTreePath                    *path;
+	gboolean                        done = FALSE;
+
+	priv = GET_PRIV (expander);
+
+	if (priv->animation_expanding) {
+		if (priv->animation_style == GTK_EXPANDER_SEMI_COLLAPSED)
+			priv->animation_style = GTK_EXPANDER_SEMI_EXPANDED;
+		else if (priv->animation_style == GTK_EXPANDER_SEMI_EXPANDED) {
+			priv->animation_style = GTK_EXPANDER_EXPANDED;
+			done = TRUE;
+		}
+	} else {
+		if (priv->animation_style == GTK_EXPANDER_SEMI_EXPANDED)
+			priv->animation_style = GTK_EXPANDER_SEMI_COLLAPSED;
+		else if (priv->animation_style == GTK_EXPANDER_SEMI_COLLAPSED) {
+			priv->animation_style = GTK_EXPANDER_COLLAPSED;
+			done = TRUE;
+		}
+	}
+
+	path = gtk_tree_row_reference_get_path (priv->animation_node);
+	invalidate_node (priv->animation_view, path);
+	gtk_tree_path_free (path);
+
+	if (done) {
+		gtk_tree_row_reference_free (priv->animation_node);
+		priv->animation_node = NULL;
+		priv->animation_timeout = 0;
+	}
+
+	return !done;
+}
+
+static gboolean
+animation_timeout (gpointer data)
+{
+	gboolean retval;
+
+	GDK_THREADS_ENTER ();
+
+	retval = do_animation (data);
+
+	GDK_THREADS_LEAVE ();
+
+	return retval;
+}
+
+static void
+gossip_cell_renderer_expander_start_animation (GossipCellRendererExpander *expander,
+					       GtkTreeView                *tree_view,
+					       GtkTreePath                *path,
+					       gboolean                    expanding,
+					       GdkRectangle               *background_area)
+{
+	GossipCellRendererExpanderPriv *priv;
+
+	priv = GET_PRIV (expander);
+
+	if (expanding) {
+		priv->animation_style = GTK_EXPANDER_SEMI_COLLAPSED;
+	} else {
+		priv->animation_style = GTK_EXPANDER_SEMI_EXPANDED;
+	}
+
+	invalidate_node (tree_view, path);
+
+	priv->animation_expanding = expanding;
+	priv->animation_view = tree_view;
+	priv->animation_node = gtk_tree_row_reference_new (gtk_tree_view_get_model (tree_view), path);
+	priv->animation_timeout = g_timeout_add (50, animation_timeout, expander);
+}
+
+static gboolean
+gossip_cell_renderer_expander_activate (GtkCellRenderer      *cell,
+					GdkEvent             *event,
+					GtkWidget            *widget,
+					const gchar          *path_string,
+					GdkRectangle         *background_area,
+					GdkRectangle         *cell_area,
+					GtkCellRendererState  flags)
+{
+	GossipCellRendererExpander     *expander;
+	GossipCellRendererExpanderPriv *priv;
+	GtkTreePath                    *path;
+	gboolean                        animate;
+	gboolean                        expanding;
+
+	expander = GOSSIP_CELL_RENDERER_EXPANDER (cell);
+	priv = GET_PRIV (cell);
+
+	if (!GTK_IS_TREE_VIEW (widget) || !priv->activatable)
+		return FALSE;
+
+	path = gtk_tree_path_new_from_string (path_string);
+
+#if 0
+	if (gtk_tree_path_get_depth (path) > 1) {
+		gtk_tree_path_free (path);
+		return TRUE;
+	}
+#endif
+
+	g_object_get (gtk_widget_get_settings (GTK_WIDGET (widget)),
+		      "gtk-enable-animations", &animate,
+		      NULL);
+
+	if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
+		gtk_tree_view_collapse_row (GTK_TREE_VIEW (widget), path);
+		expanding = FALSE;
+	} else {
+		gtk_tree_view_expand_row (GTK_TREE_VIEW (widget), path, FALSE);
+		expanding = TRUE;
+	}
+
+	if (animate) {
+		gossip_cell_renderer_expander_start_animation (expander,
+							       GTK_TREE_VIEW (widget),
+							       path,
+							       expanding,
+							       background_area);
+	}
+
+	gtk_tree_path_free (path);
+
+	return TRUE;
+}
Index: widgets/gossip-cell-renderer-expander.h
===================================================================
RCS file: widgets/gossip-cell-renderer-expander.h
diff -N widgets/gossip-cell-renderer-expander.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ widgets/gossip-cell-renderer-expander.h	29 Nov 2006 17:28:59 -0000
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006  Kristian Rietveld <kris@babi-pangang.org>
+ *
+ * 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 of the
+ * License, 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GOSSIP_CELL_RENDERER_EXPANDER_H__
+#define __GOSSIP_CELL_RENDERER_EXPANDER_H__
+
+#include <gtk/gtkcellrenderer.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CELL_RENDERER_EXPANDER		(gossip_cell_renderer_expander_get_type ())
+#define GOSSIP_CELL_RENDERER_EXPANDER(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GOSSIP_TYPE_CELL_RENDERER_EXPANDER, GossipCellRendererExpander))
+#define GOSSIP_CELL_RENDERER_EXPANDER_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), GOSSIP_TYPE_CELL_RENDERER_EXPANDER, GossipCellRendererExpanderClass))
+#define GOSSIP_IS_CELL_RENDERER_EXPANDER(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOSSIP_TYPE_CELL_RENDERER_EXPANDER))
+#define GOSSIP_IS_CELL_RENDERER_EXPANDER_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), GOSSIP_TYPE_CELL_RENDERER_EXPANDER))
+#define GOSSIP_CELL_RENDERER_EXPANDER_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), GOSSIP_TYPE_CELL_RENDERER_EXPANDER, GossipCellRendererExpanderClass))
+
+typedef struct _GossipCellRendererExpander GossipCellRendererExpander;
+typedef struct _GossipCellRendererExpanderClass GossipCellRendererExpanderClass;
+
+struct _GossipCellRendererExpander {
+  GtkCellRenderer parent;
+};
+
+struct _GossipCellRendererExpanderClass {
+  GtkCellRendererClass parent_class;
+
+  /* Padding for future expansion */
+  void (*_gtk_reserved1) (void);
+  void (*_gtk_reserved2) (void);
+  void (*_gtk_reserved3) (void);
+  void (*_gtk_reserved4) (void);
+};
+
+GType            gossip_cell_renderer_expander_get_type (void) G_GNUC_CONST;
+GtkCellRenderer *gossip_cell_renderer_expander_new      (void);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CELL_RENDERER_EXPANDER_H__ */
Index: widgets/rb-cell-renderer-pixbuf.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/widgets/rb-cell-renderer-pixbuf.c,v
retrieving revision 1.11
diff -p -u -r1.11 rb-cell-renderer-pixbuf.c
--- widgets/rb-cell-renderer-pixbuf.c	14 Jul 2006 12:32:00 -0000	1.11
+++ widgets/rb-cell-renderer-pixbuf.c	29 Nov 2006 17:28:59 -0000
@@ -23,10 +23,10 @@
  */
 
 #include <config.h>
-#include <libgnome/gnome-i18n.h>
+
 #include <stdlib.h>
-#include <gtk/gtktreeview.h>
-#include <gtk/gtkiconfactory.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
 
 #include "rb-cell-renderer-pixbuf.h"
 #include "rb-cut-and-paste-code.h"
