diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pub/pitivi/pitivi.py pitivi_rjb/pitivi/pitivi.py
--- pitivi_pub/pitivi/pitivi.py	2006-12-03 18:50:42.000000000 +0000
+++ pitivi_rjb/pitivi/pitivi.py	2006-12-04 23:08:33.000000000 +0000
@@ -3,6 +3,7 @@
 #       pitivi/pitivi.py
 #
 # Copyright (c) 2005, Edward Hervey <bilboed@bilboed.com>
+# 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
@@ -40,35 +41,75 @@
 from gettext import gettext as _
 
 class Pitivi(gobject.GObject):
-    """
-    Pitivi's main class
+    """Pitivi's main class.
+
+    This class is mainly responsible for managing Pitivi projects, controling
+    construction and destruction of the UI, and running the mainloop.
 
-    Signals:
-      new-project : A new project has been loaded, the Project object is given
-      closing-project : Pitivi wishes to close the project, callbacks return False
-                if they don't want the project to be closed, True otherwise
-      not-project : The given uri is not a project file
+    Signals for other classes to listen for:
+     _ new-project-loaded : A new project has been loaded, the Project object
+       is the extra argument.
+     _ close-project-requested : Pitivi wishes to close the project, callbacks
+       should return False if they want the project not to be closed
+       immediately, True otherwise.
+     _ save-project-requested : Pitivi wishes to save a project, but doesn't
+       have a filename to save as.
+     _ confirm-overwrite-file-changed : The project has been requested to save
+       itself, but the project file has changed since the last time it was
+       saved (or, since it was loaded).  The path is supplied as an extra
+       argument to the signal.  Callbacks should return True if the existing
+       path should be overwritten.
+     _ open-project-failed : An attempt to load a project failed.  The uri of
+       the project is the first extra argument, and a string giving the reason
+       for the failure is the second argument.
+
+    Signals used internally:
+     _ request-new-project: Called to request opening of a new project
+       (replacing the current one).  No arguments.
+     _ request-load-project: Called to request opening of an existing project
+       (replacing the current one).  1 argument: the uri to open.
+     _ request-shutdown: Called to request shutdown of PiViTi.  No arguments.
 
     """
     __gsignals__ = {
-        "new-project" : ( gobject.SIGNAL_RUN_LAST,
-                          gobject.TYPE_NONE,
-                          (gobject.TYPE_PYOBJECT, )),
-        "closing-project" : ( gobject.SIGNAL_RUN_LAST,
-                              gobject.TYPE_BOOLEAN,
-                              (gobject.TYPE_PYOBJECT, )),
-        "not-project" : ( gobject.SIGNAL_RUN_LAST,
-                          gobject.TYPE_NONE,
-                          (gobject.TYPE_STRING, ))
+        "new-project-loaded" : ( gobject.SIGNAL_RUN_LAST,
+                                 gobject.TYPE_NONE,
+                                 (gobject.TYPE_PYOBJECT, )),
+        "close-project-requested" : ( gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_BOOLEAN,
+                                      (gobject.TYPE_PYOBJECT, )),
+        "save-project-requested" : ( gobject.SIGNAL_RUN_LAST,
+                                     gobject.TYPE_NONE,
+                                     ()),
+        "confirm-overwrite-file-changed" : ( gobject.SIGNAL_RUN_LAST,
+                                             gobject.TYPE_BOOLEAN,
+                                             (gobject.TYPE_STRING, )),
+        "open-project-failed" : ( gobject.SIGNAL_RUN_LAST,
+                                  gobject.TYPE_NONE,
+                                  (gobject.TYPE_STRING, gobject.TYPE_STRING, )),
+
+
+        "request-new-project" : ( gobject.SIGNAL_RUN_LAST,
+                                  gobject.TYPE_NONE,
+                                  ()),
+        "request-load-project" : ( gobject.SIGNAL_RUN_LAST,
+                                  gobject.TYPE_NONE,
+                                  ()),
+        "request-shutdown" : ( gobject.SIGNAL_RUN_LAST,
+                               gobject.TYPE_NONE,
+                               ()),
         }
 
     project = None
 
     def __init__(self, *args):
+        """Initialize pitivi, using the command line arguments.
+
         """
-        initialize pitivi with the command line arguments
-        """
-        gst.log("starting up pitivi...")
+        self.pendingAction = None
+        self.pendingActionArgs = None
+
+        gst.log("Starting up pitivi...")
         gobject.GObject.__init__(self)
 
         # store ourself in the instance global
@@ -80,55 +121,185 @@
 
         self.playground = PlayGround()
         self.current = Project(_("New Project"))
+        self._connectToProject()
         self.effects = Magician()
         
         # we're starting a GUI for the time being
         self.gui = mainwindow.PitiviMainWindow()
         self.gui.show()
 
-    def loadProject(self, uri=None, filepath=None):
-        """ Load the given file through it's uri or filepath """
-        gst.info("uri:%s, filepath:%s" % (uri, filepath))
-        if not uri and not filepath:
-            self.emit("not-project", "")
+        self.connect_object("request-new-project", Pitivi.newProject, self)
+        self.connect_object("request-load-project", Pitivi.loadProjectByUri, self)
+        self.connect_object("request-shutdown", Pitivi.requestShutdown, self)
+
+    def _connectToProject(self):
+        """Create a new project, and connect to its signals.
+
+        """
+        self.current.connect("save-project-requested",
+                             self._saveProjectRequestedCb)
+        self.current.connect("confirm-overwrite-file-changed",
+                             self._confirmOverwriteFileChangedCb)
+
+    def _saveProjectRequestedCb(self, unused_project):
+        """Handle a save-project-requested signal from a project.
+
+        This simply passes the signal on to anyone listening for the equivalent
+        signal from this object.  This is useful because it prevents listeners
+        for the signal having to register with each new project.
+
+        """
+        return self.emit("save-project-requested")
+
+    def _confirmOverwriteFileChangedCb(self, project, uri):
+        """Handle a confirm-overwrite-file-changed signal from a project.
+
+        This simply passes the signal on to anyone listening for the equivalent
+        signal from this object.  This is useful because it prevents listeners
+        for the signal having to register with each new project.
+
+        """
+        return self.emit(signal, uri)
+
+    def newProject(self):
+        """Start up a new blank project.
+        
+        """
+        # If there's a running project we must close it first.
+        if not self._closeCurrentProject("request-new-project"):
             return
