diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pristine/pitivi/ui/Makefile.am pitivi_pub/pitivi/ui/Makefile.am
--- pitivi_pristine/pitivi/ui/Makefile.am	2006-12-05 00:15:36.000000000 +0000
+++ pitivi_pub/pitivi/ui/Makefile.am	2006-12-05 00:18:15.000000000 +0000
@@ -12,6 +12,7 @@
 	gstwidget.py		\
 	mainwindow.py		\
 	projectsettings.py	\
+	signalgroup.py		\
 	sourcefactories.py	\
 	timeline.py		\
 	timelineobjects.py	\
diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pristine/pitivi/ui/signalgroup.py pitivi_pub/pitivi/ui/signalgroup.py
--- pitivi_pristine/pitivi/ui/signalgroup.py	1970-01-01 01:00:00.000000000 +0100
+++ pitivi_pub/pitivi/ui/signalgroup.py	2006-12-05 00:17:29.000000000 +0000
@@ -0,0 +1,76 @@
+# PiTiVi , Non-linear video editor
+#
+#       pitivi/ui/signalgroup.py
+#
+# Copyright (c) 2006, Richard Boulton <richard@tartarus.org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser 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.
+
+"""
+A group of signals, which can be disconnected easily.
+
+Used to make it easy to keep signals attached to the current project.
+"""
+
+class SignalGroup:
+    def __init__(self):
+        self.signal_handler_ids = {}
+
+    def connect(self, object, signal, id, callback, *args):
+        """Connect a signal.
+
+         _ `object` is the object which defines the signal.
+         _ `signal` is the name of the signal to connect to.
+         _ `id` is a unique (within this SignalGroup) identifer for the signal to
+           connect to.  If this is None, the value of `signal` will be used
+           instead.
+         _ `callback` is the callable to call on the signal.
+         _ `args` are any extra arguments to pass to the callback.
+
+        If there is already a connected signal in the group with the specified
+        unique identifier, this signal will first be disconnected.
+
+        """
+        if id is None:
+            id = signal
+
+        if id in self.signal_handler_ids:
+            old_object, handler_id = self.signal_handler_ids[id]
+            #print "Disconnecting %s" % handler_id
+            old_object.disconnect(handler_id)
+            del self.signal_handler_ids[id]
+
+        handler_id = object.connect(signal, callback, *args)
+        #print "Connected %s to signal %s " % (handler_id, signal)
+        self.signal_handler_ids[id] = (object, handler_id)
+
+    def disconnect(self, id):
+        """Disconnect the signal with the specified unique identifier.
+
+        If there is no such signal, this returns without having any effect.
+
+        """
+        if id in self.signal_handler_ids:
+            old_object, handler_id = self.signal_handler_ids[id]
+            old_object.disconnect(handler_id)
+
+    def disconnectAll(self):
+        """Disconnect all signals in the group.
+
+        """
+        for old_object, handler_id in self.signal_handler_ids.itervalues():
+            old_object.disconnect(handler_id)
+        self.signal_handler_ids = {}
diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pristine/pitivi/ui/sourcefactories.py pitivi_pub/pitivi/ui/sourcefactories.py
--- pitivi_pristine/pitivi/ui/sourcefactories.py	2006-12-05 00:15:36.000000000 +0000
+++ pitivi_pub/pitivi/ui/sourcefactories.py	2006-12-05 00:57:05.000000000 +0000
@@ -37,6 +37,7 @@
 import pitivi.dnd as dnd
 from pitivi.configure import get_pixmap_dir
 
+from signalgroup import SignalGroup
 from filelisterrordialog import FileListErrorDialog
 
 from gettext import gettext as _
@@ -182,12 +183,10 @@
         # Start up with tree view
         self.scrollwin.add(self.treeview)
 
-        # callbacks from discoverer
-        # TODO : we must remove and reset the callbacks when changing project
-        instance.PiTiVi.current.sources.connect("file_added", self._fileAddedCb)
-        instance.PiTiVi.current.sources.connect("file_removed", self._fileRemovedCb)
-        instance.PiTiVi.current.sources.connect("not_media_file", self._notMediaFileCb)
-
+        # Connect to project.  We must remove and reset the callbacks when
+        # changing project.
+        self.project_signals = SignalGroup()
+        self._connectToProject(instance.PiTiVi.current)
         instance.PiTiVi.connect("new-project", self._newProjectCb)
 
         # default pixbufs
@@ -214,6 +213,17 @@
         # Error dialog box
         self.errorDialogBox = None
 
+    def _connectToProject(self, project):
+        """Connect signal handlers to a project.
+
+        This first disconnects any handlers connected to an old project.
+        If project is None, this just disconnects any connected handlers.
+
+        """
+        self.project_signals.connect(project.sources, "file_added", None, self._fileAddedCb)
+        self.project_signals.connect(project.sources, "file_removed", None, self._fileRemovedCb)
+        self.project_signals.connect(project.sources, "not_media_file", None, self._notMediaFileCb)
+
     def showImportSourcesDialog(self, select_folders=False):
         if self._importDialog:
             return
@@ -422,9 +432,8 @@
                 # FIXME : update with new table structure (icon, infotext, objectfactory, uri
                 self.storemodel.append([thumbnail, name, factory, factory.name,
                                 "<b>%s</b>" % beautify_length(factory.length)])
-        
-        instance.PiTiVi.current.sources.connect("file_added", self._fileAddedCb)
-        instance.PiTiVi.current.sources.connect("file_removed", self._fileRemovedCb)
+
+        self._connectToProject(project)
 
 
     ## Drag and Drop
diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pristine/pitivi/ui/timelineobjects.py pitivi_pub/pitivi/ui/timelineobjects.py
--- pitivi_pristine/pitivi/ui/timelineobjects.py	2006-12-03 18:53:32.000000000 +0000
+++ pitivi_pub/pitivi/ui/timelineobjects.py	2006-12-05 00:57:08.000000000 +0000
@@ -33,6 +33,7 @@
 from pitivi.timeline import Timeline, TimelineComposition, TimelineFileSource, TimelineSource, TimelineTransition, MEDIA_TYPE_AUDIO, MEDIA_TYPE_VIDEO
 import pitivi.dnd as dnd
 from sourcefactories import beautify_length
+from signalgroup import SignalGroup
 
 # Default width / height ratio for simple elements
 DEFAULT_SIMPLE_SIZE_RATIO = 1.0 # default width / height ratio
@@ -58,17 +59,14 @@
 
         self.hadjustment = self.get_property("hadjustment")
 
-        # timeline and top level compositions
-        self.timeline = instance.PiTiVi.current.timeline
-        self.condensed = self.timeline.videocomp.condensed
-
-        # TODO : connect signals for when the timeline changes
-
         # widgets correspondance dictionnary
         self.widgets = {}
 
-        self.timeline.videocomp.connect("condensed-list-changed",
-                                        self._condensedListChangedCb)
+        # Connect to timeline.  We must remove and reset the callbacks when
+        # changing project.
+        self.project_signals = SignalGroup()
+        self._connectToTimeline(instance.PiTiVi.current.timeline)
+        instance.PiTiVi.connect("new-project", self._newProjectCb)
 
         # size
         self.width = int(DEFAULT_WIDTH)
@@ -95,6 +93,23 @@
 
         self.show_all()
 
+    def _connectToTimeline(self, timeline):
+        self.timeline = timeline
+        self.condensed = self.timeline.videocomp.condensed
+
+        self.project_signals.connect(self.timeline.videocomp,
+                                     "condensed-list-changed",
+                                     None, self._condensedListChangedCb)
+
+    def _newProjectCb(self, unused_pitivi, project):
+        assert(instance.PiTiVi.current == project)
+
+        for widget in self.widgets.itervalues():
+            self.remove(widget)
+        self.widgets = {}
+
+        self._connectToTimeline(instance.PiTiVi.current.timeline)
+
     def _condensedListChangedCb(self, unused_videocomp, clist):
         """ add/remove the widgets """
         gst.debug("condensed list changed in videocomp")
@@ -103,7 +118,7 @@
         # go through the condensed list
         for element in clist:
             if element in current:
-                # element stil exists
+                # element still exists
                 current.remove(element)
             else:
                 # new element
@@ -308,7 +323,6 @@
 
     def _sourceDeleteMeCb(self, widget, element):
         # remove this element from the timeline
-        print element
         self.timeline.videocomp.removeSource(element, collapse_neighbours=True)
 
 
diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pristine/pitivi/ui/viewer.py pitivi_pub/pitivi/ui/viewer.py
--- pitivi_pristine/pitivi/ui/viewer.py	2006-12-03 18:53:32.000000000 +0000
+++ pitivi_pub/pitivi/ui/viewer.py	2006-12-05 01:11:00.000000000 +0000
@@ -35,6 +35,7 @@
 import pitivi.dnd as dnd
 from pitivi.settings import ExportSettings
 from exportsettingswidget import ExportSettingsDialog
+from signalgroup import SignalGroup
 
 from gettext import gettext as _
 
@@ -62,21 +63,32 @@
         self.currentState = gst.STATE_PAUSED
         self._createUi()
 
-        # connect to the sourcelist for temp factories
-        # TODO remove/replace the signal when closing/opening projects
-        instance.PiTiVi.current.sources.connect("tmp_is_ready", self._tmpIsReadyCb)
-
+        # Connect to project.  We must remove and reset the callbacks when
+        # changing project.
+        self.project_signals = SignalGroup()
+        self._connectToProject(instance.PiTiVi.current)
         instance.PiTiVi.connect("new-project", self._newProjectCb)
+
         instance.PiTiVi.playground.connect("current-state", self._currentStateCb)
         instance.PiTiVi.playground.connect("position", self._playgroundPositionCb)
 
         # callback to know when to set the XID on our viewer widget
         instance.PiTiVi.playground.connect("element-message", self._playgroundElementMessageCb)
 
-        instance.PiTiVi.current.connect("settings-changed",
-                                        self._settingsChangedCb)
         self._addTimelineToPlayground()
 
+    def _connectToProject(self, project):
+        """Connect signal handlers to a project.
+ 
+        This first disconnects any handlers connected to an old project.
+        If project is None, this just disconnects any connected handlers.
+ 
+        """
+        self.project_signals.connect(project.sources, "tmp_is_ready",
+                                     None, self._tmpIsReadyCb)
+        self.project_signals.connect(project, "settings-changed",
+                                     None, self._settingsChangedCb)
+
     def _createUi(self):
         """ Creates the Viewer GUI """
         self.set_border_width(5)
@@ -345,10 +357,10 @@
         gst.info("%s" % factory)
         instance.PiTiVi.playground.playTemporaryFilesourcefactory(factory)
 
-    def _newProjectCb(self, unused_pitivi, unused_project):
+    def _newProjectCb(self, unused_pitivi, project):
         """ the current project has changed """
-        instance.PiTiVi.current.sources.connect("tmp_is_ready", self._tmpIsReadyCb)
-        instance.PiTiVi.current.connect("settings-changed", self._settingsChangedCb)
+        assert(instance.PiTiVi.current == project)
+        self._connectToProject(project)
         
     def _addTimelineToPlayground(self):
         instance.PiTiVi.playground.addPipeline(instance.PiTiVi.current.getBin())