-        if filepath:
-            uri = "file://" + filepath
-        # is the given filepath a valid pitivi project
-        if not file_is_project(uri):
-            self.emit("not-project", uri)
+
+        self.playground.pause()
+        self.current = Project(_("New Project"))
+        self._connectToProject()
+        self.emit("new-project-loaded", self.current)
+
+    def loadProjectByFilepath(self, filepath):
+        """Load a project from a given filepath, replacing the current one.
+
+        `filepath` is the filepath to load the project from.
+        
+        """
+        self.loadProjectByUri("file://" + filepath)
+
+    def loadProjectByUri(self, uri):
+        """Load a project from a given URI, replacing the current one.
+
+        `uri` is the uri to load the project from.
+
+        """
+        # Close any current project
+        if not self._closeCurrentProject("request-load-project", uri):
+            return
+
+        gst.info("Loading project from %s" % uri)
+
+        # Check if the given filepath is a valid pitivi project
+        new_project = Project(uri=uri)
+        failure_reason = new_project.load()
+        if failure_reason is not None:
+            self.emit("open-project-failed", uri, failure_reason)
             return
-        # if current project, try to close it
-        if self._closeRunningProject():
-            self.current = Project(uri)
-            self.emit("new-project", self.current)
-
-    def _closeRunningProject(self):
-        """ close the current project """
-        gst.info("closing running project")
+
+        self.current = new_project
+        self._connectToProject()
+        self.emit("new-project-loaded", self.current)
+
+    def cancelPendingAction(self):
+        """Cancel the action which was pending.
+        
+        This is used to cancel the action which was in progress when request
+        to close the current project was denied (probably because the user
+        was presented with a question about whether to save the project or
+        not).
+
+        """
+        self.pendingAction = None
+        self.pendingActionArgs = None
+
+    def resumePendingAction(self):
+        """Resume the action which was pending.
+        
+        This is used to resume the action which was in progress when request
+        to close the current project was denied (probably because the user
+        was presented with a question about whether to save the project or
+        not).
+
+        If no action was pending (or the action has already been cancelled with
+        cancelPendingAction), this does nothing.
+
+        """
+        if self.pendingAction:
+            pendingAction = self.pendingAction
+            pendingActionArgs = self.pendingActionArgs
+            self.pendingAction = None
+            self.pendingActionArgs = None
+            self.emit(pendingAction, *pendingActionArgs)
+
+    def closeCurrentProjectForce(self):
+        """Force the current project to close.
+
+        This will close the current project immediately, even if it has unsaved
+        modifications.
+
+        """
+        gst.info("Closing project")
         if self.current:
-            if not self.emit("closing-project", self.current):
-                return False
             self.playground.pause()
             self.current = None
+
+    def _closeCurrentProject(self, pendingAction, *args):
+        """Close the current project.
+
+        If the project is successfully closed (or not open), returns True.
+        Otherwise, returns False.
+
+        pendingAction is the name of a signal to call, with the arguments
+        supplied in *args, if this method returns False, but the condition
+        preventing immediate closing is later resolved.
+        
+        """
+        if self.current is None:
+            return True
+
+        gst.info("Sending close-project-requested signal, with pendingAction=%s, args=%s" %
+                 (pendingAction, repr(args)))
+        self.pendingAction = pendingAction
+        self.pendingActionArgs = args
+        if not self.emit("close-project-requested", self.current):
+            return False
+
+        self.pendingAction = None
+        self.pendingActionArgs = None
+        self.closeCurrentProjectForce()
         return True
         
-    def newBlankProject(self):
-        """ start up a new blank project """
-        # if there's a running project we must close it
-        if self._closeRunningProject():
-            self.playground.pause()
-            self.current = Project(_("New Project"))
-            self.emit("new-project", self.current)
+    def requestShutdown(self):
+        """Request that PiTiVi shuts itself down.
+
+        This request may result in PiTiVi shutting down immediately, or may
+        result in a "closing-project" signal being sent (if there are unsaved
+        changes).
+
+        """
+        gst.info("shut down requested")
+        if not self._closeCurrentProject("request-shutdown"):
+            return True
 
-    def shutdown(self):
-        """ close PiTiVi """
-        gst.debug("shutting down")
-        if not self._closeRunningProject():
-            return
         self.playground.shutdown()
         gst.debug("Exiting main loop")
         gtk.main_quit()
+        return False
+
+    def forceShutdown(self):
+        """Force PiTiVi to itself down immediately.
+
+        """
+        gst.info("shutting down")
+        self.closeCurrentProjectForce()
+        self.playground.shutdown()
+        gst.info("Exiting main loop")
+        gtk.main_quit()
 
         
 def main(argv):
diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pub/pitivi/project.py pitivi_rjb/pitivi/project.py
--- pitivi_pub/pitivi/project.py	2006-12-03 18:50:42.000000000 +0000
+++ pitivi_rjb/pitivi/project.py	2006-12-03 17:05:34.000000000 +0000
@@ -25,6 +25,9 @@
 """
 
 import os.path
+import tempfile
+import time
+import math
 import gobject
 import gst
 from timeline import Timeline
@@ -36,20 +39,38 @@
 from gettext import gettext as _
 
 class Project(gobject.GObject):
-    """ The base class for PiTiVi projects """
+    """The base class for PiTiVi projects
 
+    Signals:
+     _ save-project-requested : The project has been requested to
+       save itself, but needs a URI to save to, or other settings.
+     _ confirm-overwrite-file-changed : The project has been requested to save
+       itself, but the project file has changed since the last time it was
+       saved (or, since it was loaded).  The path is supplied as an extra
+       argument to the signal.  Callbacks should return True if the existing
+       path should be overwritten.
+
+    """
     __gsignals__ = {
         "settings-changed" : ( gobject.SIGNAL_RUN_LAST,
                                gobject.TYPE_NONE,
-                               (  ))
+                               ()),
+        "save-project-requested" : ( gobject.SIGNAL_RUN_LAST,
+                                     gobject.TYPE_NONE,
+                                     ()),
+        "confirm-overwrite-file-changed" : ( gobject.SIGNAL_RUN_LAST,
+                                             gobject.TYPE_BOOLEAN,
+                                             (gobject.TYPE_STRING, )),
         }
 
     def __init__(self, name="", uri=None):
+        """Initialise a new project.
+
+        Even if a URI is specified, this does not load the project.
+        Loading is performed only when the load method is explicitly called.
+
         """
-        name : the name of the project
-        uri : the uri of the project
-        """
-        gst.log("name:%s, uri:%s" % (name, uri))
+        gst.log("Making new project, name:%s, uri:%s" % (name, uri))
         gobject.GObject.__init__(self)
         self.name = name
         self.settings = None
@@ -59,15 +80,153 @@
         self.timeline = None
         self.timelinebin = None
         self.settingssigid = 0
-        self._load()
-
-    def _load(self):
-        """ loads the project from a file """
-        if self.timeline:
-            return
+        self.modified = False
+        self.last_mtime = None # mtime of saved project
+        self.first_modification_time = None
         self.timeline = Timeline(self)
-        if self.uri:
-            raise NotImplementedError
+        self.clearModificationState()
+
+    def clearModificationState(self):
+        """Clear the modified state.
+
+        This is used, for example, after a save to indicate that there are no
+        longer unsaved modifications.
+
+        """
+        self.modified = False
+        self.first_modification_time = None
+
+    def registerModification(self):
+        """Register that a modification has occurred.
+
+        TODO - expand this into a framework which also handles undo/redo.
+
+        """
+        if not self.modified:
+            self.modified = True
+            self.first_modification_time = time.time()
+
+    def secondsSinceFirstModification(self):
+        """Return the number of seconds since the first modification occurred.
+
+        This returns an integer number of seconds, rounded up, or 0 if no
+        modifications have occurred.
+
+        """
+        if self.first_modification_time is None:
+            return 0
+        return int(math.ceil(time.time() - self.first_modification_time))
+
+    def load(self):
+        """Loads the project.
+
+        The project's URI must be set before this is called.
+
+        """
+        assert self.uri is not None
+        assert self.uri.startswith("file:/")
+        filename = self.uri[5:]
+        self.last_mtime = os.path.getmtime(filename)
+
+        # TODO
+        raise NotImplementedError
+
+        self.clearModificationState()
+
+    def _writeToFd(self, fd):
+        """Write the project to the given file descriptor.
+
+        """
+        assert self.timeline is not None
+        self.timeline.writeToFd(fd)
+
+    def _save(self, uri):
+        """Internal save function.
+        
+        """
+        assert uri is not None
+        assert uri.startswith("file:/")
+        newfilename = uri[5:]
+
+        currentfilename = None
+        if (self.uri is not None and
+            self.uri.startswith("file:/")):
+            currentfilename = self.uri[5:]
+
+        # If we're saving to the same URI, check if it's been changed
+        # externally since the last save, and ask the UI what to do if so.
+        if (currentfilename is not None and
+            os.path.exists(currentfilename) and
+            os.path.exists(newfilename) and
+            os.path.samefile(currentfilename, newfilename) and
+            self.last_mtime != os.path.getmtime(newfilename)):
+            if not self.emit('confirm-overwrite-file-changed', uri):
+                return
+
+        targetdir = os.path.dirname(newfilename)
+        targetname = os.path.basename(newfilename)
+        assert targetname != ''
+        (tmpfd, tmpfilename) = tempfile.mkstemp('.tmp', targetname, targetdir)
+        try:
+            fd = file(tmpfilename, "wb")
+            try:
+                self._writeToFd(fd)
+            finally:
+                fd.close()
+            try:
+                os.remove(newfilename)
+            except OSError:
+                pass
+            os.rename(tmpfilename, newfilename)
+        except:
+            try:
+                os.remove(tmpfilename)
+            except OSError:
+                pass
+            raise
+
+        self.last_mtime = os.path.getmtime(newfilename)
+        self.clearModificationState()
+
+    def save(self):
+        """Save the project.
+
+        If the project has a URI and all necessary settings, this immediately
+        saves the project.
+
+        If the project does not have a URI or some of the necessary settings,
+        this emits the save-project-requested signal, and doesn't save the
+        project.
+
+        """
+        if self.uri is None:
+            self.emit('save-project-requested')
+        else:
+            self._save(self.uri)
+
+    def saveAs(self, uri):
+        """Saves the project to the given URI.
+        
+        This also sets the project URI to the given URI, so future calls to
+        save() will use this URI.
+        
+        """
+        assert uri is not None
+        assert uri.startswith("file:/")
+
+        if (self.uri is not None and
+            self.uri.startswith("file:/") and
+            os.path.samefile(self.uri[5:], uri[5:])):
+            self.save()
+        else:
+            self._save(uri)
+            self.uri = uri
+
+    def hasUnsavedModifications(self):
+        """Return True iff the project has been modified since saving.
+
+        """
+        return self.modified
 
     def getBin(self):
         """ returns the SmartTimelineBin of the project """
@@ -77,18 +236,6 @@
             self.timelinebin = SmartTimelineBin(self)
         return self.timelinebin
 
-    def _save(self, filename):
-        """ internal save function """
-        # TODO
-        pass
-
-    def save(self):
-        """ Saves the project to the project's current file """
-        self._save(self.uri)
-
-    def saveAs(self, filename):
-        """ Saves the project to the given file name """
-        self._save(filename)
 
     # setting methods
     def _settingsChangedCb(self, settings):
diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pub/pitivi/sourcelist.py pitivi_rjb/pitivi/sourcelist.py
--- pitivi_pub/pitivi/sourcelist.py	2006-12-03 18:50:42.000000000 +0000
+++ pitivi_rjb/pitivi/sourcelist.py	2006-12-03 17:05:34.000000000 +0000
@@ -38,6 +38,9 @@
                 A file was removed from the SourceList
     _ tmp-is-ready (FileSourceFactory) :
                 The temporary uri given to the SourceList is ready to use.
+    _ not_media_file (string : uri, string : reason) :
+                Location given by uri was found not to be a media file, for
+                given reason.
     """
 
     __gsignals__ = {
@@ -99,6 +102,7 @@
             return
         self.sources[uri] = None
         self.discoverer.addFile(uri)
+        self.project.registerModification()
 
     def addUris(self, uris):
         """ Add the list of uris to the list of sources, they will be discovered """
@@ -109,6 +113,7 @@
                 self.sources[uri] = None
                 rlist.append(uri)
         self.discoverer.addFiles(rlist)
+        self.project.registerModification()
 
     def addTmpUri(self, uri):
         """ Adds a temporary uri, will not be saved """
@@ -129,6 +134,7 @@
                 rmuri.append(uri)
         for uri in rmuri:
             del self[uri]
+        self.project.registerModification()
 
     def _finishedAnalyzingCb(self, unused_discoverer, factory):
         # callback from finishing analyzing factory
diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pub/pitivi/timeline.py pitivi_rjb/pitivi/timeline.py
--- pitivi_pub/pitivi/timeline.py	2006-12-03 18:50:42.000000000 +0000
+++ pitivi_rjb/pitivi/timeline.py	2006-12-03 17:05:34.000000000 +0000
@@ -95,6 +95,9 @@
         self.audiocomp.gnlobject.connect("pad-removed", self._removedAudioPadCb)
         self.videocomp.gnlobject.connect("pad-removed", self._removedVideoPadCb)
 
+    def writeToFd(self, fd):
+        fd.write(self.videocomp.serialise())
+
     def _newAudioPadCb(self, unused_audiocomp, pad):
         self.timeline.add_pad(gst.GhostPad("asrc", pad))
 
@@ -126,7 +129,6 @@
             s.audiodepth = a.audiodepth
         return s
 
-
 class TimelineObject(gobject.GObject):
     """
     Base class for all timeline objects
@@ -281,7 +283,7 @@
                 self.duration = long(duration)
         #if not start == -1 or not duration == -1:
         self.emit("start-duration-changed", self.start, self.duration)
-            
+
 
         
 class TimelineSource(TimelineObject):
@@ -395,6 +397,16 @@
     def getExportSettings(self):
         return self.factory.getExportSettings()
 
+    def serialise(self):
+        """Save the composition to a filedescriptor.
+
+        """
+        result = []
+        result.append("start=%d," % self.start)
+        result.append("duration=%d," % self.duration)
+        result.append("filename=%s," % self.factory.name)
+        return u''.join(result)
+
 
 class TimelineLiveSource(TimelineSource):
     """
@@ -931,6 +943,19 @@
                 return self._autoVideoSettings()
         
 
+    def serialise(self):
+        """Save the composition to a filedescriptor.
+
+        """
+        result = []
+        for (min_priority, max_priority, sourcelist) in self.sources:
+            result.append("(%d, %d, [" % (min_priority, max_priority))
+            for source in sourcelist:
+                result.append("%s" % source.serialise())
+            result.append("])")
+        return u''.join(result)
+            
+
 class TimelineEffect(TimelineObject):
     """
     Base class for effects (1->n input(s))
diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pub/pitivi/ui/confirmclosedialog.glade pitivi_rjb/pitivi/ui/confirmclosedialog.glade
--- pitivi_pub/pitivi/ui/confirmclosedialog.glade	1970-01-01 01:00:00.000000000 +0100
+++ pitivi_rjb/pitivi/ui/confirmclosedialog.glade	2006-12-03 17:05:34.000000000 +0000
@@ -0,0 +1,180 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkDialog" id="confirmclosedialog">
+  <property name="border_width">5</property>
+  <property name="visible">True</property>
+  <property name="title"></property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">True</property>
+  <property name="resizable">False</property>
+  <property name="destroy_with_parent">True</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">False</property>
+  <property name="skip_pager_hint">False</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="focus_on_map">True</property>
+  <property name="urgency_hint">False</property>
+  <property name="has_separator">False</property>
+  <signal name="close" handler="_closeCb" last_modification_time="Mon, 20 Mar 2006 15:44:55 GMT"/>
+  <signal name="response" handler="_responseCb" last_modification_time="Mon, 20 Mar 2006 15:45:06 GMT"/>
+
+  <child internal-child="vbox">
+    <widget class="GtkVBox" id="dialog-vbox1">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">14</property>
+
+      <child internal-child="action_area">
+	<widget class="GtkHButtonBox" id="dialog-action_area1">
+	  <property name="visible">True</property>
+	  <property name="layout_style">GTK_BUTTONBOX_DEFAULT_STYLE</property>
+
+	  <child>
+	    <widget class="GtkButton" id="closebutton">
+	      <property name="visible">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">Close _without Saving</property>
+	      <property name="use_underline">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">-7</property>
+	    </widget>
+	  </child>
+
+	  <child>
+	    <widget class="GtkButton" id="cancelbutton">
+	      <property name="visible">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-cancel</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">-6</property>
+	    </widget>
+	  </child>
+
+	  <child>
+	    <widget class="GtkButton" id="savebutton">
+	      <property name="visible">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-save</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">-5</property>
+	    </widget>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">False</property>
+	  <property name="pack_type">GTK_PACK_END</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkHBox" id="hbox1">
+	  <property name="border_width">5</property>
+	  <property name="visible">True</property>
+	  <property name="homogeneous">False</property>
+	  <property name="spacing">10</property>
+
+	  <child>
+	    <widget class="GtkImage" id="image1">
+	      <property name="visible">True</property>
+	      <property name="stock">gtk-dialog-warning</property>
+	      <property name="icon_size">6</property>
+	      <property name="xalign">0</property>
+	      <property name="yalign">0</property>
+	      <property name="xpad">0</property>
+	      <property name="ypad">0</property>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">False</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkVBox" id="vbox1">
+	      <property name="visible">True</property>
+	      <property name="homogeneous">False</property>
+	      <property name="spacing">8</property>
+
+	      <child>
+		<widget class="GtkLabel" id="headline">
+		  <property name="visible">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="label">&lt;span weight=&quot;bold&quot; size=&quot;larger&quot;&gt;Save changes to current project before closing it?&lt;/span&gt;</property>
+		  <property name="use_underline">False</property>
+		  <property name="use_markup">True</property>
+		  <property name="justify">GTK_JUSTIFY_LEFT</property>
+		  <property name="wrap">True</property>
+		  <property name="selectable">True</property>
+		  <property name="xalign">0</property>
+		  <property name="yalign">0.5</property>
+		  <property name="xpad">0</property>
+		  <property name="ypad">0</property>
+		  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		  <property name="width_chars">-1</property>
+		  <property name="single_line_mode">False</property>
+		  <property name="angle">0</property>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">False</property>
+		  <property name="fill">False</property>
+		</packing>
+	      </child>
+
+	      <child>
+		<widget class="GtkLabel" id="detail">
+		  <property name="visible">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="label" translatable="yes"></property>
+		  <property name="use_underline">False</property>
+		  <property name="use_markup">False</property>
+		  <property name="justify">GTK_JUSTIFY_LEFT</property>
+		  <property name="wrap">True</property>
+		  <property name="selectable">True</property>
+		  <property name="xalign">0</property>
+		  <property name="yalign">0.5</property>
+		  <property name="xpad">0</property>
+		  <property name="ypad">0</property>
+		  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		  <property name="width_chars">-1</property>
+		  <property name="single_line_mode">False</property>
+		  <property name="angle">0</property>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">False</property>
+		  <property name="fill">False</property>
+		</packing>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">True</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+</glade-interface>
diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pub/pitivi/ui/confirmclosedialog.py pitivi_rjb/pitivi/ui/confirmclosedialog.py
--- pitivi_pub/pitivi/ui/confirmclosedialog.py	1970-01-01 01:00:00.000000000 +0100
+++ pitivi_rjb/pitivi/ui/confirmclosedialog.py	2006-12-03 17:05:34.000000000 +0000
@@ -0,0 +1,88 @@
+# PiTiVi , Non-linear video editor
+#
+#       ui/confirmclosedialog.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.
+
+"""
+Dialog box listing files which had errors, and the reasons.
+"""
+
+import gobject
+import gtk
+import gst
+import pango
+from glade import GladeWindow
+
+from gettext import gettext as _
+from gettext import ngettext
+
+class ConfirmCloseDialog(GladeWindow):
+    """ Dialog box for showing errors from discovering files """
+    glade_file = "confirmclosedialog.glade"
+    __gsignals__ = {
+        'close': (gobject.SIGNAL_RUN_LAST,
+                  gobject.TYPE_NONE,
+                  ( )),
+        'response': (gobject.SIGNAL_RUN_LAST,
+                     gobject.TYPE_NONE,
+                     (gobject.TYPE_PYOBJECT, ))
+        }
+
+    def _get_detail_message(self, secs):
+        """Get a detailed warning message for the confirm close dialog box.
+
+        This is modeled after the equivalent warning box for gedit.
+
+        """
+        mins = secs // 60
+        hours = mins // 60
+
+        if hours > 0:
+            detail = ngettext("If you don't save, changes from the last hour will be permanently lost.",
+                              "If you don't save, changes from the last %(hours)s hours will be permanently lost.",
+                              hours)
+        elif mins > 0:
+            detail = ngettext("If you don't save, changes from the last minute will be permanently lost.",
+                              "If you don't save, changes from the last %(mins)s minutes will be permanently lost.",
+                              mins)
+        else:
+            detail = ngettext("If you don't save, changes from the last second will be permanently lost.",
+                              "If you don't save, changes from the last %(secs)s seconds will be permanently lost.",
+                              secs)
+        return detail % {"secs": secs, "mins": mins, "hours": hours}
+
+    def __init__(self, project):
+        GladeWindow.__init__(self)
+        self.project = project
+        self.window.set_modal(True)
+        self._update_display()
+        gobject.timeout_add(1000, self._update_display)
+
+    def _update_display(self):
+        detail = self._get_detail_message(self.project.secondsSinceFirstModification())
+        self.widgets["detail"].set_text(detail)
+        return True
+
+    ## Callbacks from glade
+
+    def _closeCb(self, unused_dialog):
+        self.emit('close')
+
+    def _responseCb(self, unused_dialog, response):
+        self.emit('response', response)
diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pub/pitivi/ui/mainwindow.py pitivi_rjb/pitivi/ui/mainwindow.py
--- pitivi_pub/pitivi/ui/mainwindow.py	2006-12-04 15:58:33.000000000 +0000
+++ pitivi_rjb/pitivi/ui/mainwindow.py	2006-12-04 15:59:10.000000000 +0000
@@ -40,11 +40,93 @@
 from projectsettings import ProjectSettingsDialog
 from pitivi.configure import pitivi_version, APPNAME
 
+from filelisterrordialog import FileListErrorDialog
+from confirmclosedialog import ConfirmCloseDialog
+
 D_G_INTERFACE = "/desktop/gnome/interface"
 
 for gconf_dir in (D_G_INTERFACE,):
     gconf.client_get_default ().add_dir (gconf_dir, gconf.CLIENT_PRELOAD_NONE)
 
+class ProjectSaveWindow:
+    def __init__(self):
+        self.dialog = None
+        self._lastFolder = None
+
+    def isOpen(self):
+        return self.dialog is not None
+
+    def open(self):
+        self.dialog = gtk.FileChooserDialog(_("Choose a file to save as"), None,
+                                            gtk.FILE_CHOOSER_ACTION_SAVE,
+                                            (gtk.STOCK_SAVE, gtk.RESPONSE_OK,
+                                             gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
+        self.dialog.set_do_overwrite_confirmation(True)
+
+        self.dialog.set_default_response(gtk.RESPONSE_OK)
+        self.dialog.set_modal(True)
+        if self._lastFolder is not None:
+            self.dialog.set_current_folder(self._lastFolder)
+        self.dialog.connect("key-press-event", self._keyPressEventCb)
+        self.dialog.connect('response', self._responseCb)
+        self.dialog.connect('close', self._closeCb)
+        self.dialog.show()
+
+    def _keyPressEventCb(self, unused_widget, event):
+        if gtk.gdk.keyval_name(event.keyval) in ['Escape']:
+            self.dialog.response(gtk.RESPONSE_CANCEL)
+
+    def _responseCb(self, dialogbox, response):
+        gst.debug("response:%r" % response)
+        if response == gtk.RESPONSE_OK:
+            self._lastFolder = dialogbox.get_current_folder()
+            instance.PiTiVi.current.saveAs(dialogbox.get_uri())
+        dialogbox.destroy()
+        self.dialog = None
+
+    def _closeCb(self, dialogbox):
+        gst.debug("closing")
+        self.dialog = None
+
+class ProjectLoadWindow:
+    def __init__(self):
+        self.dialog = None
+        self._lastFolder = None
+
+    def isOpen(self):
+        return self.dialog is not None
+
+    def open(self):
+        self.dialog = gtk.FileChooserDialog(_("Choose a project file to load"), None,
+                                            gtk.FILE_CHOOSER_ACTION_OPEN,
+                                            (gtk.STOCK_OPEN, gtk.RESPONSE_OK,
+                                             gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
+
+        self.dialog.set_default_response(gtk.RESPONSE_OK)
+        self.dialog.set_modal(True)
+        if self._lastFolder is not None:
+            self.dialog.set_current_folder(self._lastFolder)
+        self.dialog.connect("key-press-event", self._keyPressEventCb)
+        self.dialog.connect('response', self._responseCb)
+        self.dialog.connect('close', self._closeCb)
+        self.dialog.show()
+
+    def _keyPressEventCb(self, unused_widget, event):
+        if gtk.gdk.keyval_name(event.keyval) in ['Escape']:
+            self.dialog.response(gtk.RESPONSE_CANCEL)
+
+    def _responseCb(self, dialogbox, response):
+        gst.debug("response:%r" % response)
+        if response == gtk.RESPONSE_OK:
+            self._lastFolder = dialogbox.get_current_folder()
+            instance.PiTiVi.loadProjectByUri(dialogbox.get_uri())
+        dialogbox.destroy()
+        self.dialog = None
+
+    def _closeCb(self, dialogbox):
+        gst.debug("closing")
+        self.dialog = None
+
 class PitiviMainWindow(gtk.Window):
     """
     Pitivi's main window
@@ -60,17 +142,24 @@
 
         self.isFullScreen = False
         self.errorDialogBox = None
+        self.openProjectErrorDialogBox = None
+        self.confirmCloseDialogBox = None
+
+        self._saveAsDialogBox = None
+        self._loadFromDialogBox = None
         
-        instance.PiTiVi.connect("new-project", self._newProjectCb)
-        instance.PiTiVi.connect("closing-project", self._closingProjectCb)
-        instance.PiTiVi.connect("not-project", self._notProjectCb)
+        instance.PiTiVi.connect("new-project-loaded", self._newProjectCb)
+        instance.PiTiVi.connect("close-project-requested", self._closingProjectCb)
+        instance.PiTiVi.connect("save-project-requested", self._saveProjectRequestedCb)
+        instance.PiTiVi.connect("confirm-overwrite-file-changed", self._confirmOverwriteChangedCb)
+        instance.PiTiVi.connect("open-project-failed", self._notProjectCb)
         instance.PiTiVi.playground.connect("error", self._playGroundErrorCb)
         self.show_all()
 
     def _setActions(self):
         """ sets up the GtkActions """
         self.actions = [
-            ("NewProject", gtk.STOCK_NEW, None, None, _("Create a new project"), self._newProjectCb),
+            ("NewProject", gtk.STOCK_NEW, None, None, _("Create a new project"), self._createNewProjectCb),
             ("OpenProject", gtk.STOCK_OPEN, None, None, _("Open an existing project"), self._openProjectCb),
             ("SaveProject", gtk.STOCK_SAVE, None, None, _("Save the current project"), self._saveProjectCb),
             ("SaveProjectAs", gtk.STOCK_SAVE_AS, None, None, _("Save the current project"), self._saveProjectAsCb),
@@ -93,16 +182,6 @@
         self.actiongroup.add_actions(self.actions)
         self.actiongroup.add_toggle_actions(self.toggleactions)
         
-        # deactivating non-functional actions
-        # FIXME : reactivate them
-        for action in self.actiongroup.list_actions():
-            if action.get_name() in ["ProjectSettings", "Quit", "File", "Help",
-                                     "About", "View", "FullScreen", "ImportSources",
-                                     "ImportSourcesFolder", "AdvancedView"]:
-                action.set_sensitive(True)
-            else:
-                action.set_sensitive(False)
-                
         self.uimanager = gtk.UIManager()
         self.add_accel_group(self.uimanager.get_accel_group())
         self.uimanager.insert_action_group(self.actiongroup, 0)
@@ -116,6 +195,7 @@
         self.set_geometry_hints(min_width=800, min_height=600)
 
         self.connect("destroy", self._destroyCb)
+        self.connect("delete-event", self._deleteCb)
 
         vbox = gtk.VBox(False, 5)
         self.add(vbox)
@@ -161,14 +241,10 @@
             self.viewer.window.unfullscreen()
             self.isFullScreen = False
 
-    ## PlayGround callback
-
-    def _errorMessageResponseCb(self, dialogbox, unused_response):
-        dialogbox.hide()
-        dialogbox.destroy()
-        self.errorDialogBox = None
-
     def _playGroundErrorCb(self, unused_playground, error, detail):
+        """Display an error from the playground.
+
+        """
         if self.errorDialogBox:
             return
         self.errorDialogBox = gtk.MessageDialog(None, gtk.DIALOG_MODAL,
@@ -176,17 +252,25 @@
                                                 gtk.BUTTONS_OK,
                                                 None)
         self.errorDialogBox.set_markup("<b>%s</b>" % error)
-        self.errorDialogBox.connect("response", self._errorMessageResponseCb)
         if detail:
             self.errorDialogBox.format_secondary_text(detail)
+
+        def responseCb(dialogbox, unused_response, mainwindow):
+            dialogbox.hide()
+            dialogbox.destroy()
+            mainwindow.errorDialogBox = None
+        self.errorDialogBox.connect("response", responseCb, self)
+
         self.errorDialogBox.show()
 
 
     ## UI Callbacks
 
     def _destroyCb(self, unused_widget, data=None):
-        instance.PiTiVi.shutdown()
+        instance.PiTiVi.forceShutdown()
 
+    def _deleteCb(self, unused_widget, data=None):
+        return instance.PiTiVi.requestShutdown()
 
     def _keyPressEventCb(self, unused_widget, event):
         if gtk.gdk.keyval_name(event.keyval) in ['f', 'F', 'F11']:
@@ -194,24 +278,30 @@
 
     ## Toolbar/Menu actions callback
         
-    def _newProjectCb(self, unused_action):
-        instance.PiTiVi.new_blank_project()
+    def _createNewProjectCb(self, unused_action):
+        instance.PiTiVi.newProject()
 
     def _openProjectCb(self, unused_action):
-        raise NotImplementedError
+        if self._loadFromDialogBox is None:
+           self._loadFromDialogBox = ProjectLoadWindow()
+        if not self._loadFromDialogBox.isOpen():
+            self._loadFromDialogBox.open()
 
     def _saveProjectCb(self, unused_action):
-        raise NotImplementedError
+        instance.PiTiVi.current.save()
 
     def _saveProjectAsCb(self, unused_action):
-        raise NotImplementedError
+        if self._saveAsDialogBox is None:
+           self._saveAsDialogBox = ProjectSaveWindow()
+        if not self._saveAsDialogBox.isOpen():
+            self._saveAsDialogBox.open()
 
     def _projectSettingsCb(self, unused_action):
         l = ProjectSettingsDialog(self, instance.PiTiVi.current)
         l.show()
 
     def _quitCb(self, unused_action):
-        instance.PiTiVi.shutdown()
+        instance.PiTiVi.requestShutdown()
 
     def _fullScreenCb(self, unused_action):
         self.toggleFullScreen()
@@ -222,9 +312,6 @@
         else:
             self.timeline.showSimpleView()
 
-    def _aboutResponseCb(self, dialog, unused_response):
-        dialog.destroy()
-
     def _aboutCb(self, unused_action):
 	abt = gtk.AboutDialog()
 	abt.set_name(APPNAME)
@@ -232,11 +319,14 @@
 	abt.set_website("http://www.pitivi.org/")
 	authors = ["Edward Hervey <edward@fluendo.com>","",_("Contributors:"),
                    "Christophe Sauthier <christophe.sauthier@gmail.com> (i18n)",
-                   "Laszlo Pandy <laszlok2@gmail.com> (UI)", ]
+                   "Laszlo Pandy <laszlok2@gmail.com> (UI)",
+                   "Richard Boulton <richard@tartarus.org>",]
 	abt.set_authors(authors)
 	abt.set_license(_("GNU Lesser General Public License\nSee http://www.gnu.org/copyleft/lesser.html for more details"))
         abt.set_icon_from_file(configure.get_global_pixmap_dir() + "/pitivi.png")
-        abt.connect("response", self._aboutResponseCb)
+        def responseCb(dialog, unused_response):
+            dialog.destroy()
+        abt.connect("response", responseCb)
 	abt.show()
 
     def _importSourcesCb(self, unused_action):
@@ -248,16 +338,95 @@
     ## PiTiVi main object callbacks
 
     def _newProjectCb(self, pitivi, project):
-        raise NotImplementedError
+        # TODO - handle anything that needs setting for a new project (eg,
+        # window title).
+        pass
 
     def _closingProjectCb(self, pitivi, project):
-        # Return True if we accept the project being close
-        # if we want to save it before it being closed, we must
-        #   do so
+        """Called when a request to close the project has been made.
+
+        Returns True if we accept the project being closed, and False if
+        we don't (yet).
+
+        """
+        if (project.hasUnsavedModifications() and
+            project.secondsSinceFirstModification() > 30):
+            # Gnome HIG says "Present a confirmation alert when the user's
+            # command may ... take more than 30 seconds of user effort to
+            # recover from if it was selected in error."
+            if not self.confirmCloseDialogBox:
+                self.confirmCloseDialogBox = ConfirmCloseDialog(project)
+                def closeCb(dialog, mainwindow, pitivi):
+                    dialog.destroy()
+                    mainwindow.confirmCloseDialogBox = None
+                    pitivi.cancelPendingAction()
+                self.confirmCloseDialogBox.connect('close', closeCb, self, pitivi)
+
+                def responseCb(dialog, response, mainwindow, pitivi):
+                    dialog.destroy()
+                    mainwindow.confirmCloseDialogBox = None
+                    if response == gtk.RESPONSE_OK:
+                        # Save the project
+                        pitivi.current.save()
+                        pitivi.resumePendingAction()
+                    elif response == gtk.RESPONSE_CLOSE:
+                        # Close the project without saving.
+                        pitivi.closeCurrentProjectForce()
+                        pitivi.resumePendingAction()
+                    elif response == gtk.RESPONSE_CANCEL:
+                        # Don't close the project
+                        pitivi.cancelPendingAction()
+                    else:
+                        # Unknown - probably because the dialog was closed.
+                        # Treat as a cancel.
+                        pitivi.cancelPendingAction()
+                self.confirmCloseDialogBox.connect('response', responseCb, self, pitivi)
+
+                self.confirmCloseDialogBox.show()
+            return False
 
-        # For the time being we always accept it being closed
         return True
 
-    def _notProjectCb(self, pitivi, uri):
-        raise NotImplementedError
-    
+    def _saveProjectRequestedCb(self, pitivi):
+        """Called when a save was requested, but a filename is needed.
+
+        """
+        if self._saveAsDialogBox is None:
+           self._saveAsDialogBox = ProjectSaveWindow()
+        if not self._saveAsDialogBox.isOpen():
+            self._saveAsDialogBox.open()
+
+    def _confirmOverwriteChangedCb(self, pitivi, project):
+	"""Called when saving a project, but the project file has changed since
+        we last saved or loaded.
+
+        Returns True if we accept the file being overwritten, and False if
+        we don't (yet).
+
+        """
+        print "Confirm overwrite callback: %s" % reason
+        return False
+
+    def _notProjectCb(self, pitivi, uri, reason):
+        """Called when opening a project file failed.
+
+        """
+        # popup a dialog box and fill up with problems
+        if not self.openProjectErrorDialogBox:
+            # construct and display it
+            self.openProjectErrorDialogBox = FileListErrorDialog(_("Error with project files"),
+                                                                 _("The following project files couldn't be opened."))
+
+            def closeCb(dialog, mainwindow):
+                dialog.destroy()
+                mainwindow.openProjectErrorDialogBox = None
+            self.openProjectErrorDialogBox.connect('close', closeCb, self)
+
+            def responseCb(dialog, unused_response, mainwindow):
+                dialog.destroy()
+                mainwindow.openProjectErrorDialogBox = None
+            self.openProjectErrorDialogBox.connect('response', responseCb, self)
+
+            self.openProjectErrorDialogBox.show()
+        self.openProjectErrorDialogBox.addFailedFile(uri, reason)
+
diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pub/pitivi/ui/Makefile.am pitivi_rjb/pitivi/ui/Makefile.am
--- pitivi_pub/pitivi/ui/Makefile.am	2006-12-05 01:23:13.000000000 +0000
+++ pitivi_rjb/pitivi/ui/Makefile.am	2006-12-05 01:23:48.000000000 +0000
@@ -6,6 +6,7 @@
 	complexsource.py	\
 	complextimeline.py	\
 	complexinterface.py	\
+	confirmclosedialog.py	\
 	exportsettingswidget.py	\
 	filelisterrordialog.py	\
 	glade.py		\
@@ -25,10 +26,12 @@
 
 ui_DATA =				\
 	actions.xml			\
+	confirmclosedialog.glade	\
 	elementsettingsdialog.glade	\
 	encodingdialog.glade		\
 	exportsettingswidget.glade	\
 	filelisterrordialog.glade	\
+	projectopenerrordialog.glade	\
 	projectsettings.glade
 
 clean-local:
diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pub/pitivi/ui/projectopenerrordialog.glade pitivi_rjb/pitivi/ui/projectopenerrordialog.glade
--- pitivi_pub/pitivi/ui/projectopenerrordialog.glade	1970-01-01 01:00:00.000000000 +0100
+++ pitivi_rjb/pitivi/ui/projectopenerrordialog.glade	2006-12-03 17:05:34.000000000 +0000
@@ -0,0 +1,147 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkDialog" id="dialog2">
+  <property name="visible">True</property>
+  <property name="title" translatable="yes">Error with project file</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">False</property>
+  <property name="resizable">True</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">False</property>
+  <property name="skip_pager_hint">False</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="focus_on_map">True</property>
+  <property name="urgency_hint">False</property>
+  <property name="has_separator">True</property>
+  <signal name="close" handler="_closeCb" last_modification_time="Mon, 20 Mar 2006 15:44:55 GMT"/>
+  <signal name="response" handler="_responseCb" last_modification_time="Mon, 20 Mar 2006 15:45:06 GMT"/>
+
+  <child internal-child="vbox">
+    <widget class="GtkVBox" id="dialog-vbox1">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">0</property>
+
+      <child internal-child="action_area">
+	<widget class="GtkHButtonBox" id="dialog-action_area1">
+	  <property name="visible">True</property>
+	  <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+	  <child>
+	    <widget class="GtkButton" id="okbutton1">
+	      <property name="visible">True</property>
+	      <property name="can_default">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-ok</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">-5</property>
+	    </widget>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">True</property>
+	  <property name="pack_type">GTK_PACK_END</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkHBox" id="hbox1">
+	  <property name="border_width">10</property>
+	  <property name="visible">True</property>
+	  <property name="homogeneous">False</property>
+	  <property name="spacing">5</property>
+
+	  <child>
+	    <widget class="GtkImage" id="image1">
+	      <property name="visible">True</property>
+	      <property name="stock">gtk-dialog-warning</property>
+	      <property name="icon_size">6</property>
+	      <property name="xalign">0.5</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xpad">0</property>
+	      <property name="ypad">0</property>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">False</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkLabel" id="label1">
+	      <property name="visible">True</property>
+	      <property name="label" translatable="yes">The following project files couldn't be opened.</property>
+	      <property name="use_underline">False</property>
+	      <property name="use_markup">False</property>
+	      <property name="justify">GTK_JUSTIFY_LEFT</property>
+	      <property name="wrap">False</property>
+	      <property name="selectable">False</property>
+	      <property name="xalign">1</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xpad">0</property>
+	      <property name="ypad">0</property>
+	      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+	      <property name="width_chars">-1</property>
+	      <property name="single_line_mode">False</property>
+	      <property name="angle">0</property>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">True</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">5</property>
+	  <property name="expand">False</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkScrolledWindow" id="scrolledwindow1">
+	  <property name="border_width">5</property>
+	  <property name="visible">True</property>
+	  <property name="can_focus">True</property>
+	  <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+	  <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+	  <property name="shadow_type">GTK_SHADOW_IN</property>
+	  <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+	  <child>
+	    <widget class="GtkTreeView" id="treeview">
+	      <property name="visible">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="headers_visible">True</property>
+	      <property name="rules_hint">True</property>
+	      <property name="reorderable">False</property>
+	      <property name="enable_search">False</property>
+	      <property name="fixed_height_mode">False</property>
+	      <property name="hover_selection">False</property>
+	      <property name="hover_expand">False</property>
+	    </widget>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">True</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+</glade-interface>
diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pub/pitivi/ui/sourcefactories.py pitivi_rjb/pitivi/ui/sourcefactories.py
--- pitivi_pub/pitivi/ui/sourcefactories.py	2006-12-05 01:19:38.000000000 +0000
+++ pitivi_rjb/pitivi/ui/sourcefactories.py	2006-12-05 01:20:16.000000000 +0000
@@ -187,7 +187,7 @@
         # changing project.
         self.project_signals = SignalGroup()
         self._connectToProject(instance.PiTiVi.current)
-        instance.PiTiVi.connect("new-project", self._newProjectCb)
+        instance.PiTiVi.connect("new-project-loaded", self._newProjectCb)
 
         # default pixbufs
         pixdir = get_pixmap_dir()
@@ -319,9 +319,18 @@
                 _("The following files weren't discovered properly.")
             )
 
-            self.errorDialogBox.connect('close', self._errorDialogBoxCloseCb)
-            self.errorDialogBox.connect('response', self._errorDialogBoxResponseCb)
+            def closeCb(dialog, sourcelist):
+                sourcelist.errorDialogBox.destroy()
+                sourcelist.errorDialogBox = None
+            self.errorDialogBox.connect('close', closeCb, self)
+
+            def responseCb(dialog, unused_response, sourcelist):
+                sourcelist.errorDialogBox.destroy()
+                sourcelist.errorDialogBox = None
+            self.errorDialogBox.connect('response', responseCb, self)
+
             self.errorDialogBox.hide()
+
         if not self.errorDialogBox.isVisible() and not self.errorDialogButton.parent:
             # show the button that will open the dialog
             self.bothbox.pack_start(self.errorDialogButton, expand=False)
@@ -329,17 +338,6 @@
         self.errorDialogBox.addFailedFile(uri, reason)
 
 
-    ## Error Dialog Box callbacks
-
-    def _errorDialogBoxCloseCb(self, unused_dialog):
-        self.errorDialogBox.destroy()
-        self.errorDialogBox = None
-
-    def _errorDialogBoxResponseCb(self, unused_dialog, unused_response):
-        self.errorDialogBox.destroy()
-        self.errorDialogBox = None
-
-
     ## Import Sources Dialog Box callbacks
         
     def _dialogBoxResponseCb(self, dialogbox, response, select_folders):
diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pub/pitivi/ui/timelineobjects.py pitivi_rjb/pitivi/ui/timelineobjects.py
--- pitivi_pub/pitivi/ui/timelineobjects.py	2006-12-05 00:57:08.000000000 +0000
+++ pitivi_rjb/pitivi/ui/timelineobjects.py	2006-12-05 00:46:48.000000000 +0000
@@ -66,7 +66,7 @@
         # changing project.
         self.project_signals = SignalGroup()
         self._connectToTimeline(instance.PiTiVi.current.timeline)
-        instance.PiTiVi.connect("new-project", self._newProjectCb)
+        instance.PiTiVi.connect("new-project-loaded", self._newProjectCb)
 
         # size
         self.width = int(DEFAULT_WIDTH)
diff -ru --exclude=configure --exclude=config.status.lineno --exclude=config.log --exclude=autom4te.cache --exclude=CVS --exclude='*.pyc' -N pitivi_pub/pitivi/ui/viewer.py pitivi_rjb/pitivi/ui/viewer.py
--- pitivi_pub/pitivi/ui/viewer.py	2006-12-05 01:11:00.000000000 +0000
+++ pitivi_rjb/pitivi/ui/viewer.py	2006-12-05 00:52:44.000000000 +0000
@@ -67,7 +67,7 @@
         # changing project.
         self.project_signals = SignalGroup()
         self._connectToProject(instance.PiTiVi.current)
-        instance.PiTiVi.connect("new-project", self._newProjectCb)
+        instance.PiTiVi.connect("new-project-loaded", self._newProjectCb)
 
         instance.PiTiVi.playground.connect("current-state", self._currentStateCb)
         instance.PiTiVi.playground.connect("position", self._playgroundPositionCb)
@@ -361,6 +361,7 @@
         """ the current project has changed """
         assert(instance.PiTiVi.current == project)
         self._connectToProject(project)
+        instance.PiTiVi.playground.switchToDefault()
         
     def _addTimelineToPlayground(self):
         instance.PiTiVi.playground.addPipeline(instance.PiTiVi.current.getBin())
@@ -409,6 +410,9 @@
             self.slider.set_sensitive(False)
             self.playpause_button.set_sensitive(False)
             self.record_button.set_sensitive(False)
+            self._newTime(0)
+            instance.PiTiVi.playground.pause()
+            instance.PiTiVi.playground.play()
         else:
             if isinstance(smartbin, SmartTimelineBin):
                 gst.info("switching to Timeline, setting duration to %s" % (gst.TIME_ARGS(smartbin.project.timeline.videocomp.duration)))
