[pitivi] ui: welcome window integration



commit 330d62a4195299a42c6ef8d5b80521172a98f9f3
Author: HarishFulara07 <harish14143 iiitd ac in>
Date:   Tue Jun 5 00:46:10 2018 +0530

    ui: welcome window integration

 data/ui/greeter.ui                                 |  127 ++
 data/ui/mainmenubutton.ui                          |   28 +-
 pitivi/application.py                              |   41 +-
 pitivi/clipproperties.py                           |    6 +-
 pitivi/dialogs/about.py                            |  108 ++
 pitivi/dialogs/browseprojects.py                   |   68 +
 pitivi/dialogs/startupwizard.py                    |  167 ---
 pitivi/editorperspective.py                        | 1076 ++++++++++++++++
 pitivi/effects.py                                  |    2 +-
 pitivi/greeterperspective.py                       |  228 ++++
 pitivi/mainwindow.py                               | 1295 ++------------------
 pitivi/medialibrary.py                             |   10 +-
 pitivi/perspective.py                              |   39 +
 pitivi/project.py                                  |   14 +-
 pitivi/render.py                                   |    2 +-
 pitivi/timeline/elements.py                        |    8 +-
 pitivi/timeline/ruler.py                           |    2 +-
 pitivi/timeline/timeline.py                        |    6 +-
 pitivi/titleeditor.py                              |    6 +-
 pitivi/utils/pipeline.py                           |    2 +-
 pitivi/utils/timeline.py                           |    9 +-
 pitivi/utils/ui.py                                 |   26 +
 pitivi/utils/widgets.py                            |   24 +-
 pitivi/viewer/overlay.py                           |    4 +-
 pitivi/viewer/viewer.py                            |   27 +-
 pre-commit.hook                                    |    2 +-
 ...est_mainwindow.py => test_editorperspective.py} |   43 +-
 tests/test_project.py                              |   32 +-
 28 files changed, 1869 insertions(+), 1533 deletions(-)
---
diff --git a/data/ui/greeter.ui b/data/ui/greeter.ui
new file mode 100644
index 00000000..5cbb3949
--- /dev/null
+++ b/data/ui/greeter.ui
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <object class="GtkScrolledWindow" id="scrolled_window">
+    <property name="visible">True</property>
+    <property name="can_focus">True</property>
+    <property name="hscrollbar_policy">never</property>
+    <child>
+      <object class="GtkViewport" id="view_port">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkBox" id="topvbox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkInfoBar" id="infobar">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="show_close_button">True</property>
+                <child internal-child="action_area">
+                  <object class="GtkButtonBox">
+                    <property name="can_focus">False</property>
+                    <property name="spacing">6</property>
+                    <property name="layout_style">end</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child internal-child="content_area">
+                  <object class="GtkBox">
+                    <property name="can_focus">False</property>
+                    <property name="spacing">16</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="recent_projects_topvbox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">center</property>
+                <property name="margin_top">30</property>
+                <property name="margin_bottom">10</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkBox" id="recent_projects_labelbox">
+                    <property name="name">recent_projects_labelbox</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkLabel" id="recent_projects_label">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Recent Projects</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkListBox" id="recent_projects_listbox">
+                    <property name="name">recent_projects_listbox</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/data/ui/mainmenubutton.ui b/data/ui/mainmenubutton.ui
index c42905ef..4943108e 100644
--- a/data/ui/mainmenubutton.ui
+++ b/data/ui/mainmenubutton.ui
@@ -6,36 +6,12 @@
     <property name="visible">True</property>
     <property name="can_focus">False</property>
     <property name="halign">end</property>
-    <child>
-      <object class="GtkMenuItem" id="menu_new">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="action_name">win.new-project</property>
-        <property name="label" translatable="yes">New project</property>
-        <property name="use_underline">True</property>
-      </object>
-    </child>
-    <child>
-      <object class="GtkMenuItem" id="menu_open">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="action_name">win.open-project</property>
-        <property name="label" translatable="yes">Open project...</property>
-        <property name="use_underline">True</property>
-      </object>
-    </child>
-    <child>
-      <object class="GtkSeparatorMenuItem" id="menu_sep1">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-      </object>
-    </child>
     <child>
       <object class="GtkMenuItem" id="menu_save_as">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
         <property name="tooltip_text" translatable="yes">Save the current project under a new name or a 
different location</property>
-        <property name="action_name">win.save-as</property>
+        <property name="action_name">editor.save-as</property>
         <property name="label" translatable="yes">Save As...</property>
         <property name="use_underline">True</property>
       </object>
@@ -129,9 +105,9 @@
       <object class="GtkMenuItem" id="menu_about">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
+        <property name="action_name">win.about</property>
         <property name="label" translatable="yes">About</property>
         <property name="use_underline">True</property>
-        <signal name="activate" handler="_aboutCb" swapped="no"/>
       </object>
     </child>
   </object>
diff --git a/pitivi/application.py b/pitivi/application.py
index b855be0a..ee47959a 100644
--- a/pitivi/application.py
+++ b/pitivi/application.py
@@ -29,7 +29,6 @@ from gi.repository import Gtk
 
 from pitivi.configure import RELEASES_URL
 from pitivi.configure import VERSION
-from pitivi.dialogs.startupwizard import StartUpWizard
 from pitivi.effects import EffectsManager
 from pitivi.mainwindow import MainWindow
 from pitivi.pluginmanager import PluginManager
@@ -58,6 +57,7 @@ class Pitivi(Gtk.Application, Loggable):
         action_log (UndoableActionLog): The undo/redo log for the current project.
         effects (EffectsManager): The effects which can be applied to a clip.
         gui (MainWindow): The main window of the app.
+        recent_manager (Gtk.RecentManager): Manages recently used projects.
         project_manager (ProjectManager): The holder of the current project.
         settings (GlobalSettings): The application-wide settings.
         system (pitivi.utils.system.System): The system running the app.
@@ -84,7 +84,7 @@ class Pitivi(Gtk.Application, Loggable):
         self._last_action_time = Gst.util_get_timestamp()
 
         self.gui = None
-        self.__welcome_wizard = None
+        self.recent_manager = Gtk.RecentManager.get_default()
         self.__inhibit_cookies = {}
 
         self._version_information = {}
@@ -152,6 +152,7 @@ class Pitivi(Gtk.Application, Loggable):
         self.project_manager.connect(
             "new-project-loaded", self._newProjectLoaded)
         self.project_manager.connect("project-closed", self._projectClosed)
+        self.project_manager.connect("project-saved", self.__project_saved_cb)
 
         self._createActions()
         self._syncDoUndo()
@@ -198,27 +199,21 @@ class Pitivi(Gtk.Application, Loggable):
                 self.gui.present()
             # No need to show the welcome wizard.
             return
-        self.createMainWindow()
-        self.welcome_wizard.show()
-
-    @property
-    def welcome_wizard(self):
-        if not self.__welcome_wizard:
-            self.__welcome_wizard = StartUpWizard(self)
-        return self.__welcome_wizard
+        self.create_main_window()
+        self.gui.show_perspective(self.gui.greeter)
+        self.gui.show()
 
-    def createMainWindow(self):
+    def create_main_window(self):
         if self.gui:
             return
         self.gui = MainWindow(self)
+        self.gui.setup_ui()
         self.add_window(self.gui)
-        self.gui.checkScreenConstraints()
-        # We might as well show it.
-        self.gui.show()
 
     def do_open(self, giofiles, unused_count, unused_hint):
         assert giofiles
-        self.createMainWindow()
+        self.create_main_window()
+        self.gui.show()
         if len(giofiles) > 1:
             self.warning(
                 "Can open only one project file at a time. Ignoring the rest!")
@@ -238,8 +233,6 @@ class Pitivi(Gtk.Application, Loggable):
             self.warning(
                 "Not closing since running project doesn't want to close")
             return False
-        if self.welcome_wizard:
-            self.welcome_wizard.hide()
         if self.gui:
             self.gui.destroy()
         self.threads.stopAllThreads()
@@ -276,13 +269,23 @@ class Pitivi(Gtk.Application, Loggable):
         self._setScenarioFile(project.get_uri())
 
     def _newProjectLoaded(self, unused_project_manager, project):
+        uri = project.get_uri()
+        if uri:
+            # We remove the project from recent projects list
+            # and then re-add it to this list to make sure it
+            # gets positioned at the top of the recent projects list.
+            self.recent_manager.remove_item(uri)
+            self.recent_manager.add_item(uri)
         self.action_log = UndoableActionLog()
         self.action_log.connect("pre-push", self._action_log_pre_push_cb)
         self.action_log.connect("commit", self._actionLogCommit)
         self.action_log.connect("move", self._action_log_move_cb)
-
         self.project_observer = ProjectObserver(project, self.action_log)
 
+    def __project_saved_cb(self, unused_project_manager, unused_project, uri):
+        if uri:
+            self.recent_manager.add_item(uri)
+
     def _projectClosed(self, unused_project_manager, project):
         if project.loaded:
             self.action_log = None
@@ -389,7 +392,7 @@ class Pitivi(Gtk.Application, Loggable):
         self.project_manager.current_project.setModificationState(dirty)
         # In the tests we do not want to create any gui
         if self.gui is not None:
-            self.gui.showProjectStatus()
+            self.gui.editor.showProjectStatus()
 
     def simple_inhibit(self, reason, flags):
         """Informs the session manager about actions to be inhibited.
diff --git a/pitivi/clipproperties.py b/pitivi/clipproperties.py
index 84f1f943..3a34cb9a 100644
--- a/pitivi/clipproperties.py
+++ b/pitivi/clipproperties.py
@@ -785,7 +785,7 @@ class TransformationProperties(Gtk.Expander, Loggable):
         for prop in ["posx", "posy", "width", "height"]:
             self.__update_spin_btn(prop)
         # Keep the overlay stack in sync with the spin buttons values
-        self.app.gui.viewer.overlay_stack.update(self.source)
+        self.app.gui.editor.viewer.overlay_stack.update(self.source)
 
     def __source_property_changed_cb(self, unused_source, unused_element, param):
         self.__update_spin_btn(param.name)
@@ -862,7 +862,7 @@ class TransformationProperties(Gtk.Expander, Loggable):
 
         if value != cvalue:
             self.__set_prop(prop, value)
-            self.app.gui.viewer.overlay_stack.update(self.source)
+            self.app.gui.editor.viewer.overlay_stack.update(self.source)
 
     def __set_source(self, source):
         if self.source:
@@ -888,7 +888,7 @@ class TransformationProperties(Gtk.Expander, Loggable):
             if source:
                 self._selected_clip = clip
                 self.__set_source(source)
-                self.app.gui.viewer.overlay_stack.select(source)
+                self.app.gui.editor.viewer.overlay_stack.select(source)
                 self.show()
                 return
 
diff --git a/pitivi/dialogs/about.py b/pitivi/dialogs/about.py
new file mode 100644
index 00000000..3ac29ecf
--- /dev/null
+++ b/pitivi/dialogs/about.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+# Pitivi video editor
+# Copyright (c) 2005, Edward Hervey <bilboed bilboed com>
+#
+# 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., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+"""Pitivi's about dialog."""
+from gettext import gettext as _
+
+from gi.repository import GES
+from gi.repository import Gst
+from gi.repository import Gtk
+
+from pitivi.configure import APPNAME
+from pitivi.configure import APPURL
+from pitivi.configure import GITVERSION
+from pitivi.configure import in_devel
+from pitivi.configure import VERSION
+
+
+# pylint: disable=too-few-public-methods
+class AboutDialog(Gtk.AboutDialog):
+    """Pitivi's about dialog.
+
+    Displays info regarding Pitivi's version, license,
+    maintainers, contributors, etc.
+
+    Attributes:
+        app (Pitivi): The app.
+    """
+
+    def __init__(self, app):
+        Gtk.AboutDialog.__init__(self)
+        self.set_program_name(APPNAME)
+        self.set_website(APPURL)
+
+        if in_devel():
+            version_str = _("Development version: %s") % GITVERSION
+        elif not app.isLatest():
+            version_str = _("Version %(cur_ver)s — %(new_ver)s is available") % \
+                {"cur_ver": GITVERSION,
+                 "new_ver": app.getLatest()}
+        elif GITVERSION:
+            version_str = _("Version %s") % GITVERSION
+        else:
+            version_str = _("Version %s") % VERSION
+        self.set_version(version_str)
+
+        comments = ["",
+                    "GES %s" % ".".join(map(str, GES.version())),
+                    "GTK+ %s" % ".".join(map(str, (Gtk.MAJOR_VERSION, Gtk.MINOR_VERSION))),
+                    "GStreamer %s" % ".".join(map(str, Gst.version()))]
+        self.set_comments("\n".join(comments))
+
+        authors = [_("Current maintainers:"),
+                   "Jean-François Fortin Tam <nekohayo gmail com>",
+                   "Thibault Saunier <tsaunier gnome org>",
+                   "Mathieu Duponchelle <mduponchelle1 gmail com>",
+                   "Alexandru Băluț <alexandru balut gmail com>",
+                   "",
+                   _("Past maintainers:"),
+                   "Edward Hervey <bilboed bilboed com>",
+                   "Alessandro Decina <alessandro decina collabora co uk>",
+                   "Brandon Lewis <brandon_lewis berkeley edu>",
+                   "",
+                   # Translators: this paragraph is to be translated, the list
+                   # of contributors is shown dynamically as a clickable link
+                   # below it
+                   _("Contributors:\n" +
+                     "A handwritten list here would...\n" +
+                     "• be too long,\n" +
+                     "• be frequently outdated,\n" +
+                     "• not show their relative merit.\n\n" +
+                     "Out of respect for our contributors, we point you instead to:\n"),
+                   # Translators: keep the %s at the end of the 1st line
+                   _("The list of contributors on Ohloh %s\n" +
+                     "Or you can run: git shortlog -s -n")
+                   % "http://ohloh.net/p/pitivi/contributors";, ]
+        self.set_authors(authors)
+        # Translators: See
+        # https://developer.gnome.org/gtk3/stable/GtkAboutDialog.html#gtk-about-dialog-set-translator-credits
+        # for details on how this is used.
+        translators = _("translator-credits")
+        if translators != "translator-credits":
+            self.set_translator_credits(translators)
+        documenters = ["Jean-François Fortin Tam <nekohayo gmail com>", ]
+        self.set_documenters(documenters)
+        self.set_license_type(Gtk.License.LGPL_2_1)
+        self.set_icon_name("pitivi")
+        self.set_logo_icon_name("pitivi")
+        self.connect("response", self.__about_response_cb)
+        self.set_transient_for(app.gui)
+
+    @staticmethod
+    def __about_response_cb(dialog, unused_response):
+        dialog.destroy()
diff --git a/pitivi/dialogs/browseprojects.py b/pitivi/dialogs/browseprojects.py
new file mode 100644
index 00000000..3815813b
--- /dev/null
+++ b/pitivi/dialogs/browseprojects.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+# Pitivi video editor
+# Copyright (c) 2005, Edward Hervey <bilboed bilboed com>
+#
+# 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., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+"""Dialog for browsing through projects to open an existing project."""
+from gettext import gettext as _
+
+from gi.repository import GES
+from gi.repository import Gtk
+
+
+# pylint: disable=too-few-public-methods
+class BrowseProjectsDialog(Gtk.FileChooserDialog):
+    """Displays the Gtk.FileChooserDialog for browsing projects.
+
+    Attributes:
+        app (Pitivi): The app.
+    """
+
+    def __init__(self, app):
+        Gtk.FileChooserDialog.__init__(self)
+
+        self.set_title(_("Open File..."))
+        self.set_transient_for(app.gui)
+        self.set_action(Gtk.FileChooserAction.OPEN)
+
+        self.add_buttons(_("Cancel"), Gtk.ResponseType.CANCEL,
+                         _("Open"), Gtk.ResponseType.OK)
+        self.set_default_response(Gtk.ResponseType.OK)
+        self.set_select_multiple(False)
+        # TODO: Remove this set_current_folder call when GTK bug 683999 is
+        # fixed
+        self.set_current_folder(app.settings.lastProjectFolder)
+        formatter_assets = GES.list_assets(GES.Formatter)
+        formatter_assets.sort(
+            key=lambda x: - x.get_meta(GES.META_FORMATTER_RANK))
+        for format_ in formatter_assets:
+            filt = Gtk.FileFilter()
+            filt.set_name(format_.get_meta(GES.META_DESCRIPTION))
+            filt.add_pattern("*%s" %
+                             format_.get_meta(GES.META_FORMATTER_EXTENSION))
+            self.add_filter(filt)
+        default = Gtk.FileFilter()
+        default.set_name(_("All supported formats"))
+        default.add_custom(Gtk.FileFilterFlags.URI, self.__can_load_uri, None)
+        self.add_filter(default)
+
+    # pylint: disable=bare-except
+    @staticmethod
+    def __can_load_uri(filterinfo, unused_uri):
+        try:
+            return GES.Formatter.can_load_uri(filterinfo.uri)
+        except:
+            return False
diff --git a/pitivi/editorperspective.py b/pitivi/editorperspective.py
new file mode 100644
index 00000000..b263e7c4
--- /dev/null
+++ b/pitivi/editorperspective.py
@@ -0,0 +1,1076 @@
+# -*- coding: utf-8 -*-
+# Pitivi video editor
+# Copyright (c) 2005, Edward Hervey <bilboed bilboed com>
+#
+# 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., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+import os
+from gettext import gettext as _
+from time import time
+from urllib.parse import unquote
+
+from gi.repository import Gdk
+from gi.repository import GES
+from gi.repository import Gio
+from gi.repository import GstPbutils
+from gi.repository import Gtk
+
+from pitivi.clipproperties import ClipProperties
+from pitivi.configure import APPNAME
+from pitivi.configure import get_ui_dir
+from pitivi.dialogs.prefs import PreferencesDialog
+from pitivi.effects import EffectListWidget
+from pitivi.mediafilespreviewer import PreviewWidget
+from pitivi.medialibrary import AssetThumbnail
+from pitivi.medialibrary import MediaLibraryWidget
+from pitivi.perspective import Perspective
+from pitivi.project import ProjectSettingsDialog
+from pitivi.settings import GlobalSettings
+from pitivi.tabsmanager import BaseTabs
+from pitivi.timeline.timeline import TimelineContainer
+from pitivi.titleeditor import TitleEditor
+from pitivi.transitions import TransitionsListWidget
+from pitivi.utils.loggable import Loggable
+from pitivi.utils.misc import path_from_uri
+from pitivi.utils.ui import beautify_missing_asset
+from pitivi.utils.ui import beautify_time_delta
+from pitivi.utils.ui import clear_styles
+from pitivi.utils.ui import info_name
+from pitivi.utils.ui import PADDING
+from pitivi.utils.ui import SPACING
+from pitivi.utils.ui import TIMELINE_CSS
+from pitivi.viewer.viewer import ViewerContainer
+
+
+GlobalSettings.addConfigSection("main-window")
+GlobalSettings.addConfigOption('mainWindowHPanePosition',
+                               section="main-window",
+                               key="hpane-position",
+                               type_=int)
+GlobalSettings.addConfigOption('mainWindowMainHPanePosition',
+                               section="main-window",
+                               key="main-hpane-position",
+                               type_=int)
+GlobalSettings.addConfigOption('mainWindowVPanePosition',
+                               section="main-window",
+                               key="vpane-position",
+                               type_=int)
+GlobalSettings.addConfigOption('lastProjectFolder',
+                               section="main-window",
+                               key="last-folder",
+                               environment="PITIVI_PROJECT_FOLDER",
+                               default=os.path.expanduser("~"))
+
+
+class EditorPerspective(Perspective, Loggable):
+    """Pitivi's Editor perspective.
+
+    Attributes:
+        app (Pitivi): The app.
+    """
+
+    def __init__(self, app):
+        Perspective.__init__(self)
+        Loggable.__init__(self)
+
+        self.app = app
+        self.settings = app.settings
+
+        self.builder_handler_ids = []
+        self.builder = Gtk.Builder()
+
+        pm = self.app.project_manager
+        pm.connect("new-project-loaded",
+                   self._projectManagerNewProjectLoadedCb)
+        pm.connect("save-project-failed",
+                   self._projectManagerSaveProjectFailedCb)
+        pm.connect("project-saved", self._projectManagerProjectSavedCb)
+        pm.connect("closing-project", self._projectManagerClosingProjectCb)
+        pm.connect("reverting-to-saved",
+                   self._projectManagerRevertingToSavedCb)
+        pm.connect("project-closed", self._projectManagerProjectClosedCb)
+        pm.connect("missing-uri", self._projectManagerMissingUriCb)
+
+    def setup_ui(self):
+        """Sets up the UI."""
+        self.__setup_css()
+        self._createUi()
+        self.app.gui.connect("destroy", self._destroyedCb)
+
+    def refresh(self):
+        """Refreshes the perspective."""
+        self.focusTimeline()
+
+    def __setup_css(self):
+        css_provider = Gtk.CssProvider()
+        css_provider.load_from_data(TIMELINE_CSS.encode('UTF-8'))
+        screen = Gdk.Screen.get_default()
+        style_context = self.app.gui.get_style_context()
+        style_context.add_provider_for_screen(screen, css_provider,
+                                              Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+
+    def _destroyedCb(self, unused_main_window):
+        """Cleanup before destroying this window."""
+        pm = self.app.project_manager
+        pm.disconnect_by_func(self._projectManagerNewProjectLoadedCb)
+        pm.disconnect_by_func(self._projectManagerSaveProjectFailedCb)
+        pm.disconnect_by_func(self._projectManagerProjectSavedCb)
+        pm.disconnect_by_func(self._projectManagerClosingProjectCb)
+        pm.disconnect_by_func(self._projectManagerRevertingToSavedCb)
+        pm.disconnect_by_func(self._projectManagerProjectClosedCb)
+        pm.disconnect_by_func(self._projectManagerMissingUriCb)
+        self.toplevel_widget.remove(self.timeline_ui)
+        self.timeline_ui.destroy()
+
+    def _renderCb(self, unused_button):
+        """Shows the RenderDialog for the current project."""
+        from pitivi.render import RenderDialog
+
+        project = self.app.project_manager.current_project
+        dialog = RenderDialog(self.app, project)
+        dialog.window.show()
+
+    def _createUi(self):
+        """Creates the graphical interface.
+
+        The rough hierarchy is:
+        vpaned:
+        - mainhpaned(secondhpaned(main_tabs, context_tabs), viewer)
+        - timeline_ui
+
+        The full hierarchy can be admired by starting the GTK+ Inspector
+        with Ctrl+Shift+I.
+        """
+        # Main "toolbar" (using client-side window decorations with HeaderBar)
+        self.headerbar = self.__create_headerbar()
+
+        # Set up our main containers, in the order documented above
+
+        # Separates the tabs+viewer from the timeline
+        self.toplevel_widget = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL)
+        # Separates the tabs from the viewer
+        self.mainhpaned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL)
+        # Separates the two sets of tabs
+        self.secondhpaned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL)
+        self.toplevel_widget.pack1(self.mainhpaned, resize=False, shrink=False)
+        self.mainhpaned.pack1(self.secondhpaned, resize=True, shrink=False)
+        self.toplevel_widget.show()
+        self.secondhpaned.show()
+        self.mainhpaned.show()
+
+        # First set of tabs
+        self.main_tabs = BaseTabs(self.app)
+        self.medialibrary = MediaLibraryWidget(self.app)
+        self.effectlist = EffectListWidget(self.app)
+        self.main_tabs.append_page("Media Library",
+            self.medialibrary, Gtk.Label(label=_("Media Library")))
+        self.main_tabs.append_page("Effect Library",
+            self.effectlist, Gtk.Label(label=_("Effect Library")))
+        self.medialibrary.connect('play', self._mediaLibraryPlayCb)
+        self.medialibrary.show()
+        self.effectlist.show()
+
+        # Second set of tabs
+        self.context_tabs = BaseTabs(self.app)
+        self.clipconfig = ClipProperties(self.app)
+        self.trans_list = TransitionsListWidget(self.app)
+        self.title_editor = TitleEditor(self.app)
+        self.context_tabs.append_page("Clip",
+            self.clipconfig, Gtk.Label(label=_("Clip")))
+        self.context_tabs.append_page("Transition",
+            self.trans_list, Gtk.Label(label=_("Transition")))
+        self.context_tabs.append_page("Title",
+            self.title_editor.widget, Gtk.Label(label=_("Title")))
+        # Show by default the Title tab, as the Clip and Transition tabs
+        # are useful only when a clip or transition is selected, but
+        # the Title tab allows adding titles.
+        self.context_tabs.set_current_page(2)
+
+        self.secondhpaned.pack1(self.main_tabs, resize=False, shrink=False)
+        self.secondhpaned.pack2(self.context_tabs, resize=False, shrink=False)
+        self.main_tabs.show()
+        self.context_tabs.show()
+
+        # Viewer
+        self.viewer = ViewerContainer(self.app)
+        self.mainhpaned.pack2(self.viewer, resize=True, shrink=False)
+
+        # Now, the lower part: the timeline
+        self.timeline_ui = TimelineContainer(self.app)
+        self.toplevel_widget.pack2(self.timeline_ui, resize=True, shrink=False)
+
+        # Setup shortcuts for HeaderBar buttons and menu items.
+        self.__set_keyboard_shortcuts()
+
+        # Identify widgets for AT-SPI, making our test suite easier to develop
+        # These will show up in sniff, accerciser, etc.
+        self.headerbar.get_accessible().set_name("editor_headerbar")
+        self.menu_button.get_accessible().set_name("main menu button")
+        self.toplevel_widget.get_accessible().set_name("contents")
+        self.mainhpaned.get_accessible().set_name("upper half")
+        self.secondhpaned.get_accessible().set_name("tabs")
+        self.main_tabs.get_accessible().set_name("primary tabs")
+        self.context_tabs.get_accessible().set_name("secondary tabs")
+        self.viewer.get_accessible().set_name("viewer")
+        self.timeline_ui.get_accessible().set_name("timeline area")
+
+        # Restore settings for position and visibility.
+        if self.settings.mainWindowHPanePosition is None:
+            self._setDefaultPositions()
+        self.secondhpaned.set_position(self.settings.mainWindowHPanePosition)
+        self.mainhpaned.set_position(self.settings.mainWindowMainHPanePosition)
+        self.toplevel_widget.set_position(self.settings.mainWindowVPanePosition)
+
+        self.updateTitle()
+
+    def _setDefaultPositions(self):
+        window_width = self.app.gui.get_size()[0]
+        if self.settings.mainWindowHPanePosition is None:
+            self.settings.mainWindowHPanePosition = window_width / 3
+        if self.settings.mainWindowMainHPanePosition is None:
+            self.settings.mainWindowMainHPanePosition = 2 * window_width / 3
+        if self.settings.mainWindowVPanePosition is None:
+            screen_width = float(self.app.gui.get_screen().get_width())
+            screen_height = float(self.app.gui.get_screen().get_height())
+            req = self.toplevel_widget.get_preferred_size()[0]
+            if screen_width / screen_height < 0.75:
+                # Tall screen, give some more vertical space the the tabs.
+                value = req.height / 3
+            else:
+                value = req.height / 2
+            self.settings.mainWindowVPanePosition = value
+
+    def switchContextTab(self, ges_clip):
+        """Activates the appropriate tab on the second set of tabs.
+
+        Args:
+            ges_clip (GES.SourceClip): The clip which has been focused.
+        """
+        if isinstance(ges_clip, GES.TitleClip):
+            page = 2
+        elif isinstance(ges_clip, GES.SourceClip):
+            page = 0
+        elif isinstance(ges_clip, GES.TransitionClip):
+            page = 1
+        else:
+            self.warning("Unknown clip type: %s", ges_clip)
+            return
+        self.context_tabs.set_current_page(page)
+
+    def focusTimeline(self):
+        layers_representation = self.timeline_ui.timeline.layout
+        # Check whether it has focus already, grab_focus always emits an event.
+        if not layers_representation.props.is_focus:
+            layers_representation.grab_focus()
+
+    def __create_headerbar(self):
+        headerbar = Gtk.HeaderBar()
+        headerbar.set_show_close_button(True)
+
+        back_button = Gtk.Button.new_from_icon_name(
+            "go-previous-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
+        back_button.set_always_show_image(True)
+        back_button.set_tooltip_text(_("Close project"))
+        back_button.connect("clicked", self.__close_project_cb)
+        back_button.set_margin_right(4 * PADDING)
+        headerbar.pack_start(back_button)
+
+        undo_button = Gtk.Button.new_from_icon_name(
+            "edit-undo-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
+        undo_button.set_always_show_image(True)
+        undo_button.set_label(_("Undo"))
+        undo_button.set_action_name("app.undo")
+        undo_button.set_use_underline(True)
+
+        redo_button = Gtk.Button.new_from_icon_name(
+            "edit-redo-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
+        redo_button.set_always_show_image(True)
+        redo_button.set_action_name("app.redo")
+        redo_button.set_use_underline(True)
+
+        self.save_button = Gtk.Button.new_with_label(_("Save"))
+        self.save_button.set_focus_on_click(False)
+
+        self.render_button = Gtk.Button.new_from_icon_name(
+            "system-run-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
+        self.render_button.set_always_show_image(True)
+        self.render_button.set_label(_("Render"))
+        self.render_button.set_tooltip_text(
+            _("Export your project as a finished movie"))
+        self.render_button.set_sensitive(False)  # The only one we have to set.
+        self.render_button.connect("clicked", self._renderCb)
+
+        undo_redo_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
+        undo_redo_box.get_style_context().add_class("linked")
+        undo_redo_box.pack_start(undo_button, expand=False, fill=False, padding=0)
+        undo_redo_box.pack_start(redo_button, expand=False, fill=False, padding=0)
+        headerbar.pack_start(undo_redo_box)
+
+        self.builder.add_from_file(
+            os.path.join(get_ui_dir(), "mainmenubutton.ui"))
+
+        # FIXME : see https://bugzilla.gnome.org/show_bug.cgi?id=729263
+        self.builder.connect_signals_full(self._builderConnectCb, self)
+
+        self.menu_button = self.builder.get_object("menubutton")
+
+        self._menubutton_items = {}
+        for widget in self.builder.get_object("menu").get_children():
+            self._menubutton_items[Gtk.Buildable.get_name(widget)] = widget
+
+        headerbar.pack_end(self.menu_button)
+        headerbar.pack_end(self.save_button)
+        headerbar.pack_end(self.render_button)
+        headerbar.show_all()
+
+        return headerbar
+
+    def __set_keyboard_shortcuts(self):
+        group = Gio.SimpleActionGroup()
+        self.toplevel_widget.insert_action_group("editor", group)
+        self.headerbar.insert_action_group("editor", group)
+
+        self.save_action = Gio.SimpleAction.new("save", None)
+        self.save_action.connect("activate", self._saveProjectCb)
+        group.add_action(self.save_action)
+        self.app.shortcuts.add("editor.save", ["<Primary>s"],
+                               _("Save the current project"), group="win")
+        self.save_button.set_action_name("editor.save")
+
+        self.save_as_action = Gio.SimpleAction.new("save-as", None)
+        self.save_as_action.connect("activate", self._saveProjectAsCb)
+        group.add_action(self.save_as_action)
+        self.app.shortcuts.add("editor.save-as", ["<Primary><Shift>s"],
+                               _("Save the current project as"), group="win")
+
+        self.import_asset_action = Gio.SimpleAction.new("import-asset", None)
+        self.import_asset_action.connect("activate", self.__import_asset_cb)
+        group.add_action(self.import_asset_action)
+        self.app.shortcuts.add("editor.import-asset", ["<Primary>i"],
+                               _("Add media files to your project"), group="win")
+
+    def __import_asset_cb(self, unusdaction, unusedparam):
+        self.medialibrary.show_import_assets_dialog()
+
+    def showProjectStatus(self):
+        project = self.app.project_manager.current_project
+        dirty = project.hasUnsavedModifications()
+        self.save_action.set_enabled(dirty)
+        if project.uri:
+            self._menubutton_items["menu_revert_to_saved"].set_sensitive(dirty)
+        self.updateTitle()
+
+# UI Callbacks
+
+    def _mediaLibraryPlayCb(self, unused_medialibrary, asset):
+        """Previews the specified asset.
+
+        If the media library item to preview is an image, show it in the user's
+        favorite image viewer. Else, preview the video/sound in Pitivi.
+        """
+        # Technically, our preview widget can show images, but it's never going
+        # to do a better job (sizing, zooming, metadata, editing, etc.)
+        # than the user's favorite image viewer.
+        if asset.is_image():
+            Gio.AppInfo.launch_default_for_uri(asset.get_id(), None)
+        else:
+            preview_window = PreviewAssetWindow(asset, self.app)
+            preview_window.preview()
+
+    def _projectChangedCb(self, unused_project):
+        self.save_action.set_enabled(True)
+        self.updateTitle()
+
+    def _builderConnectCb(self, builder, gobject, signal_name, handler_name,
+                          connect_object, flags, user_data):
+        id_ = gobject.connect(signal_name, getattr(self, handler_name))
+        self.builder_handler_ids.append((gobject, id_))
+
+# Toolbar/Menu actions callback
+
+    def __close_project_cb(self, unused_button):
+        """Closes the current project."""
+        self.app.project_manager.closeRunningProject()
+
+    def _saveProjectCb(self, action, unused_param):
+        if not self.app.project_manager.current_project.uri or self.app.project_manager.disable_save:
+            self.saveProjectAs()
+        else:
+            self.app.project_manager.saveProject()
+
+    def _saveProjectAsCb(self, unused_action, unused_param):
+        self.saveProjectAs()
+
+    def saveProject(self):
+        self._saveProjectCb(None, None)
+
+    def saveProjectAsDialog(self):
+        self._saveProjectAsCb(None, None)
+
+    def _revertToSavedProjectCb(self, unused_action):
+        return self.app.project_manager.revertToSavedProject()
+
+    def _exportProjectAsTarCb(self, unused_action):
+        uri = self._showExportDialog(self.app.project_manager.current_project)
+        result = None
+        if uri:
+            result = self.app.project_manager.exportProject(
+                self.app.project_manager.current_project, uri)
+
+        if not result:
+            self.log("Project couldn't be exported")
+        return result
+
+    def _projectSettingsCb(self, unused_action):
+        self.showProjectSettingsDialog()
+
+    def showProjectSettingsDialog(self):
+        project = self.app.project_manager.current_project
+        dialog = ProjectSettingsDialog(self.app.gui, project, self.app)
+        dialog.window.run()
+        self.updateTitle()
+
+    def _prefsCb(self, unused_action):
+        PreferencesDialog(self.app).run()
+
+# Project management callbacks
+
+    def _projectManagerNewProjectLoadedCb(self, project_manager, project):
+        """Starts connecting the UI to the specified project.
+
+        Args:
+            project_manager (ProjectManager): The project manager.
+            project (Project): The project which has been loaded.
+        """
+        self.log("A new project has been loaded")
+
+        self._connectToProject(project)
+        project.pipeline.activatePositionListener()
+        self._setProject(project)
+
+        self.updateTitle()
+
+        if project_manager.disable_save is True:
+            # Special case: we enforce "Save as", but the normal "Save" button
+            # redirects to it if needed, so we still want it to be enabled:
+            self.save_action.set_enabled(True)
+
+        if project.ges_timeline.props.duration != 0:
+            self.render_button.set_sensitive(True)
+
+    def _projectManagerSaveProjectFailedCb(self, unused_project_manager, uri, exception=None):
+        project_filename = unquote(uri.split("/")[-1])
+        dialog = Gtk.MessageDialog(transient_for=self.app.gui,
+                                   modal=True,
+                                   message_type=Gtk.MessageType.ERROR,
+                                   buttons=Gtk.ButtonsType.OK,
+                                   text=_('Unable to save project "%s"') % project_filename)
+        if exception:
+            dialog.set_property("secondary-use-markup", True)
+            dialog.set_property("secondary-text", unquote(str(exception)))
+        dialog.set_transient_for(self.app.gui)
+        dialog.run()
+        dialog.destroy()
+        self.error("failed to save project")
+
+    def _projectManagerProjectSavedCb(self, unused_project_manager, project, uri):
+        # FIXME GES: Reimplement Undo/Redo
+        # self.app.action_log.checkpoint()
+        self.updateTitle()
+
+        self.save_action.set_enabled(False)
+        if project.uri is None:
+            project.uri = uri
+
+    def _projectManagerClosingProjectCb(self, project_manager, project):
+        """Investigates whether it's possible to close the specified project.
+
+        Args:
+            project_manager (ProjectManager): The project manager.
+            project (Project): The project which has been closed.
+
+        Returns:
+            bool: True when it's OK to close it, False when the user chooses
+                to cancel the closing operation.
+        """
+        if not project.hasUnsavedModifications():
+            return True
+
+        if project.uri and not project_manager.disable_save:
+            save = _("Save")
+        else:
+            save = _("Save as...")
+
+        dialog = Gtk.Dialog(title="", transient_for=self.app.gui, modal=True)
+        dialog.add_buttons(_("Close without saving"), Gtk.ResponseType.REJECT,
+                           _("Cancel"), Gtk.ResponseType.CANCEL,
+                           save, Gtk.ResponseType.YES)
+        # Even though we set the title to an empty string when creating dialog,
+        # seems we really have to do it once more so it doesn't show
+        # "pitivi"...
+        dialog.set_resizable(False)
+        dialog.set_default_response(Gtk.ResponseType.CANCEL)
+        dialog.get_accessible().set_name("unsaved changes dialog")
+
+        primary = Gtk.Label()
+        primary.set_line_wrap(True)
+        primary.set_use_markup(True)
+        primary.set_alignment(0, 0.5)
+
+        message = _("Save changes to the current project before closing?")
+        primary.set_markup("<span weight=\"bold\">" + message + "</span>")
+
+        secondary = Gtk.Label()
+        secondary.set_line_wrap(True)
+        secondary.set_use_markup(True)
+        secondary.set_alignment(0, 0.5)
+
+        if project.uri:
+            path = unquote(project.uri).split("file://")[1]
+            last_saved = max(
+                os.path.getmtime(path), project_manager.time_loaded)
+            time_delta = time() - last_saved
+            message = _("If you don't save, "
+                        "the changes from the last %s will be lost.") % \
+                beautify_time_delta(time_delta)
+        else:
+            message = _("If you don't save, your changes will be lost.")
+        secondary.props.label = message
+
+        # put the text in a vbox
+        vbox = Gtk.Box(homogeneous=False, spacing=SPACING * 2)
+        vbox.set_orientation(Gtk.Orientation.VERTICAL)
+        vbox.pack_start(primary, True, True, 0)
+        vbox.pack_start(secondary, True, True, 0)
+
+        # make the [[image] text] hbox
+        image = Gtk.Image.new_from_icon_name(
+            "dialog-question", Gtk.IconSize.DIALOG)
+        hbox = Gtk.Box(homogeneous=False, spacing=SPACING * 2)
+        hbox.set_orientation(Gtk.Orientation.HORIZONTAL)
+        hbox.pack_start(image, False, False, 0)
+        hbox.pack_start(vbox, True, True, 0)
+        hbox.set_border_width(SPACING)
+
+        # stuff the hbox in the dialog
+        content_area = dialog.get_content_area()
+        content_area.pack_start(hbox, True, True, 0)
+        content_area.set_spacing(SPACING * 2)
+        hbox.show_all()
+
+        response = dialog.run()
+        dialog.destroy()
+        if response == Gtk.ResponseType.YES:
+            if project.uri is not None and project_manager.disable_save is False:
+                res = self.app.project_manager.saveProject()
+            else:
+                res = self.saveProjectAs()
+        elif response == Gtk.ResponseType.REJECT:
+            res = True
+        else:
+            res = False
+
+        return res
+
+    def _projectManagerProjectClosedCb(self, unused_project_manager, project):
+        """Starts disconnecting the UI from the specified project.
+
+        This happens when the user closes the app or asks to load another
+        project, immediately after the user confirmed that unsaved changes,
+        if any, can be discarded but before the filechooser to pick the next
+        project to load appears.
+
+        Args:
+            project (Project): The project which has been closed.
+        """
+
+        # We must disconnect from the project pipeline before it is released:
+        if project.pipeline is not None:
+            project.pipeline.deactivatePositionListener()
+
+        self.info("Project closed")
+        self.updateTitle()
+        if project.loaded:
+            self._disconnectFromProject(project)
+        self.timeline_ui.setProject(None)
+        self.render_button.set_sensitive(False)
+        return False
+
+    def _projectManagerRevertingToSavedCb(self, unused_project_manager, unused_project):
+        if self.app.project_manager.current_project.hasUnsavedModifications():
+            dialog = Gtk.MessageDialog(transient_for=self.app.gui,
+                                       modal=True,
+                                       message_type=Gtk.MessageType.WARNING,
+                                       buttons=Gtk.ButtonsType.NONE,
+                                       text=_("Revert to saved project version?"))
+            dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.NO,
+                               Gtk.STOCK_REVERT_TO_SAVED, Gtk.ResponseType.YES)
+            dialog.set_resizable(False)
+            dialog.set_property("secondary-text",
+                                _("This will reload the current project. All unsaved changes will be lost."))
+            dialog.set_default_response(Gtk.ResponseType.NO)
+            dialog.set_transient_for(self.app.gui)
+            response = dialog.run()
+            dialog.destroy()
+            if response != Gtk.ResponseType.YES:
+                return False
+        return True
+
+    def _projectManagerMissingUriCb(self, project_manager, project, unused_error, asset):
+        if project.at_least_one_asset_missing:
+            # One asset is already missing so no point in spamming the user
+            # with more file-missing dialogs, as we need all of them.
+            return None
+
+        if self.app.proxy_manager.is_proxy_asset(asset):
+            uri = self.app.proxy_manager.getTargetUri(asset)
+        else:
+            uri = asset.get_id()
+        dialog = Gtk.Dialog(title=_("Locate missing file..."),
+                            transient_for=self.app.gui,
+                            modal=True)
+
+        dialog.add_buttons(_("Cancel"), Gtk.ResponseType.CANCEL,
+                           _("Open"), Gtk.ResponseType.OK)
+        dialog.set_border_width(SPACING * 2)
+        dialog.get_content_area().set_spacing(SPACING)
+        dialog.set_transient_for(self.app.gui)
+        dialog.set_default_response(Gtk.ResponseType.OK)
+
+        # This box will contain widgets with details about the missing file.
+        vbox = Gtk.Box()
+        vbox.set_orientation(Gtk.Orientation.VERTICAL)
+
+        label_start = Gtk.Label()
+        label_start.set_markup(_("The following file could not be found:"))
+        label_start.set_xalign(0)
+        vbox.pack_start(label_start, False, False, 0)
+
+        hbox = Gtk.Box()
+        hbox.set_orientation(Gtk.Orientation.HORIZONTAL)
+        hbox.set_margin_top(PADDING)
+        hbox.set_spacing(PADDING * 2)
+
+        label_asset_info = Gtk.Label()
+        label_asset_info.set_markup(beautify_missing_asset(asset))
+        label_asset_info.set_xalign(0)
+        label_asset_info.set_yalign(0)
+        hbox.pack_start(label_asset_info, False, False, 0)
+
+        small_thumb, large_thumb = AssetThumbnail.get_thumbnails_from_xdg_cache(uri)
+        if large_thumb:
+            self.debug("A thumbnail file was found for %s", uri)
+            thumbnail = Gtk.Image.new_from_pixbuf(large_thumb)
+            hbox.pack_end(thumbnail, False, False, 0)
+
+        vbox.pack_start(hbox, False, False, 0)
+
+        label_end = Gtk.Label()
+        label_end.set_markup(_("Please specify its new location:"))
+        label_end.set_xalign(0)
+        label_end.set_margin_top(PADDING)
+        vbox.pack_start(label_end, False, False, 0)
+
+        dialog.get_content_area().pack_start(vbox, False, False, 0)
+        vbox.show_all()
+
+        chooser = Gtk.FileChooserWidget(action=Gtk.FileChooserAction.OPEN)
+        chooser.set_select_multiple(False)
+        previewer = PreviewWidget(self.settings, discover_sync=True)
+        chooser.set_preview_widget(previewer)
+        chooser.set_use_preview_label(False)
+        chooser.connect('update-preview', previewer.update_preview_cb)
+        chooser.set_current_folder(self.settings.lastProjectFolder)
+        # Use a Gtk FileFilter to only show files with the same extension
+        # Note that splitext gives us the extension with the ".", no need to
+        # add it inside the filter string.
+        unused_filename, extension = os.path.splitext(uri)
+        filter_ = Gtk.FileFilter()
+        # Translators: this is a format filter in a filechooser. Ex: "AVI
+        # files"
+        filter_.set_name(_("%s files") % extension)
+        filter_.add_pattern("*%s" % extension.lower())
+        filter_.add_pattern("*%s" % extension.upper())
+        default = Gtk.FileFilter()
+        default.set_name(_("All files"))
+        default.add_pattern("*")
+        chooser.add_filter(filter_)
+        chooser.add_filter(default)
+        dialog.get_content_area().pack_start(chooser, True, True, 0)
+        chooser.show()
+
+        # If the window is too big, the window manager will resize it so that
+        # it fits on the screen.
+        dialog.set_default_size(1024, 1000)
+        response = dialog.run()
+
+        new_uri = None
+        if response == Gtk.ResponseType.OK:
+            self.log("User chose a new URI for the missing file")
+            new_uri = chooser.get_uri()
+        else:
+            dialog.hide()
+
+            if not self.app.proxy_manager.checkProxyLoadingSucceeded(asset):
+                # Reset the project manager and disconnect all the signals.
+                project_manager.closeRunningProject()
+                # Signal the project loading failure.
+                # You have to do this *after* successfully creating a blank project,
+                # or the startupwizard will still be connected to that signal too.
+                reason = _('No replacement file was provided for "<i>%s</i>".\n\n'
+                           'Pitivi does not currently support partial projects.') % \
+                    info_name(asset)
+                project_manager.emit("new-project-failed", project.uri, reason)
+
+        dialog.destroy()
+        return new_uri
+
+    def _connectToProject(self, project):
+        # FIXME GES we should re-enable this when possible
+        # medialibrary.connect("missing-plugins", self._sourceListMissingPluginsCb)
+        project.connect("project-changed", self._projectChangedCb)
+        project.connect("rendering-settings-changed",
+                        self._rendering_settings_changed_cb)
+        project.ges_timeline.connect("notify::duration",
+                                     self._timelineDurationChangedCb)
+
+    def _sourceListMissingPluginsCb(
+        self, unused_project, unused_uri, unused_factory,
+            details, unused_descriptions, missingPluginsCallback):
+        res = self._installPlugins(details, missingPluginsCallback)
+        return res
+
+    def _installPlugins(self, details, missingPluginsCallback):
+        context = GstPbutils.InstallPluginsContext()
+        if self.app.system.has_x11():
+            context.set_xid(self.window.xid)
+
+        res = GstPbutils.install_plugins_async(details, context,
+                                               missingPluginsCallback)
+        return res
+
+    def _setProject(self, project):
+        """Disconnects and then reconnects callbacks to the specified project.
+
+        Args:
+            project (Project): The new current project.
+        """
+        if not project:
+            self.warning("Current project instance does not exist")
+            return False
+
+        self.viewer.setPipeline(project.pipeline)
+        self._reset_viewer_aspect_ratio(project)
+        self.clipconfig.project = project
+
+        # When creating a blank project there's no project URI yet.
+        if project.uri:
+            folder_path = os.path.dirname(path_from_uri(project.uri))
+            self.settings.lastProjectFolder = folder_path
+
+    def _disconnectFromProject(self, project):
+        project.disconnect_by_func(self._projectChangedCb)
+        project.disconnect_by_func(self._rendering_settings_changed_cb)
+        project.ges_timeline.disconnect_by_func(self._timelineDurationChangedCb)
+
+    def _rendering_settings_changed_cb(self, project, unused_item):
+        """Handles Project metadata changes."""
+        self._reset_viewer_aspect_ratio(project)
+
+    def _reset_viewer_aspect_ratio(self, project):
+        """Resets the viewer aspect ratio."""
+        self.viewer.setDisplayAspectRatio(project.getDAR())
+        self.viewer.timecode_entry.setFramerate(project.videorate)
+
+    def _timelineDurationChangedCb(self, timeline, unused_duration):
+        """Updates the render button.
+
+        This covers the case when a clip is inserted into a blank timeline.
+        This callback is not triggered by loading a project.
+        """
+        duration = timeline.get_duration()
+        self.debug("Timeline duration changed to %s", duration)
+        self.render_button.set_sensitive(duration > 0)
+
+    def _showExportDialog(self, project):
+        self.log("Export requested")
+        chooser = Gtk.FileChooserDialog(title=_("Export To..."),
+                                        transient_for=self.app.gui,
+                                        action=Gtk.FileChooserAction.SAVE)
+        chooser.add_buttons(_("Cancel"), Gtk.ResponseType.CANCEL,
+                            _("Save"), Gtk.ResponseType.OK)
+        chooser.set_default_response(Gtk.ResponseType.OK)
+
+        chooser.set_select_multiple(False)
+        chooser.props.do_overwrite_confirmation = True
+
+        asset = GES.Formatter.get_default()
+        asset_extension = asset.get_meta(GES.META_FORMATTER_EXTENSION)
+
+        if not project.name:
+            chooser.set_current_name(
+                _("Untitled") + "." + asset_extension + "_tar")
+        else:
+            chooser.set_current_name(
+                project.name + "." + asset_extension + "_tar")
+
+        filt = Gtk.FileFilter()
+        filt.set_name(_("Tar archive"))
+        filt.add_pattern("*.%s_tar" % asset_extension)
+        chooser.add_filter(filt)
+        default = Gtk.FileFilter()
+        default.set_name(_("Detect automatically"))
+        default.add_pattern("*")
+        chooser.add_filter(default)
+
+        response = chooser.run()
+        if response == Gtk.ResponseType.OK:
+            self.log("User chose a URI to export project to")
+            # need to do this to work around bug in Gst.uri_construct
+            # which escapes all /'s in path!
+            uri = "file://" + chooser.get_filename()
+            self.log("uri: %s", uri)
+            ret = uri
+        else:
+            self.log("User didn't choose a URI to export project to")
+            ret = None
+
+        chooser.destroy()
+        return ret
+
+    def saveProjectAs(self):
+        uri = self._showSaveAsDialog()
+        if uri is None:
+            return False
+        return self.app.project_manager.saveProject(uri)
+
+    def _showSaveAsDialog(self):
+        self.log("Save URI requested")
+        chooser = Gtk.FileChooserDialog(title=_("Save As..."),
+                                        transient_for=self.app.gui,
+                                        action=Gtk.FileChooserAction.SAVE)
+        chooser.add_buttons(_("Cancel"), Gtk.ResponseType.CANCEL,
+                            _("Save"), Gtk.ResponseType.OK)
+        chooser.set_default_response(Gtk.ResponseType.OK)
+        asset = GES.Formatter.get_default()
+        filt = Gtk.FileFilter()
+        filt.set_name(asset.get_meta(GES.META_DESCRIPTION))
+        filt.add_pattern("*.%s" % asset.get_meta(GES.META_FORMATTER_EXTENSION))
+        chooser.add_filter(filt)
+
+        chooser.set_select_multiple(False)
+        chooser.set_current_name(_("Untitled") + "." +
+                                 asset.get_meta(GES.META_FORMATTER_EXTENSION))
+        chooser.set_current_folder(self.settings.lastProjectFolder)
+        chooser.props.do_overwrite_confirmation = True
+
+        default = Gtk.FileFilter()
+        default.set_name(_("Detect automatically"))
+        default.add_pattern("*")
+        chooser.add_filter(default)
+
+        response = chooser.run()
+        if response == Gtk.ResponseType.OK:
+            self.log("User chose a URI to save project to")
+            # need to do this to work around bug in Gst.uri_construct
+            # which escapes all /'s in path!
+            uri = "file://" + chooser.get_filename()
+            file_filter = chooser.get_filter().get_name()
+            self.log("uri:%s , filter:%s", uri, file_filter)
+            self.settings.lastProjectFolder = chooser.get_current_folder()
+            ret = uri
+        else:
+            self.log("User didn't choose a URI to save project to")
+            ret = None
+
+        chooser.destroy()
+        return ret
+
+    def _screenshotCb(self, unused_action):
+        """Exports a snapshot of the current frame as an image file."""
+        foo = self._showSaveScreenshotDialog()
+        if foo:
+            path, mime = foo[0], foo[1]
+            self.app.project_manager.current_project.pipeline.save_thumbnail(
+                -1, -1, mime, path)
+
+    def _showSaveScreenshotDialog(self):
+        """Asks the user where to save the current frame.
+
+        Returns:
+            List[str]: The full path and the mimetype if successful, None otherwise.
+        """
+        chooser = Gtk.FileChooserDialog(title=_("Save As..."),
+            transient_for=self.app.gui, action=Gtk.FileChooserAction.SAVE)
+        chooser.add_buttons(_("Cancel"), Gtk.ResponseType.CANCEL,
+                            _("Save"), Gtk.ResponseType.OK)
+        chooser.set_default_response(Gtk.ResponseType.OK)
+        chooser.set_select_multiple(False)
+        chooser.set_current_name(_("Untitled"))
+        chooser.props.do_overwrite_confirmation = True
+        formats = {_("PNG image"): ["image/png", ("png",)],
+                   _("JPEG image"): ["image/jpeg", ("jpg", "jpeg")]}
+        for format in formats:
+            filt = Gtk.FileFilter()
+            filt.set_name(format)
+            filt.add_mime_type(formats.get(format)[0])
+            chooser.add_filter(filt)
+        response = chooser.run()
+        if response == Gtk.ResponseType.OK:
+            chosen_format = formats.get(chooser.get_filter().get_name())
+            chosen_ext = chosen_format[1][0]
+            chosen_mime = chosen_format[0]
+            uri = os.path.join(
+                chooser.get_current_folder(), chooser.get_filename())
+            ret = ["%s.%s" % (uri, chosen_ext), chosen_mime]
+        else:
+            ret = None
+        chooser.destroy()
+        return ret
+
+    def updateTitle(self):
+        project = self.app.project_manager.current_project
+        if project:
+            if project.name:
+                name = project.name
+            else:
+                name = _("Untitled")
+            unsaved_mark = ""
+            if project.hasUnsavedModifications():
+                unsaved_mark = "*"
+            title = "%s%s — %s" % (unsaved_mark, name, APPNAME)
+        else:
+            title = APPNAME
+        event_box = Gtk.EventBox()
+        label = Gtk.Label()
+        clear_styles(label)
+        label.set_text(title)
+        event_box.add(label)
+        event_box.show_all()
+        event_box.connect("button-press-event", self.__titleClickCb, project)
+        self.headerbar.set_custom_title(event_box)
+        self.app.gui.set_title(title)
+
+    def __titleClickCb(self, unused_widget, unused_event, project):
+        entry = Gtk.Entry()
+        entry.set_width_chars(100)
+        entry.set_margin_left(SPACING)
+        entry.set_margin_right(SPACING)
+        entry.show()
+        entry.set_text(project.name)
+        self.headerbar.set_custom_title(entry)
+        if project.hasDefaultName():
+            entry.grab_focus()
+        else:
+            entry.grab_focus_without_selecting()
+        entry.connect("focus-out-event", self.__titleChangedCb, project)
+        entry.connect("key_release_event", self.__titleTypeCb, project)
+
+    def __titleChangedCb(self, widget, event, project):
+        if not event.window:
+            # Workaround https://bugzilla.gnome.org/show_bug.cgi?id=757036
+            return
+        name = widget.get_text()
+        if project.name == name:
+            self.updateTitle()
+        else:
+            project.name = name
+
+    def __titleTypeCb(self, widget, event, project):
+        if event.keyval == Gdk.KEY_Return:
+            self.focusTimeline()
+            return True
+        elif event.keyval == Gdk.KEY_Escape:
+            widget.set_text(project.name)
+            self.focusTimeline()
+            return True
+        return False
+
+
+class PreviewAssetWindow(Gtk.Window):
+    """Window for previewing a video or audio asset.
+
+    Args:
+        asset (GES.UriClipAsset): The asset to be previewed.
+        app (Pitivi): The app.
+    """
+
+    def __init__(self, asset, app):
+        Gtk.Window.__init__(self)
+        self._asset = asset
+        self.app = app
+
+        self.set_title(_("Preview"))
+        self.set_type_hint(Gdk.WindowTypeHint.UTILITY)
+        self.set_transient_for(app.gui)
+
+        self._previewer = PreviewWidget(app.settings, minimal=True)
+        self.add(self._previewer)
+        self._previewer.preview_uri(self._asset.get_id())
+        self._previewer.show()
+
+        self.connect("focus-out-event", self._leavePreviewCb)
+        self.connect("key-press-event", self._keyPressCb)
+
+    def preview(self):
+        """Shows the window and starts the playback."""
+        width, height = self._calculatePreviewWindowSize()
+        self.resize(width, height)
+        # Setting the position of the window only works if it's currently hidden
+        # otherwise, after the resize the position will not be readjusted
+        self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
+        self.show()
+
+        self._previewer.play()
+        # Hack so that we really really force the "utility" window to be
+        # focused
+        self.present()
+
+    def _calculatePreviewWindowSize(self):
+        info = self._asset.get_info()
+        video_streams = info.get_video_streams()
+        if not video_streams:
+            # There is no video/image stream. This is an audio file.
+            # Resize to the minimum and let the window manager deal with it.
+            return 1, 1
+        # For videos and images, automatically resize the window
+        # Try to keep it 1:1 if it can fit within 85% of the parent window
+        video = video_streams[0]
+        img_width = video.get_square_width()
+        img_height = video.get_height()
+        mainwindow_width, mainwindow_height = self.app.gui.get_size()
+        max_width = 0.85 * mainwindow_width
+        max_height = 0.85 * mainwindow_height
+
+        controls_height = self._previewer.bbox.get_preferred_size()[0].height
+        if img_width < max_width and (img_height + controls_height) < max_height:
+            # The video is small enough, keep it 1:1
+            return img_width, img_height + controls_height
+        else:
+            # The video is too big, size it down
+            # TODO: be smarter, figure out which (width, height) is bigger
+            new_height = max_width * img_height / img_width
+            return int(max_width), int(new_height + controls_height)
+
+    def _leavePreviewCb(self, window, unused):
+        self.destroy()
+        return True
+
+    def _keyPressCb(self, unused_widget, event):
+        if event.keyval in (Gdk.KEY_Escape, Gdk.KEY_Q, Gdk.KEY_q):
+            self.destroy()
+        elif event.keyval == Gdk.KEY_space:
+            self._previewer.togglePlayback()
+        return True
diff --git a/pitivi/effects.py b/pitivi/effects.py
index 54b59d2a..face6bfd 100644
--- a/pitivi/effects.py
+++ b/pitivi/effects.py
@@ -538,7 +538,7 @@ class EffectListWidget(Gtk.Box, Loggable):
         effect_info = self.app.effects.getInfo(effect)
         if not effect_info:
             return
-        timeline = self.app.gui.timeline_ui.timeline
+        timeline = self.app.gui.editor.timeline_ui.timeline
         clip = timeline.selection.getSingleClip()
         if not clip:
             return
diff --git a/pitivi/greeterperspective.py b/pitivi/greeterperspective.py
new file mode 100644
index 00000000..771e458e
--- /dev/null
+++ b/pitivi/greeterperspective.py
@@ -0,0 +1,228 @@
+# -*- coding: utf-8 -*-
+# Pitivi video editor
+# Copyright (c) 2010 Mathieu Duponchelle <seeed laposte net>
+# Copyright (c) 2018 Harish Fulara <harishfulara1996 gmail com>
+#
+# 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., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+"""Pitivi's Welcome/Greeter perspective."""
+import os
+from gettext import gettext as _
+
+from gi.repository import Gdk
+from gi.repository import GES
+from gi.repository import Gio
+from gi.repository import Gtk
+
+from pitivi.configure import get_ui_dir
+from pitivi.dialogs.browseprojects import BrowseProjectsDialog
+from pitivi.perspective import Perspective
+from pitivi.utils.ui import fix_infobar
+from pitivi.utils.ui import GREETER_PERSPECTIVE_CSS
+
+MAX_RECENT_PROJECTS = 10
+
+
+class ProjectInfoRow(Gtk.ListBoxRow):
+    """Displays a project's info.
+
+    Attributes:
+        project: Project's meta-data.
+    """
+    def __init__(self, project):
+        Gtk.ListBoxRow.__init__(self)
+        self.uri = project.get_uri()
+        self.add(Gtk.Label(project.get_display_name(), xalign=0))
+
+
+# pylint: disable=too-many-instance-attributes
+class GreeterPerspective(Perspective):
+    """Pitivi's Welcome/Greeter perspective.
+
+    Allows the user to create a new project or open an existing one.
+
+    Attributes:
+        app (Pitivi): The app.
+    """
+
+    def __init__(self, app):
+        Perspective.__init__(self)
+
+        self.app = app
+        self.new_project_action = None
+        self.open_project_action = None
+
+        self.__recent_projects_listbox = None
+        self.__project_filter = self.__create_project_filter()
+        self.__infobar = None
+
+        if app.getLatest():
+            self.__show_newer_available_version()
+        else:
+            app.connect("version-info-received", self.__app_version_info_received_cb)
+
+    def setup_ui(self):
+        """Sets up the UI."""
+        builder = Gtk.Builder()
+        builder.add_from_file(os.path.join(get_ui_dir(), "greeter.ui"))
+
+        self.toplevel_widget = builder.get_object("scrolled_window")
+
+        self.__recent_projects_listbox = builder.get_object("recent_projects_listbox")
+        self.__recent_projects_listbox.set_selection_mode(Gtk.SelectionMode.NONE)
+        self.__recent_projects_listbox.connect(
+            "row_activated", self.__projects_row_activated_cb)
+
+        self.__infobar = builder.get_object("infobar")
+        fix_infobar(self.__infobar)
+        self.__infobar.hide()
+        self.__infobar.connect("response", self.__infobar_response_cb)
+
+        self.__setup_css()
+        self.headerbar = self.__create_headerbar()
+        self.__set_keyboard_shortcuts()
+
+    def refresh(self):
+        """Refreshes the perspective."""
+        self.toplevel_widget.grab_focus()
+        self.__show_recent_projects()
+
+    def __setup_css(self):
+        css_provider = Gtk.CssProvider()
+        css_provider.load_from_data(GREETER_PERSPECTIVE_CSS.encode('UTF-8'))
+        screen = Gdk.Screen.get_default()
+        style_context = self.app.gui.get_style_context()
+        style_context.add_provider_for_screen(screen, css_provider,
+                                              Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+
+    def __create_headerbar(self):
+        headerbar = Gtk.HeaderBar()
+        headerbar.set_show_close_button(True)
+        headerbar.set_title(_("Select a Project"))
+
+        new_project_button = Gtk.Button.new_with_label(_("New"))
+        new_project_button.set_tooltip_text(_("Create a new project"))
+        new_project_button.set_action_name("greeter.new-project")
+
+        open_project_button = Gtk.Button.new_with_label(_("Open…"))
+        open_project_button.set_tooltip_text(_("Open an existing project"))
+        open_project_button.set_action_name("greeter.open-project")
+
+        self.menu_button = self.__create_menu()
+
+        headerbar.pack_start(new_project_button)
+        headerbar.pack_start(open_project_button)
+        headerbar.pack_end(self.menu_button)
+        headerbar.show_all()
+
+        return headerbar
+
+    def __set_keyboard_shortcuts(self):
+        group = Gio.SimpleActionGroup()
+        self.toplevel_widget.insert_action_group("greeter", group)
+        self.headerbar.insert_action_group("greeter", group)
+
+        self.new_project_action = Gio.SimpleAction.new("new-project", None)
+        self.new_project_action.connect("activate", self.__new_project_cb)
+        group.add_action(self.new_project_action)
+        self.app.shortcuts.add("greeter.new-project", ["<Primary>n"],
+                               _("Create a new project"), group="win")
+
+        self.open_project_action = Gio.SimpleAction.new("open-project", None)
+        self.open_project_action.connect("activate", self.__open_project_cb)
+        group.add_action(self.open_project_action)
+        self.app.shortcuts.add("greeter.open-project", ["<Primary>o"],
+                               _("Open a project"), group="win")
+
+    def __show_recent_projects(self):
+        """Displays recent projects."""
+        # Clear the currently displayed list.
+        for child in self.__recent_projects_listbox.get_children():
+            self.__recent_projects_listbox.remove(child)
+
+        recent_items = [item for item in self.app.recent_manager.get_items()
+                        if item.get_display_name().endswith(self.__project_filter)]
+
+        for item in recent_items[:MAX_RECENT_PROJECTS]:
+            self.__recent_projects_listbox.add(ProjectInfoRow(item))
+
+        self.__recent_projects_listbox.show_all()
+
+    @staticmethod
+    def __create_project_filter():
+        filter_ = []
+        for asset in GES.list_assets(GES.Formatter):
+            filter_.append(asset.get_meta(GES.META_FORMATTER_EXTENSION))
+        return tuple(filter_)
+
+    @staticmethod
+    def __create_menu():
+        builder = Gtk.Builder()
+        builder.add_from_file(os.path.join(get_ui_dir(), "mainmenubutton.ui"))
+        menu_button = builder.get_object("menubutton")
+        # Menu options we want to display.
+        visible_options = ["menu_shortcuts", "menu_help", "menu_about"]
+        for widget in builder.get_object("menu").get_children():
+            if Gtk.Buildable.get_name(widget) not in visible_options:
+                widget.hide()
+            else:
+                visible_options.remove(Gtk.Buildable.get_name(widget))
+        assert not visible_options
+        return menu_button
+
+    def __new_project_cb(self, unused_action, unused_param):
+        self.app.project_manager.newBlankProject()
+
+    def __open_project_cb(self, unused_action, unused_param):
+        dialog = BrowseProjectsDialog(self.app)
+        response = dialog.run()
+        uri = dialog.get_uri()
+        dialog.destroy()
+        if response == Gtk.ResponseType.OK:
+            self.app.project_manager.loadProject(uri)
+
+    def __app_version_info_received_cb(self, app, unused_version_information):
+        """Handles new version info."""
+        if app.isLatest():
+            # current version, don't show message
+            return
+        self.__show_newer_available_version()
+
+    def __show_newer_available_version(self):
+        latest_version = self.app.getLatest()
+
+        if self.app.settings.lastCurrentVersion != latest_version:
+            # new latest version, reset counter
+            self.app.settings.lastCurrentVersion = latest_version
+            self.app.settings.displayCounter = 0
+
+        if self.app.settings.displayCounter >= 5:
+            # current version info already showed 5 times, don't show again
+            return
+
+        # increment counter, create infobar and show info
+        self.app.settings.displayCounter += 1
+        text = _("Pitivi %s is available.") % latest_version
+        label = Gtk.Label(label=text)
+        self.__infobar.get_content_area().add(label)
+        self.__infobar.set_message_type(Gtk.MessageType.INFO)
+        self.__infobar.show_all()
+
+    def __infobar_response_cb(self, unused_infobar, response_id):
+        if response_id == Gtk.ResponseType.CLOSE:
+            self.__infobar.hide()
+
+    def __projects_row_activated_cb(self, unused_listbox, row):
+        self.app.project_manager.loadProject(row.uri)
diff --git a/pitivi/mainwindow.py b/pitivi/mainwindow.py
index 79d76f26..8397be71 100644
--- a/pitivi/mainwindow.py
+++ b/pitivi/mainwindow.py
@@ -16,63 +16,23 @@
 # License along with this program; if not, write to the
 # Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 # Boston, MA 02110-1301, USA.
+"""Pitivi's main window."""
 import os
 from gettext import gettext as _
-from time import time
 from urllib.parse import unquote
 
-from gi.repository import Gdk
-from gi.repository import GES
 from gi.repository import Gio
-from gi.repository import Gst
-from gi.repository import GstPbutils
 from gi.repository import Gtk
 
-from pitivi.clipproperties import ClipProperties
-from pitivi.configure import APPNAME
-from pitivi.configure import APPURL
 from pitivi.configure import get_pixmap_dir
-from pitivi.configure import get_ui_dir
-from pitivi.configure import GITVERSION
-from pitivi.configure import in_devel
-from pitivi.configure import VERSION
-from pitivi.dialogs.prefs import PreferencesDialog
-from pitivi.effects import EffectListWidget
-from pitivi.mediafilespreviewer import PreviewWidget
-from pitivi.medialibrary import AssetThumbnail
-from pitivi.medialibrary import MediaLibraryWidget
-from pitivi.project import ProjectSettingsDialog
+from pitivi.dialogs.about import AboutDialog
+from pitivi.editorperspective import EditorPerspective
+from pitivi.greeterperspective import GreeterPerspective
 from pitivi.settings import GlobalSettings
-from pitivi.tabsmanager import BaseTabs
-from pitivi.timeline.timeline import TimelineContainer
-from pitivi.titleeditor import TitleEditor
-from pitivi.transitions import TransitionsListWidget
 from pitivi.utils.loggable import Loggable
-from pitivi.utils.misc import path_from_uri
 from pitivi.utils.misc import show_user_manual
-from pitivi.utils.ui import beautify_missing_asset
-from pitivi.utils.ui import beautify_time_delta
-from pitivi.utils.ui import clear_styles
-from pitivi.utils.ui import info_name
-from pitivi.utils.ui import PADDING
-from pitivi.utils.ui import SPACING
-from pitivi.utils.ui import TIMELINE_CSS
-from pitivi.viewer.viewer import ViewerContainer
 
 
-GlobalSettings.addConfigSection("main-window")
-GlobalSettings.addConfigOption('mainWindowHPanePosition',
-                               section="main-window",
-                               key="hpane-position",
-                               type_=int)
-GlobalSettings.addConfigOption('mainWindowMainHPanePosition',
-                               section="main-window",
-                               key="main-hpane-position",
-                               type_=int)
-GlobalSettings.addConfigOption('mainWindowVPanePosition',
-                               section="main-window",
-                               key="vpane-position",
-                               type_=int)
 GlobalSettings.addConfigOption('mainWindowX',
                                section="main-window",
                                key="X", default=0, type_=int)
@@ -85,17 +45,14 @@ GlobalSettings.addConfigOption('mainWindowWidth',
 GlobalSettings.addConfigOption('mainWindowHeight',
                                section="main-window",
                                key="height", default=-1, type_=int)
-GlobalSettings.addConfigOption('lastProjectFolder',
-                               section="main-window",
-                               key="last-folder",
-                               environment="PITIVI_PROJECT_FOLDER",
-                               default=os.path.expanduser("~"))
+
 GlobalSettings.addConfigSection('export')
 GlobalSettings.addConfigOption('lastExportFolder',
                                section='export',
                                key="last-export-folder",
                                environment="PITIVI_EXPORT_FOLDER",
                                default=os.path.expanduser("~"))
+
 GlobalSettings.addConfigSection("version")
 GlobalSettings.addConfigOption('displayCounter',
                                section='version',
@@ -110,6 +67,9 @@ GlobalSettings.addConfigOption('lastCurrentVersion',
 class MainWindow(Gtk.ApplicationWindow, Loggable):
     """Pitivi's main window.
 
+    It manages the UI and handles the switch between different perspectives,
+    such as the default GreeterPerspective, and the EditorPerspective.
+
     Attributes:
         app (Pitivi): The app.
     """
@@ -125,213 +85,51 @@ class MainWindow(Gtk.ApplicationWindow, Loggable):
         os.environ["PULSE_PROP_media.role"] = "production"
         os.environ["PULSE_PROP_application.icon_name"] = "pitivi"
 
-        Gtk.ApplicationWindow.__init__(self)
-        Loggable.__init__(self)
-        self.app = app
-        self.log("Creating MainWindow")
-        self.settings = app.settings
-
         Gtk.IconTheme.get_default().append_search_path(get_pixmap_dir())
 
-        self.connect("destroy", self._destroyedCb)
-
-        self.setupCss()
-        self.builder_handler_ids = []
-        self.builder = Gtk.Builder()
-
-        self._createUi()
-        self.recent_manager = Gtk.RecentManager()
-
-        pm = self.app.project_manager
-        pm.connect("new-project-loading",
-                   self._projectManagerNewProjectLoadingCb)
-        pm.connect("new-project-loaded",
-                   self._projectManagerNewProjectLoadedCb)
-        pm.connect("new-project-failed",
-                   self._projectManagerNewProjectFailedCb)
-        pm.connect("save-project-failed",
-                   self._projectManagerSaveProjectFailedCb)
-        pm.connect("project-saved", self._projectManagerProjectSavedCb)
-        pm.connect("closing-project", self._projectManagerClosingProjectCb)
-        pm.connect("reverting-to-saved",
-                   self._projectManagerRevertingToSavedCb)
-        pm.connect("project-closed", self._projectManagerProjectClosedCb)
-        pm.connect("missing-uri", self._projectManagerMissingUriCb)
-
-    def setupCss(self):
-        css_provider = Gtk.CssProvider()
-        css_provider.load_from_data(TIMELINE_CSS.encode('UTF-8'))
-        screen = Gdk.Screen.get_default()
-        style_context = self.get_style_context()
-        style_context.add_provider_for_screen(screen, css_provider,
-                                              Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
-
-    def showRenderDialog(self):
-        """Shows the RenderDialog for the current project."""
-        from pitivi.render import RenderDialog
-
-        project = self.app.project_manager.current_project
-        dialog = RenderDialog(self.app, project)
-        dialog.window.show()
-
-    def _destroyedCb(self, unused_self):
-        self.render_button.disconnect_by_func(self._renderCb)
-        pm = self.app.project_manager
-        pm.disconnect_by_func(self._projectManagerNewProjectLoadingCb)
-        pm.disconnect_by_func(self._projectManagerNewProjectLoadedCb)
-        pm.disconnect_by_func(self._projectManagerNewProjectFailedCb)
-        pm.disconnect_by_func(self._projectManagerSaveProjectFailedCb)
-        pm.disconnect_by_func(self._projectManagerProjectSavedCb)
-        pm.disconnect_by_func(self._projectManagerClosingProjectCb)
-        pm.disconnect_by_func(self._projectManagerRevertingToSavedCb)
-        pm.disconnect_by_func(self._projectManagerProjectClosedCb)
-        pm.disconnect_by_func(self._projectManagerMissingUriCb)
-        self.save_action.disconnect_by_func(self._saveProjectCb)
-        self.new_project_action.disconnect_by_func(self._newProjectMenuCb)
-        self.open_project_action.disconnect_by_func(self._openProjectCb)
-        self.save_as_action.disconnect_by_func(self._saveProjectAsCb)
-        self.help_action.disconnect_by_func(self._userManualCb)
-        self.menu_button_action.disconnect_by_func(self._menuCb)
-        self.disconnect_by_func(self._destroyedCb)
-        self.disconnect_by_func(self._configureCb)
-        for gobject, id_ in self.builder_handler_ids:
-            gobject.disconnect(id_)
-        self.builder_handler_ids = None
-        self.vpaned.remove(self.timeline_ui)
-        self.timeline_ui.destroy()
-
-    def _renderCb(self, unused_button):
-        self.showRenderDialog()
+        Gtk.ApplicationWindow.__init__(self)
+        Loggable.__init__(self)
 
-    def _createUi(self):
-        """Creates the graphical interface.
+        self.log("Creating main window")
 
-        The rough hierarchy is:
-        vpaned:
-        - mainhpaned(secondhpaned(main_tabs, context_tabs), viewer)
-        - timeline_ui
+        self.app = app
+        self.greeter = GreeterPerspective(app)
+        self.editor = EditorPerspective(app)
+        self.__perspective = None
+        self.help_action = None
+        self.about_action = None
+        self.main_menu_action = None
+
+        app.project_manager.connect("new-project-loading",
+                                    self.__new_project_loading_cb)
+        app.project_manager.connect("new-project-failed",
+                                    self.__new_project_failed_cb)
+        app.project_manager.connect("project-closed", self.__project_closed_cb)
+
+    def setup_ui(self):
+        """Sets up the various perspectives's UI."""
+        self.log("Setting up the perspectives.")
 
-        The full hierarchy can be admired by starting the GTK+ Inspector
-        with Ctrl+Shift+I.
-        """
         self.set_icon_name("pitivi")
+        self.__check_screen_constraints()
+        self.__set_keyboard_shortcuts()
 
-        # Main "toolbar" (using client-side window decorations with HeaderBar)
-        self._headerbar = Gtk.HeaderBar()
-        self._create_headerbar_buttons()
-        self._headerbar.set_show_close_button(True)
-        self._headerbar.show_all()
-        self.set_titlebar(self._headerbar)
-
-        # Set up our main containers, in the order documented above
-        self.vpaned = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL)  # Separates the tabs+viewer from the 
timeline
-        self.mainhpaned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL)  # Separates the tabs from the 
viewer
-        self.secondhpaned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL)  # Separates the two sets of 
tabs
-        self.vpaned.pack1(self.mainhpaned, resize=False, shrink=False)
-        self.mainhpaned.pack1(self.secondhpaned, resize=True, shrink=False)
-        self.add(self.vpaned)
-        self.vpaned.show()
-        self.secondhpaned.show()
-        self.mainhpaned.show()
-
-        # First set of tabs
-        self.main_tabs = BaseTabs(self.app)
-        self.medialibrary = MediaLibraryWidget(self.app)
-        self.effectlist = EffectListWidget(self.app)
-        self.main_tabs.append_page("Media Library",
-            self.medialibrary, Gtk.Label(label=_("Media Library")))
-        self.main_tabs.append_page("Effect Library",
-            self.effectlist, Gtk.Label(label=_("Effect Library")))
-        self.medialibrary.connect('play', self._mediaLibraryPlayCb)
-        self.medialibrary.show()
-        self.effectlist.show()
-
-        # Second set of tabs
-        self.context_tabs = BaseTabs(self.app)
-        self.clipconfig = ClipProperties(self.app)
-        self.trans_list = TransitionsListWidget(self.app)
-        self.title_editor = TitleEditor(self.app)
-        self.context_tabs.append_page("Clip",
-            self.clipconfig, Gtk.Label(label=_("Clip")))
-        self.context_tabs.append_page("Transition",
-            self.trans_list, Gtk.Label(label=_("Transition")))
-        self.context_tabs.append_page("Title",
-            self.title_editor.widget, Gtk.Label(label=_("Title")))
-        # Show by default the Title tab, as the Clip and Transition tabs
-        # are useful only when a clip or transition is selected, but
-        # the Title tab allows adding titles.
-        self.context_tabs.set_current_page(2)
-
-        self.secondhpaned.pack1(self.main_tabs, resize=False, shrink=False)
-        self.secondhpaned.pack2(self.context_tabs, resize=False, shrink=False)
-        self.main_tabs.show()
-        self.context_tabs.show()
+        self.greeter.setup_ui()
+        self.editor.setup_ui()
 
-        # Viewer
-        self.viewer = ViewerContainer(self.app)
-        self.mainhpaned.pack2(self.viewer, resize=True, shrink=False)
+        width = self.app.settings.mainWindowWidth
+        height = self.app.settings.mainWindowHeight
 
-        # Now, the lower part: the timeline
-        self.timeline_ui = TimelineContainer(self.app)
-        self.vpaned.pack2(self.timeline_ui, resize=True, shrink=False)
-
-        # Enable our shortcuts for HeaderBar buttons and menu items:
-        self._set_keyboard_shortcuts()
-
-        # Identify widgets for AT-SPI, making our test suite easier to develop
-        # These will show up in sniff, accerciser, etc.
-        self.get_accessible().set_name("main window")
-        self._headerbar.get_accessible().set_name("headerbar")
-        self._menubutton.get_accessible().set_name("main menu button")
-        self.vpaned.get_accessible().set_name("contents")
-        self.mainhpaned.get_accessible().set_name("upper half")
-        self.secondhpaned.get_accessible().set_name("tabs")
-        self.main_tabs.get_accessible().set_name("primary tabs")
-        self.context_tabs.get_accessible().set_name("secondary tabs")
-        self.viewer.get_accessible().set_name("viewer")
-        self.timeline_ui.get_accessible().set_name("timeline area")
-
-        # Restore settings for position and visibility.
-        if self.settings.mainWindowHPanePosition is None:
-            self._setDefaultPositions()
-        width = self.settings.mainWindowWidth
-        height = self.settings.mainWindowHeight
         if height == -1 and width == -1:
             self.maximize()
         else:
             self.set_default_size(width, height)
-            self.move(self.settings.mainWindowX, self.settings.mainWindowY)
-        self.secondhpaned.set_position(self.settings.mainWindowHPanePosition)
-        self.mainhpaned.set_position(self.settings.mainWindowMainHPanePosition)
-        self.vpaned.set_position(self.settings.mainWindowVPanePosition)
-
-        # Connect the main window's signals at the end, to avoid messing around
-        # with the restoration of settings above.
-        self.connect("delete-event", self._deleteCb)
-        self.connect("configure-event", self._configureCb)
+            self.move(self.app.settings.mainWindowX, self.app.settings.mainWindowY)
 
-        # Focus the timeline by default!
-        self.focusTimeline()
-        self.updateTitle()
+        self.connect("configure-event", self.__configure_cb)
+        self.connect("delete-event", self.__delete_cb)
 
-    def _setDefaultPositions(self):
-        window_width = self.get_size()[0]
-        if self.settings.mainWindowHPanePosition is None:
-            self.settings.mainWindowHPanePosition = window_width / 3
-        if self.settings.mainWindowMainHPanePosition is None:
-            self.settings.mainWindowMainHPanePosition = 2 * window_width / 3
-        if self.settings.mainWindowVPanePosition is None:
-            screen_width = float(self.get_screen().get_width())
-            screen_height = float(self.get_screen().get_height())
-            req = self.vpaned.get_preferred_size()[0]
-            if screen_width / screen_height < 0.75:
-                # Tall screen, give some more vertical space the the tabs.
-                value = req.height / 3
-            else:
-                value = req.height / 2
-            self.settings.mainWindowVPanePosition = value
-
-    def checkScreenConstraints(self):
+    def __check_screen_constraints(self):
         """Measures the approximate minimum size required by the main window.
 
         Shrinks some widgets to fit better on smaller screen resolutions.
@@ -340,7 +138,7 @@ class MainWindow(Gtk.ApplicationWindow, Loggable):
         # is only an approximation. As of 2015, GTK still does not have
         # a way, even with client-side decorations, to tell us the exact
         # minimum required dimensions of a window.
-        min_size, natural_size = self.get_preferred_size()
+        min_size, _ = self.get_preferred_size()
         screen_width = self.get_screen().get_width()
         screen_height = self.get_screen().get_height()
         self.debug("Minimum UI size is %sx%s", min_size.width, min_size.height)
@@ -348,549 +146,67 @@ class MainWindow(Gtk.ApplicationWindow, Loggable):
         if min_size.width >= 0.9 * screen_width:
             self.medialibrary.activateCompactMode()
             self.viewer.activateCompactMode()
-            min_size, natural_size = self.get_preferred_size()
+            min_size, _ = self.get_preferred_size()
             self.info("Minimum UI size has been reduced to %sx%s",
                       min_size.width, min_size.height)
 
-    def switchContextTab(self, ges_clip):
-        """Activates the appropriate tab on the second set of tabs.
-
-        Args:
-            ges_clip (GES.SourceClip): The clip which has been focused.
-        """
-        if isinstance(ges_clip, GES.TitleClip):
-            page = 2
-        elif isinstance(ges_clip, GES.SourceClip):
-            page = 0
-        elif isinstance(ges_clip, GES.TransitionClip):
-            page = 1
-        else:
-            self.warning("Unknown clip type: %s", ges_clip)
-            return
-        self.context_tabs.set_current_page(page)
-
-    def focusTimeline(self):
-        layers_representation = self.timeline_ui.timeline.layout
-        # Check whether it has focus already, grab_focus always emits an event.
-        if not layers_representation.props.is_focus:
-            layers_representation.grab_focus()
-
-    def _create_headerbar_buttons(self):
-
-        undo_button = Gtk.Button.new_from_icon_name(
-            "edit-undo-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
-        undo_button.set_always_show_image(True)
-        undo_button.set_label(_("Undo"))
-        undo_button.set_action_name("app.undo")
-        undo_button.set_use_underline(True)
-
-        redo_button = Gtk.Button.new_from_icon_name(
-            "edit-redo-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
-        redo_button.set_always_show_image(True)
-        redo_button.set_action_name("app.redo")
-        redo_button.set_use_underline(True)
-
-        self.save_button = Gtk.Button.new_with_label(_("Save"))
-        self.save_button.set_focus_on_click(False)
-
-        self.render_button = Gtk.Button.new_from_icon_name(
-            "system-run-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
-        self.render_button.set_always_show_image(True)
-        self.render_button.set_label(_("Render"))
-        self.render_button.set_tooltip_text(
-            _("Export your project as a finished movie"))
-        self.render_button.set_sensitive(False)  # The only one we have to set.
-        self.render_button.connect("clicked", self._renderCb)
-
-        undo_redo_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
-        undo_redo_box.get_style_context().add_class("linked")
-        undo_redo_box.pack_start(undo_button, expand=False, fill=False, padding=0)
-        undo_redo_box.pack_start(redo_button, expand=False, fill=False, padding=0)
-        self._headerbar.pack_start(undo_redo_box)
-
-        self.builder.add_from_file(
-            os.path.join(get_ui_dir(), "mainmenubutton.ui"))
-
-        # FIXME : see https://bugzilla.gnome.org/show_bug.cgi?id=729263
-        self.builder.connect_signals_full(self._builderConnectCb, self)
-
-        self._menubutton = self.builder.get_object("menubutton")
-
-        self._menubutton_items = {}
-        for widget in self.builder.get_object("menu").get_children():
-            self._menubutton_items[Gtk.Buildable.get_name(widget)] = widget
-
-        self._headerbar.pack_end(self._menubutton)
-        self._headerbar.pack_end(self.save_button)
-        self._headerbar.pack_end(self.render_button)
-
-    def _set_keyboard_shortcuts(self):
+    def __set_keyboard_shortcuts(self):
         self.app.shortcuts.register_group("win", _("Project"), position=20)
-        self.save_action = Gio.SimpleAction.new("save", None)
-        self.save_action.connect("activate", self._saveProjectCb)
-        self.add_action(self.save_action)
-        self.app.shortcuts.add("win.save", ["<Primary>s"],
-                               _("Save the current project"))
-        self.save_button.set_action_name("win.save")
-
-        self.new_project_action = Gio.SimpleAction.new("new-project", None)
-        self.new_project_action.connect("activate", self._newProjectMenuCb)
-        self.add_action(self.new_project_action)
-        self.app.shortcuts.add("win.new-project", ["<Primary>n"],
-                               _("Create a new project"))
-
-        self.open_project_action = Gio.SimpleAction.new("open-project", None)
-        self.open_project_action.connect("activate", self._openProjectCb)
-        self.add_action(self.open_project_action)
-        self.app.shortcuts.add("win.open-project", ["<Primary>o"],
-                               _("Open a project"))
-
-        self.save_as_action = Gio.SimpleAction.new("save-as", None)
-        self.save_as_action.connect("activate", self._saveProjectAsCb)
-        self.add_action(self.save_as_action)
-        self.app.shortcuts.add("win.save-as", ["<Primary><Shift>s"],
-                               _("Save the current project as"))
 
         self.help_action = Gio.SimpleAction.new("help", None)
-        self.help_action.connect("activate", self._userManualCb)
+        self.help_action.connect("activate", self.__user_manual_cb)
         self.add_action(self.help_action)
         self.app.shortcuts.add("win.help", ["F1"], _("Help"), group="app")
 
-        self.menu_button_action = Gio.SimpleAction.new("menu-button", None)
-        self.menu_button_action.connect("activate", self._menuCb)
-        self.add_action(self.menu_button_action)
-        self.app.shortcuts.add("win.menu-button", ["F10"],
-                               _("Show the menu button content"),
-                               group="app")
+        self.about_action = Gio.SimpleAction.new("about", None)
+        self.about_action.connect("activate", self.__about_cb)
+        self.add_action(self.about_action)
+        self.app.shortcuts.add("win.about", ["<Primary><Shift>a"],
+                               _("About"), group="app")
 
-        import_asset_action = Gio.SimpleAction.new("import-asset", None)
-        import_asset_action.connect("activate", self.__import_asset_cb)
-        self.add_action(import_asset_action)
-        self.app.shortcuts.add("win.import-asset", ["<Primary>i"],
-                               _("Add media files to your project"))
+        self.main_menu_action = Gio.SimpleAction.new("menu-button", None)
+        self.main_menu_action.connect("activate", self.__menu_cb)
+        self.add_action(self.main_menu_action)
+        self.app.shortcuts.add("win.menu-button", ["F10"],
+                               _("Show the menu button content"), group="app")
 
-    def __import_asset_cb(self, unusdaction, unusedparam):
-        self.medialibrary.show_import_assets_dialog()
+    @staticmethod
+    def __user_manual_cb(unused_action, unused_param):
+        show_user_manual()
 
-    def showProjectStatus(self):
-        project = self.app.project_manager.current_project
-        dirty = project.hasUnsavedModifications()
-        self.save_action.set_enabled(dirty)
-        if project.uri:
-            self._menubutton_items["menu_revert_to_saved"].set_sensitive(dirty)
-        self.updateTitle()
+    def __about_cb(self, unused_action, unused_param):
+        about_dialog = AboutDialog(self.app)
+        about_dialog.show()
 
-# UI Callbacks
+    def __menu_cb(self, unused_action, unused_param):
+        self.__perspective.menu_button.set_active(
+            not self.__perspective.menu_button.get_active())
 
-    def _configureCb(self, unused_widget, unused_event):
+    def __configure_cb(self, unused_widget, unused_event):
         """Saves the main window position and size."""
         # Takes window manager decorations into account.
         position = self.get_position()
-        self.settings.mainWindowX = position.root_x
-        self.settings.mainWindowY = position.root_y
+        self.app.settings.mainWindowX = position.root_x
+        self.app.settings.mainWindowY = position.root_y
 
         # Does not include the size of the window manager decorations.
         size = self.get_size()
-        self.settings.mainWindowWidth = size.width
-        self.settings.mainWindowHeight = size.height
-
-    def _deleteCb(self, unused_widget, unused_data=None):
-        self._saveWindowSettings()
-        if not self.app.shutdown():
-            return True
-
-        return False
-
-    def _saveWindowSettings(self):
-        self.settings.mainWindowHPanePosition = self.secondhpaned.get_position(
-        )
-        self.settings.mainWindowMainHPanePosition = self.mainhpaned.get_position(
-        )
-        self.settings.mainWindowVPanePosition = self.vpaned.get_position()
-
-    def _mediaLibraryPlayCb(self, unused_medialibrary, asset):
-        """Previews the specified asset.
-
-        If the media library item to preview is an image, show it in the user's
-        favorite image viewer. Else, preview the video/sound in Pitivi.
-        """
-        # Technically, our preview widget can show images, but it's never going
-        # to do a better job (sizing, zooming, metadata, editing, etc.)
-        # than the user's favorite image viewer.
-        if asset.is_image():
-            Gio.AppInfo.launch_default_for_uri(asset.get_id(), None)
-        else:
-            preview_window = PreviewAssetWindow(asset, self)
-            preview_window.preview()
-
-    def _projectChangedCb(self, unused_project):
-        self.save_action.set_enabled(True)
-        self.updateTitle()
-
-    def _builderConnectCb(self, builder, gobject, signal_name, handler_name,
-                          connect_object, flags, user_data):
-        id_ = gobject.connect(signal_name, getattr(self, handler_name))
-        self.builder_handler_ids.append((gobject, id_))
-
-# Toolbar/Menu actions callback
-
-    def _newProjectMenuCb(self, unused_action, unused_param):
-        self.app.project_manager.newBlankProject()
-
-    def _openProjectCb(self, unused_action, unused_param):
-        self.openProject()
-
-    def _saveProjectCb(self, action, unused_param):
-        if not self.app.project_manager.current_project.uri or self.app.project_manager.disable_save:
-            self.saveProjectAs()
-        else:
-            self.app.project_manager.saveProject()
-
-    def _saveProjectAsCb(self, unused_action, unused_param):
-        self.saveProjectAs()
-
-    def saveProject(self):
-        self._saveProjectCb(None, None)
-
-    def saveProjectAsDialog(self):
-        self._saveProjectAsCb(None, None)
-
-    def _revertToSavedProjectCb(self, unused_action):
-        return self.app.project_manager.revertToSavedProject()
-
-    def _exportProjectAsTarCb(self, unused_action):
-        uri = self._showExportDialog(self.app.project_manager.current_project)
-        result = None
-        if uri:
-            result = self.app.project_manager.exportProject(
-                self.app.project_manager.current_project, uri)
-
-        if not result:
-            self.log("Project couldn't be exported")
-        return result
-
-    def _projectSettingsCb(self, unused_action):
-        self.showProjectSettingsDialog()
-
-    def showProjectSettingsDialog(self):
-        project = self.app.project_manager.current_project
-        dialog = ProjectSettingsDialog(self, project, self.app)
-        dialog.window.run()
-        self.updateTitle()
-
-    def _menuCb(self, unused_action, unused_param):
-        self._menubutton.set_active(not self._menubutton.get_active())
-
-    def _userManualCb(self, unused_action, unused_param):
-        show_user_manual()
-
-    def _aboutResponseCb(self, dialog, unused_response):
-        dialog.destroy()
-
-    def _aboutCb(self, unused_action):
-        abt = Gtk.AboutDialog()
-        abt.set_program_name(APPNAME)
-        abt.set_website(APPURL)
-
-        if in_devel():
-            version_str = _("Development version: %s") % GITVERSION
-        elif not self.app.isLatest():
-            version_str = _("Version %(cur_ver)s — %(new_ver)s is available") % \
-                {"cur_ver": GITVERSION,
-                 "new_ver": self.app.getLatest()}
-        elif GITVERSION:
-            version_str = _("Version %s") % GITVERSION
-        else:
-            version_str = _("Version %s") % VERSION
-        abt.set_version(version_str)
-
-        comments = ["",
-                    "GES %s" % ".".join(map(str, GES.version())),
-                    "GTK+ %s" % ".".join(map(str, (Gtk.MAJOR_VERSION, Gtk.MINOR_VERSION))),
-                    "GStreamer %s" % ".".join(map(str, Gst.version()))]
-        abt.set_comments("\n".join(comments))
-
-        authors = [_("Current maintainers:"),
-                   "Jean-François Fortin Tam <nekohayo gmail com>",
-                   "Thibault Saunier <tsaunier gnome org>",
-                   "Mathieu Duponchelle <mduponchelle1 gmail com>",
-                   "Alexandru Băluț <alexandru balut gmail com>",
-                   "",
-                   _("Past maintainers:"),
-                   "Edward Hervey <bilboed bilboed com>",
-                   "Alessandro Decina <alessandro decina collabora co uk>",
-                   "Brandon Lewis <brandon_lewis berkeley edu>",
-                   "",
-                   # Translators: this paragraph is to be translated, the list
-                   # of contributors is shown dynamically as a clickable link
-                   # below it
-                   _("Contributors:\n" +
-                     "A handwritten list here would...\n" +
-                     "• be too long,\n" +
-                     "• be frequently outdated,\n" +
-                     "• not show their relative merit.\n\n" +
-                     "Out of respect for our contributors, we point you instead to:\n"),
-                   # Translators: keep the %s at the end of the 1st line
-                   _("The list of contributors on Ohloh %s\n" +
-                     "Or you can run: git shortlog -s -n")
-                   % "http://ohloh.net/p/pitivi/contributors";, ]
-        abt.set_authors(authors)
-        # Translators: See
-        # https://developer.gnome.org/gtk3/stable/GtkAboutDialog.html#gtk-about-dialog-set-translator-credits
-        # for details on how this is used.
-        translators = _("translator-credits")
-        if translators != "translator-credits":
-            abt.set_translator_credits(translators)
-        documenters = ["Jean-François Fortin Tam <nekohayo gmail com>", ]
-        abt.set_documenters(documenters)
-        abt.set_license_type(Gtk.License.LGPL_2_1)
-        abt.set_icon_name("pitivi")
-        abt.set_logo_icon_name("pitivi")
-        abt.connect("response", self._aboutResponseCb)
-        abt.set_transient_for(self)
-        abt.show()
-
-    def openProject(self):
-        # Requesting project closure at this point in time prompts users about
-        # unsaved changes (if any); much better than having ProjectManager
-        # trigger this *after* the user already chose a new project to load...
-        if not self.app.project_manager.closeRunningProject():
-            return  # The user has not made a decision, don't do anything
-
-        chooser = Gtk.FileChooserDialog(title=_("Open File..."),
-                                        transient_for=self,
-                                        action=Gtk.FileChooserAction.OPEN)
-        chooser.add_buttons(_("Cancel"), Gtk.ResponseType.CANCEL,
-                            _("Open"), Gtk.ResponseType.OK)
-        chooser.set_default_response(Gtk.ResponseType.OK)
-        chooser.set_select_multiple(False)
-        # TODO: Remove this set_current_folder call when GTK bug 683999 is
-        # fixed
-        chooser.set_current_folder(self.settings.lastProjectFolder)
-        formatter_assets = GES.list_assets(GES.Formatter)
-        formatter_assets.sort(
-            key=lambda x: - x.get_meta(GES.META_FORMATTER_RANK))
-        for format_ in formatter_assets:
-            filt = Gtk.FileFilter()
-            filt.set_name(format_.get_meta(GES.META_DESCRIPTION))
-            filt.add_pattern("*%s" %
-                             format_.get_meta(GES.META_FORMATTER_EXTENSION))
-            chooser.add_filter(filt)
-        default = Gtk.FileFilter()
-        default.set_name(_("All supported formats"))
-        default.add_custom(Gtk.FileFilterFlags.URI, self._canLoadUri, None)
-        chooser.add_filter(default)
-
-        response = chooser.run()
-        uri = chooser.get_uri()
-        chooser.destroy()
-        if response == Gtk.ResponseType.OK:
-            self.app.project_manager.loadProject(uri)
-        else:
-            self.info("User cancelled loading a new project")
-            self.app.welcome_wizard.show()
-
-    def _canLoadUri(self, filterinfo, unused_uri):
-        try:
-            return GES.Formatter.can_load_uri(filterinfo.uri)
-        except:
-            return False
-
-    def _prefsCb(self, unused_action):
-        PreferencesDialog(self.app).run()
-
-# Project management callbacks
-
-    def _projectManagerNewProjectLoadedCb(self, project_manager, project):
-        """Starts connecting the UI to the specified project.
-
-        Args:
-            project_manager (ProjectManager): The project manager.
-            project (Project): The project which has been loaded.
-        """
-        self.log("A new project has been loaded")
-        self._connectToProject(project)
-        project.pipeline.activatePositionListener()
-        self._setProject(project)
-
-        self.updateTitle()
-
-        if project_manager.disable_save is True:
-            # Special case: we enforce "Save as", but the normal "Save" button
-            # redirects to it if needed, so we still want it to be enabled:
-            self.save_action.set_enabled(True)
-
-        if project.ges_timeline.props.duration != 0:
-            self.render_button.set_sensitive(True)
-
-    def _projectManagerNewProjectLoadingCb(self, unused_project_manager, project):
-        uri = project.get_uri()
-        if uri:
-            self.recent_manager.add_item(uri)
-        self.log("A NEW project is loading, deactivate UI")
-
-    def _projectManagerSaveProjectFailedCb(self, unused_project_manager, uri, exception=None):
-        project_filename = unquote(uri.split("/")[-1])
-        dialog = Gtk.MessageDialog(transient_for=self,
-                                   modal=True,
-                                   message_type=Gtk.MessageType.ERROR,
-                                   buttons=Gtk.ButtonsType.OK,
-                                   text=_('Unable to save project "%s"') % project_filename)
-        if exception:
-            dialog.set_property("secondary-use-markup", True)
-            dialog.set_property("secondary-text", unquote(str(exception)))
-        dialog.set_transient_for(self)
-        dialog.run()
-        dialog.destroy()
-        self.error("failed to save project")
-
-    def _projectManagerProjectSavedCb(self, unused_project_manager, project, uri):
-        # FIXME GES: Reimplement Undo/Redo
-        # self.app.action_log.checkpoint()
-        self.updateTitle()
-
-        self.save_action.set_enabled(False)
-        if uri:
-            self.recent_manager.add_item(uri)
-
-        if project.uri is None:
-            project.uri = uri
+        self.app.settings.mainWindowWidth = size.width
+        self.app.settings.mainWindowHeight = size.height
 
-    def _projectManagerClosingProjectCb(self, project_manager, project):
-        """Investigates whether it's possible to close the specified project.
+    def __delete_cb(self, unused_widget, unused_data=None):
+        self.app.settings.mainWindowHPanePosition = self.editor.secondhpaned.get_position()
+        self.app.settings.mainWindowMainHPanePosition = self.editor.mainhpaned.get_position()
+        self.app.settings.mainWindowVPanePosition = self.editor.toplevel_widget.get_position()
 
-        Args:
-            project_manager (ProjectManager): The project manager.
-            project (Project): The project which has been closed.
-
-        Returns:
-            bool: True when it's OK to close it, False when the user chooses
-                to cancel the closing operation.
-        """
-        if not project.hasUnsavedModifications():
+        if not self.app.shutdown():
             return True
-
-        if project.uri and not project_manager.disable_save:
-            save = _("Save")
-        else:
-            save = _("Save as...")
-
-        dialog = Gtk.Dialog(title="", transient_for=self, modal=True)
-        dialog.add_buttons(_("Close without saving"), Gtk.ResponseType.REJECT,
-                           _("Cancel"), Gtk.ResponseType.CANCEL,
-                           save, Gtk.ResponseType.YES)
-        # Even though we set the title to an empty string when creating dialog,
-        # seems we really have to do it once more so it doesn't show
-        # "pitivi"...
-        dialog.set_resizable(False)
-        dialog.set_default_response(Gtk.ResponseType.CANCEL)
-        dialog.get_accessible().set_name("unsaved changes dialog")
-
-        primary = Gtk.Label()
-        primary.set_line_wrap(True)
-        primary.set_use_markup(True)
-        primary.set_alignment(0, 0.5)
-
-        message = _("Save changes to the current project before closing?")
-        primary.set_markup("<span weight=\"bold\">" + message + "</span>")
-
-        secondary = Gtk.Label()
-        secondary.set_line_wrap(True)
-        secondary.set_use_markup(True)
-        secondary.set_alignment(0, 0.5)
-
-        if project.uri:
-            path = unquote(project.uri).split("file://")[1]
-            last_saved = max(
-                os.path.getmtime(path), project_manager.time_loaded)
-            time_delta = time() - last_saved
-            message = _("If you don't save, "
-                        "the changes from the last %s will be lost.") % \
-                beautify_time_delta(time_delta)
-        else:
-            message = _("If you don't save, your changes will be lost.")
-        secondary.props.label = message
-
-        # put the text in a vbox
-        vbox = Gtk.Box(homogeneous=False, spacing=SPACING * 2)
-        vbox.set_orientation(Gtk.Orientation.VERTICAL)
-        vbox.pack_start(primary, True, True, 0)
-        vbox.pack_start(secondary, True, True, 0)
-
-        # make the [[image] text] hbox
-        image = Gtk.Image.new_from_icon_name(
-            "dialog-question", Gtk.IconSize.DIALOG)
-        hbox = Gtk.Box(homogeneous=False, spacing=SPACING * 2)
-        hbox.set_orientation(Gtk.Orientation.HORIZONTAL)
-        hbox.pack_start(image, False, False, 0)
-        hbox.pack_start(vbox, True, True, 0)
-        hbox.set_border_width(SPACING)
-
-        # stuff the hbox in the dialog
-        content_area = dialog.get_content_area()
-        content_area.pack_start(hbox, True, True, 0)
-        content_area.set_spacing(SPACING * 2)
-        hbox.show_all()
-
-        response = dialog.run()
-        dialog.destroy()
-        if response == Gtk.ResponseType.YES:
-            if project.uri is not None and project_manager.disable_save is False:
-                res = self.app.project_manager.saveProject()
-            else:
-                res = self.saveProjectAs()
-        elif response == Gtk.ResponseType.REJECT:
-            res = True
-        else:
-            res = False
-
-        return res
-
-    def _projectManagerProjectClosedCb(self, unused_project_manager, project):
-        """Starts disconnecting the UI from the specified project.
-
-        This happens when the user closes the app or asks to load another
-        project, immediately after the user confirmed that unsaved changes,
-        if any, can be discarded but before the filechooser to pick the next
-        project to load appears.
-
-        Args:
-            project (Project): The project which has been closed.
-        """
-
-        # We must disconnect from the project pipeline before it is released:
-        if project.pipeline is not None:
-            project.pipeline.deactivatePositionListener()
-
-        self.info("Project closed")
-        self.updateTitle()
-        if project.loaded:
-            self._disconnectFromProject(project)
-        self.timeline_ui.setProject(None)
-        self.render_button.set_sensitive(False)
         return False
 
-    def _projectManagerRevertingToSavedCb(self, unused_project_manager, unused_project):
-        if self.app.project_manager.current_project.hasUnsavedModifications():
-            dialog = Gtk.MessageDialog(transient_for=self,
-                                       modal=True,
-                                       message_type=Gtk.MessageType.WARNING,
-                                       buttons=Gtk.ButtonsType.NONE,
-                                       text=_("Revert to saved project version?"))
-            dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.NO,
-                               Gtk.STOCK_REVERT_TO_SAVED, Gtk.ResponseType.YES)
-            dialog.set_resizable(False)
-            dialog.set_property("secondary-text",
-                                _("This will reload the current project. All unsaved changes will be lost."))
-            dialog.set_default_response(Gtk.ResponseType.NO)
-            dialog.set_transient_for(self)
-            response = dialog.run()
-            dialog.destroy()
-            if response != Gtk.ResponseType.YES:
-                return False
-        return True
+    def __new_project_loading_cb(self, unused_project_manager, unused_project):
+        self.show_perspective(self.editor)
 
-    def _projectManagerNewProjectFailedCb(self, unused_project_manager, uri, reason):
+    def __new_project_failed_cb(self, unused_project_manager, uri, reason):
         project_filename = unquote(uri.split("/")[-1])
         dialog = Gtk.MessageDialog(transient_for=self,
                                    modal=True,
@@ -902,451 +218,22 @@ class MainWindow(Gtk.ApplicationWindow, Loggable):
         dialog.set_transient_for(self)
         dialog.run()
         dialog.destroy()
-        self.app.welcome_wizard.show()
-
-    def _projectManagerMissingUriCb(self, project_manager, project, unused_error, asset):
-        if project.at_least_one_asset_missing:
-            # One asset is already missing so no point in spamming the user
-            # with more file-missing dialogs, as we need all of them.
-            return None
-
-        if self.app.proxy_manager.is_proxy_asset(asset):
-            uri = self.app.proxy_manager.getTargetUri(asset)
-        else:
-            uri = asset.get_id()
-        dialog = Gtk.Dialog(title=_("Locate missing file..."),
-                            transient_for=self,
-                            modal=True)
-
-        dialog.add_buttons(_("Cancel"), Gtk.ResponseType.CANCEL,
-                           _("Open"), Gtk.ResponseType.OK)
-        dialog.set_border_width(SPACING * 2)
-        dialog.get_content_area().set_spacing(SPACING)
-        dialog.set_transient_for(self)
-        dialog.set_default_response(Gtk.ResponseType.OK)
-
-        # This box will contain widgets with details about the missing file.
-        vbox = Gtk.Box()
-        vbox.set_orientation(Gtk.Orientation.VERTICAL)
-
-        label_start = Gtk.Label()
-        label_start.set_markup(_("The following file could not be found:"))
-        label_start.set_xalign(0)
-        vbox.pack_start(label_start, False, False, 0)
-
-        hbox = Gtk.Box()
-        hbox.set_orientation(Gtk.Orientation.HORIZONTAL)
-        hbox.set_margin_top(PADDING)
-        hbox.set_spacing(PADDING * 2)
-
-        label_asset_info = Gtk.Label()
-        label_asset_info.set_markup(beautify_missing_asset(asset))
-        label_asset_info.set_xalign(0)
-        label_asset_info.set_yalign(0)
-        hbox.pack_start(label_asset_info, False, False, 0)
-
-        small_thumb, large_thumb = AssetThumbnail.get_thumbnails_from_xdg_cache(uri)
-        if large_thumb:
-            self.debug("A thumbnail file was found for %s", uri)
-            thumbnail = Gtk.Image.new_from_pixbuf(large_thumb)
-            hbox.pack_end(thumbnail, False, False, 0)
-
-        vbox.pack_start(hbox, False, False, 0)
-
-        label_end = Gtk.Label()
-        label_end.set_markup(_("Please specify its new location:"))
-        label_end.set_xalign(0)
-        label_end.set_margin_top(PADDING)
-        vbox.pack_start(label_end, False, False, 0)
-
-        dialog.get_content_area().pack_start(vbox, False, False, 0)
-        vbox.show_all()
-
-        chooser = Gtk.FileChooserWidget(action=Gtk.FileChooserAction.OPEN)
-        chooser.set_select_multiple(False)
-        previewer = PreviewWidget(self.settings, discover_sync=True)
-        chooser.set_preview_widget(previewer)
-        chooser.set_use_preview_label(False)
-        chooser.connect('update-preview', previewer.update_preview_cb)
-        chooser.set_current_folder(self.settings.lastProjectFolder)
-        # Use a Gtk FileFilter to only show files with the same extension
-        # Note that splitext gives us the extension with the ".", no need to
-        # add it inside the filter string.
-        unused_filename, extension = os.path.splitext(uri)
-        filter_ = Gtk.FileFilter()
-        # Translators: this is a format filter in a filechooser. Ex: "AVI
-        # files"
-        filter_.set_name(_("%s files") % extension)
-        filter_.add_pattern("*%s" % extension.lower())
-        filter_.add_pattern("*%s" % extension.upper())
-        default = Gtk.FileFilter()
-        default.set_name(_("All files"))
-        default.add_pattern("*")
-        chooser.add_filter(filter_)
-        chooser.add_filter(default)
-        dialog.get_content_area().pack_start(chooser, True, True, 0)
-        chooser.show()
-
-        # If the window is too big, the window manager will resize it so that
-        # it fits on the screen.
-        dialog.set_default_size(1024, 1000)
-        response = dialog.run()
-
-        new_uri = None
-        if response == Gtk.ResponseType.OK:
-            self.log("User chose a new URI for the missing file")
-            new_uri = chooser.get_uri()
-        else:
-            dialog.hide()
-
-            if not self.app.proxy_manager.checkProxyLoadingSucceeded(asset):
-                # Reset the project manager and disconnect all the signals.
-                project_manager.closeRunningProject()
-                # Signal the project loading failure.
-                # You have to do this *after* successfully creating a blank project,
-                # or the startupwizard will still be connected to that signal too.
-                reason = _('No replacement file was provided for "<i>%s</i>".\n\n'
-                           'Pitivi does not currently support partial projects.') % \
-                    info_name(asset)
-                project_manager.emit("new-project-failed", project.uri, reason)
-
-        dialog.destroy()
-        return new_uri
-
-    def _connectToProject(self, project):
-        # FIXME GES we should re-enable this when possible
-        # medialibrary.connect("missing-plugins", self._sourceListMissingPluginsCb)
-        project.connect("project-changed", self._projectChangedCb)
-        project.connect("rendering-settings-changed",
-                        self._rendering_settings_changed_cb)
-        project.ges_timeline.connect("notify::duration",
-                                     self._timelineDurationChangedCb)
-
-    def _sourceListMissingPluginsCb(
-        self, unused_project, unused_uri, unused_factory,
-            details, unused_descriptions, missingPluginsCallback):
-        res = self._installPlugins(details, missingPluginsCallback)
-        return res
-
-    def _installPlugins(self, details, missingPluginsCallback):
-        context = GstPbutils.InstallPluginsContext()
-        if self.app.system.has_x11():
-            context.set_xid(self.window.xid)
-
-        res = GstPbutils.install_plugins_async(details, context,
-                                               missingPluginsCallback)
-        return res
+        self.show_perspective(self.greeter)
 
-    def _setProject(self, project):
-        """Disconnects and then reconnects callbacks to the specified project.
+    def __project_closed_cb(self, unused_project_manager, unused_project):
+        self.show_perspective(self.greeter)
 
-        Args:
-            project (Project): The new current project.
-        """
-        if not project:
-            self.warning("Current project instance does not exist")
-            return False
-
-        self.viewer.setPipeline(project.pipeline)
-        self._reset_viewer_aspect_ratio(project)
-        self.clipconfig.project = project
-
-        # When creating a blank project there's no project URI yet.
-        if project.uri:
-            folder_path = os.path.dirname(path_from_uri(project.uri))
-            self.settings.lastProjectFolder = folder_path
-
-    def _disconnectFromProject(self, project):
-        project.disconnect_by_func(self._projectChangedCb)
-        project.disconnect_by_func(self._rendering_settings_changed_cb)
-        project.ges_timeline.disconnect_by_func(self._timelineDurationChangedCb)
-
-    def _rendering_settings_changed_cb(self, project, unused_item):
-        """Handles Project metadata changes."""
-        self._reset_viewer_aspect_ratio(project)
-
-    def _reset_viewer_aspect_ratio(self, project):
-        """Resets the viewer aspect ratio."""
-        self.viewer.setDisplayAspectRatio(project.getDAR())
-        self.viewer.timecode_entry.setFramerate(project.videorate)
-
-    def _timelineDurationChangedCb(self, timeline, unused_duration):
-        """Updates the render button.
-
-        This covers the case when a clip is inserted into a blank timeline.
-        This callback is not triggered by loading a project.
-        """
-        duration = timeline.get_duration()
-        self.debug("Timeline duration changed to %s", duration)
-        self.render_button.set_sensitive(duration > 0)
-
-    def _showExportDialog(self, project):
-        self.log("Export requested")
-        chooser = Gtk.FileChooserDialog(title=_("Export To..."),
-                                        transient_for=self,
-                                        action=Gtk.FileChooserAction.SAVE)
-        chooser.add_buttons(_("Cancel"), Gtk.ResponseType.CANCEL,
-                            _("Save"), Gtk.ResponseType.OK)
-        chooser.set_default_response(Gtk.ResponseType.OK)
-
-        chooser.set_select_multiple(False)
-        chooser.props.do_overwrite_confirmation = True
-
-        asset = GES.Formatter.get_default()
-        asset_extension = asset.get_meta(GES.META_FORMATTER_EXTENSION)
-
-        if not project.name:
-            chooser.set_current_name(
-                _("Untitled") + "." + asset_extension + "_tar")
-        else:
-            chooser.set_current_name(
-                project.name + "." + asset_extension + "_tar")
-
-        filt = Gtk.FileFilter()
-        filt.set_name(_("Tar archive"))
-        filt.add_pattern("*.%s_tar" % asset_extension)
-        chooser.add_filter(filt)
-        default = Gtk.FileFilter()
-        default.set_name(_("Detect automatically"))
-        default.add_pattern("*")
-        chooser.add_filter(default)
-
-        response = chooser.run()
-        if response == Gtk.ResponseType.OK:
-            self.log("User chose a URI to export project to")
-            # need to do this to work around bug in Gst.uri_construct
-            # which escapes all /'s in path!
-            uri = "file://" + chooser.get_filename()
-            self.log("uri: %s", uri)
-            ret = uri
-        else:
-            self.log("User didn't choose a URI to export project to")
-            ret = None
-
-        chooser.destroy()
-        return ret
-
-    def saveProjectAs(self):
-        uri = self._showSaveAsDialog()
-        if uri is None:
-            return False
-        return self.app.project_manager.saveProject(uri)
-
-    def _showSaveAsDialog(self):
-        self.log("Save URI requested")
-        chooser = Gtk.FileChooserDialog(title=_("Save As..."),
-                                        transient_for=self,
-                                        action=Gtk.FileChooserAction.SAVE)
-        chooser.add_buttons(_("Cancel"), Gtk.ResponseType.CANCEL,
-                            _("Save"), Gtk.ResponseType.OK)
-        chooser.set_default_response(Gtk.ResponseType.OK)
-        asset = GES.Formatter.get_default()
-        filt = Gtk.FileFilter()
-        filt.set_name(asset.get_meta(GES.META_DESCRIPTION))
-        filt.add_pattern("*.%s" % asset.get_meta(GES.META_FORMATTER_EXTENSION))
-        chooser.add_filter(filt)
-
-        chooser.set_select_multiple(False)
-        chooser.set_current_name(_("Untitled") + "." +
-                                 asset.get_meta(GES.META_FORMATTER_EXTENSION))
-        chooser.set_current_folder(self.settings.lastProjectFolder)
-        chooser.props.do_overwrite_confirmation = True
-
-        default = Gtk.FileFilter()
-        default.set_name(_("Detect automatically"))
-        default.add_pattern("*")
-        chooser.add_filter(default)
-
-        response = chooser.run()
-        if response == Gtk.ResponseType.OK:
-            self.log("User chose a URI to save project to")
-            # need to do this to work around bug in Gst.uri_construct
-            # which escapes all /'s in path!
-            uri = "file://" + chooser.get_filename()
-            file_filter = chooser.get_filter().get_name()
-            self.log("uri:%s , filter:%s", uri, file_filter)
-            self.settings.lastProjectFolder = chooser.get_current_folder()
-            ret = uri
-        else:
-            self.log("User didn't choose a URI to save project to")
-            ret = None
-
-        chooser.destroy()
-        return ret
-
-    def _screenshotCb(self, unused_action):
-        """Exports a snapshot of the current frame as an image file."""
-        foo = self._showSaveScreenshotDialog()
-        if foo:
-            path, mime = foo[0], foo[1]
-            self.app.project_manager.current_project.pipeline.save_thumbnail(
-                -1, -1, mime, path)
-
-    def _showSaveScreenshotDialog(self):
-        """Asks the user where to save the current frame.
-
-        Returns:
-            List[str]: The full path and the mimetype if successful, None otherwise.
-        """
-        chooser = Gtk.FileChooserDialog(title=_("Save As..."),
-                                        transient_for=self, action=Gtk.FileChooserAction.SAVE)
-        chooser.add_buttons(_("Cancel"), Gtk.ResponseType.CANCEL,
-                            _("Save"), Gtk.ResponseType.OK)
-        chooser.set_default_response(Gtk.ResponseType.OK)
-        chooser.set_select_multiple(False)
-        chooser.set_current_name(_("Untitled"))
-        chooser.props.do_overwrite_confirmation = True
-        formats = {_("PNG image"): ["image/png", ("png",)],
-                   _("JPEG image"): ["image/jpeg", ("jpg", "jpeg")]}
-        for format in formats:
-            filt = Gtk.FileFilter()
-            filt.set_name(format)
-            filt.add_mime_type(formats.get(format)[0])
-            chooser.add_filter(filt)
-        response = chooser.run()
-        if response == Gtk.ResponseType.OK:
-            chosen_format = formats.get(chooser.get_filter().get_name())
-            chosen_ext = chosen_format[1][0]
-            chosen_mime = chosen_format[0]
-            uri = os.path.join(
-                chooser.get_current_folder(), chooser.get_filename())
-            ret = ["%s.%s" % (uri, chosen_ext), chosen_mime]
-        else:
-            ret = None
-        chooser.destroy()
-        return ret
-
-    def updateTitle(self):
-        project = self.app.project_manager.current_project
-        if project:
-            if project.name:
-                name = project.name
-            else:
-                name = _("Untitled")
-            unsaved_mark = ""
-            if project.hasUnsavedModifications():
-                unsaved_mark = "*"
-            title = "%s%s — %s" % (unsaved_mark, name, APPNAME)
-        else:
-            title = APPNAME
-        event_box = Gtk.EventBox()
-        label = Gtk.Label()
-        clear_styles(label)
-        label.set_text(title)
-        event_box.add(label)
-        event_box.show_all()
-        event_box.connect("button-press-event", self.__titleClickCb, project)
-        self._headerbar.set_custom_title(event_box)
-        self.set_title(title)
-
-    def __titleClickCb(self, unused_widget, unused_event, project):
-        entry = Gtk.Entry()
-        entry.set_width_chars(100)
-        entry.set_margin_left(SPACING)
-        entry.set_margin_right(SPACING)
-        entry.show()
-        entry.set_text(project.name)
-        self._headerbar.set_custom_title(entry)
-        if project.hasDefaultName():
-            entry.grab_focus()
-        else:
-            entry.grab_focus_without_selecting()
-        entry.connect("focus-out-event", self.__titleChangedCb, project)
-        entry.connect("key_release_event", self.__titleTypeCb, project)
-
-    def __titleChangedCb(self, widget, event, project):
-        if not event.window:
-            # Workaround https://bugzilla.gnome.org/show_bug.cgi?id=757036
+    def show_perspective(self, perspective):
+        """Displays the specified perspective."""
+        if self.__perspective is perspective:
             return
-        name = widget.get_text()
-        if project.name == name:
-            self.updateTitle()
-        else:
-            project.name = name
-
-    def __titleTypeCb(self, widget, event, project):
-        if event.keyval == Gdk.KEY_Return:
-            self.focusTimeline()
-            return True
-        elif event.keyval == Gdk.KEY_Escape:
-            widget.set_text(project.name)
-            self.focusTimeline()
-            return True
-        return False
-
-
-class PreviewAssetWindow(Gtk.Window):
-    """Window for previewing a video or audio asset.
-
-    Args:
-        asset (GES.UriClipAsset): The asset to be previewed.
-        main_window (MainWindow): The main window.
-    """
-
-    def __init__(self, asset, main_window):
-        Gtk.Window.__init__(self)
-        self._asset = asset
-        self._main_window = main_window
-
-        self.set_title(_("Preview"))
-        self.set_type_hint(Gdk.WindowTypeHint.UTILITY)
-        self.set_transient_for(main_window)
-
-        self._previewer = PreviewWidget(main_window.settings, minimal=True)
-        self.add(self._previewer)
-        self._previewer.preview_uri(self._asset.get_id())
-        self._previewer.show()
-
-        self.connect("focus-out-event", self._leavePreviewCb)
-        self.connect("key-press-event", self._keyPressCb)
-
-    def preview(self):
-        """Shows the window and starts the playback."""
-        width, height = self._calculatePreviewWindowSize()
-        self.resize(width, height)
-        # Setting the position of the window only works if it's currently hidden
-        # otherwise, after the resize the position will not be readjusted
-        self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
-        self.show()
-
-        self._previewer.play()
-        # Hack so that we really really force the "utility" window to be
-        # focused
-        self.present()
-
-    def _calculatePreviewWindowSize(self):
-        info = self._asset.get_info()
-        video_streams = info.get_video_streams()
-        if not video_streams:
-            # There is no video/image stream. This is an audio file.
-            # Resize to the minimum and let the window manager deal with it.
-            return 1, 1
-        # For videos and images, automatically resize the window
-        # Try to keep it 1:1 if it can fit within 85% of the parent window
-        video = video_streams[0]
-        img_width = video.get_square_width()
-        img_height = video.get_height()
-        mainwindow_width, mainwindow_height = self._main_window.get_size()
-        max_width = 0.85 * mainwindow_width
-        max_height = 0.85 * mainwindow_height
-
-        controls_height = self._previewer.bbox.get_preferred_size()[0].height
-        if img_width < max_width and (img_height + controls_height) < max_height:
-            # The video is small enough, keep it 1:1
-            return img_width, img_height + controls_height
-        else:
-            # The video is too big, size it down
-            # TODO: be smarter, figure out which (width, height) is bigger
-            new_height = max_width * img_height / img_width
-            return int(max_width), int(new_height + controls_height)
-
-    def _leavePreviewCb(self, window, unused):
-        self.destroy()
-        return True
-
-    def _keyPressCb(self, unused_widget, event):
-        if event.keyval in (Gdk.KEY_Escape, Gdk.KEY_Q, Gdk.KEY_q):
-            self.destroy()
-        elif event.keyval == Gdk.KEY_space:
-            self._previewer.togglePlayback()
-        return True
+        if self.__perspective:
+            # Remove the current perspective before adding the
+            # specified perspective because we can only add one
+            # toplevel widget to the main window at a time.
+            self.remove(self.__perspective.toplevel_widget)
+        self.log("Displaying perspective: %s", type(perspective).__name__)
+        self.__perspective = perspective
+        self.set_titlebar(perspective.headerbar)
+        self.add(perspective.toplevel_widget)
+        perspective.refresh()
diff --git a/pitivi/medialibrary.py b/pitivi/medialibrary.py
index 9af5968c..e5843e2a 100644
--- a/pitivi/medialibrary.py
+++ b/pitivi/medialibrary.py
@@ -655,18 +655,18 @@ class MediaLibraryWidget(Gtk.Box, Loggable):
                 asset = model[row.get_path()][COL_ASSET]
                 target = asset.get_proxy_target()
                 self._project.remove_asset(asset)
-                self.app.gui.timeline_ui.purgeAsset(asset.props.id)
+                self.app.gui.editor.timeline_ui.purgeAsset(asset.props.id)
 
                 if target:
                     self._project.remove_asset(target)
-                    self.app.gui.timeline_ui.purgeAsset(target.props.id)
+                    self.app.gui.editor.timeline_ui.purgeAsset(target.props.id)
 
         # The treeview can make some of the remaining items selected, so
         # make sure none are selected.
         self._unselectAll()
 
     def _insertEndCb(self, unused_action, unused_parameter):
-        self.app.gui.timeline_ui.insertAssets(self.getSelectedAssets(), -1)
+        self.app.gui.editor.timeline_ui.insertAssets(self.getSelectedAssets(), -1)
 
     def _searchEntryChangedCb(self, entry):
         # With many hundred clips in an iconview with dynamic columns and
@@ -877,7 +877,7 @@ class MediaLibraryWidget(Gtk.Box, Loggable):
             self._addAsset(asset)
 
         if self._project.loaded:
-            self.app.gui.timeline_ui.switchProxies(asset)
+            self.app.gui.editor.timeline_ui.switchProxies(asset)
 
     def _assetAddedCb(self, unused_project, asset):
         """Checks whether the asset added to the project should be shown."""
@@ -1045,7 +1045,7 @@ class MediaLibraryWidget(Gtk.Box, Loggable):
 
     def __projectSettingsSetInfobarCb(self, infobar, response_id):
         if response_id == Gtk.ResponseType.OK:
-            self.app.gui.showProjectSettingsDialog()
+            self.app.gui.editor.showProjectSettingsDialog()
         infobar.hide()
 
     def _clipPropertiesCb(self, unused_widget):
diff --git a/pitivi/perspective.py b/pitivi/perspective.py
new file mode 100644
index 00000000..4496a1df
--- /dev/null
+++ b/pitivi/perspective.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Pitivi video editor
+# Copyright (c) 2018 Harish Fulara <harishfulara1996 gmail com>
+#
+# 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., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+"""Interface for different perspectives."""
+
+
+class Perspective(object):
+    """Interface for different perspectives."""
+
+    def __init__(self):
+        self.toplevel_widget = None
+        self.headerbar = None
+        self.menu_button = None
+
+    def setup_ui(self):
+        """Sets up the UI of the perspective.
+
+        Populates the toplevel_widget, headerbar and menu_button attributes.
+        """
+        raise NotImplementedError()
+
+    def refresh(self):
+        """Refreshes the perspective while activating it."""
+        raise NotImplementedError()
diff --git a/pitivi/project.py b/pitivi/project.py
index 5c32d470..1fb84096 100644
--- a/pitivi/project.py
+++ b/pitivi/project.py
@@ -213,9 +213,9 @@ class ProjectManager(GObject.Object, Loggable):
         dialog.destroy()
 
         if response == 1:
-            self.app.gui.saveProjectAsDialog()
+            self.app.gui.editor.saveProjectAsDialog()
         elif response == 2:
-            self.app.gui.saveProject()
+            self.app.gui.editor.saveProject()
 
         self.app.shutdown()
 
@@ -225,8 +225,7 @@ class ProjectManager(GObject.Object, Loggable):
         If a backup file exists, asks if it should be loaded instead, and if so,
         forces the user to use "Save as" afterwards.
         """
-        if self.current_project is not None and not self.closeRunningProject():
-            return False
+        assert self.current_project is None
 
         is_validate_scenario = self._isValidateScenario(uri)
         if not is_validate_scenario:
@@ -509,11 +508,8 @@ class ProjectManager(GObject.Object, Loggable):
             bool: Whether the project has been created successfully.
         """
         self.debug("New blank project")
-        if self.current_project is not None:
-            # This will prompt users about unsaved changes (if any):
-            if not ignore_unsaved_changes and not self.closeRunningProject():
-                # The user has not made a decision, don't do anything
-                return False
+
+        assert self.current_project is None
 
         self.__start_loading_time = time.time()
         project = Project(self.app, name=DEFAULT_NAME)
diff --git a/pitivi/render.py b/pitivi/render.py
index 3052c486..f902a2f0 100644
--- a/pitivi/render.py
+++ b/pitivi/render.py
@@ -1006,7 +1006,7 @@ class RenderDialog(Loggable):
         # Hide the rendering settings dialog while rendering
         self.window.hide()
 
-        self.app.gui.timeline_ui.timeline.set_best_zoom_ratio(allow_zoom_in=True)
+        self.app.gui.editor.timeline_ui.timeline.set_best_zoom_ratio(allow_zoom_in=True)
         self.project.set_rendering(True)
         self._pipeline.set_render_settings(
             self.outfile, self.project.container_profile)
diff --git a/pitivi/timeline/elements.py b/pitivi/timeline/elements.py
index 6b6bb399..98cb4d14 100644
--- a/pitivi/timeline/elements.py
+++ b/pitivi/timeline/elements.py
@@ -1129,7 +1129,7 @@ class Clip(Gtk.EventBox, Zoomable, Loggable):
             self.timeline.resetSelectionGroup()
             self.timeline.current_group.add(self.ges_clip)
             self.timeline.selection.setSelection([self.ges_clip], SELECT)
-            self.app.gui.switchContextTab(self.ges_clip)
+            self.app.gui.editor.switchContextTab(self.ges_clip)
 
             effect_info = self.app.effects.getInfo(self.timeline.dropData)
             pipeline = self.timeline.ges_timeline.get_parent()
@@ -1270,7 +1270,7 @@ class Clip(Gtk.EventBox, Zoomable, Loggable):
         else:
             self.timeline.resetSelectionGroup()
             self.timeline.current_group.add(self.ges_clip.get_toplevel_parent())
-            self.app.gui.switchContextTab(self.ges_clip)
+            self.app.gui.editor.switchContextTab(self.ges_clip)
 
         parent = self.ges_clip.get_parent()
         if parent == self.timeline.current_group or parent is None:
@@ -1474,9 +1474,9 @@ class TransitionClip(Clip):
 
     def _selectedChangedCb(self, unused_selected, selected, ges_timeline_element):
         if selected:
-            self.app.gui.trans_list.activate(ges_timeline_element)
+            self.app.gui.editor.trans_list.activate(ges_timeline_element)
         else:
-            self.app.gui.trans_list.deactivate()
+            self.app.gui.editor.trans_list.deactivate()
 
 
 GES_TYPE_UI_TYPE = {
diff --git a/pitivi/timeline/ruler.py b/pitivi/timeline/ruler.py
index 40cc9b00..d8fc327d 100644
--- a/pitivi/timeline/ruler.py
+++ b/pitivi/timeline/ruler.py
@@ -206,7 +206,7 @@ class ScaleRuler(Gtk.DrawingArea, Zoomable, Loggable):
         button = event.button
         if button == 3 or (button == 1 and self.app.settings.leftClickAlsoSeeks):
             self.debug("button released at x:%d", event.x)
-            self.app.gui.focusTimeline()
+            self.app.gui.editor.focusTimeline()
             position = self.pixelToNs(event.x + self.pixbuf_offset)
             self.__set_tooltip_text(position)
         return False
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index eab4cbfd..94db5983 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -639,7 +639,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
 
     def update_visible_overlays(self):
         sources = self.get_sources_at_position(self.__last_position)
-        self.app.gui.viewer.overlay_stack.set_current_sources(sources)
+        self.app.gui.editor.viewer.overlay_stack.set_current_sources(sources)
 
     def set_next_seek_position(self, next_seek_position):
         """Sets the position the playhead seeks to on the next button-release.
@@ -651,7 +651,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
 
     def _button_press_event_cb(self, unused_widget, event):
         self.debug("PRESSED %s", event)
-        self.app.gui.focusTimeline()
+        self.app.gui.editor.focusTimeline()
 
         event_widget = Gtk.get_event_widget(event)
 
@@ -1411,7 +1411,7 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
                 else:
                     raise TimelineError("Cannot insert: %s" % type(obj))
                 clip_position += duration
-        self.app.gui.focusTimeline()
+        self.app.gui.editor.focusTimeline()
 
         if zoom_was_fitted:
             self.timeline.set_best_zoom_ratio()
diff --git a/pitivi/titleeditor.py b/pitivi/titleeditor.py
index 1c899642..c6be684b 100644
--- a/pitivi/titleeditor.py
+++ b/pitivi/titleeditor.py
@@ -225,7 +225,7 @@ class TitleEditor(Loggable):
         title_clip = GES.TitleClip()
         duration = self.app.settings.titleClipLength * Gst.MSECOND
         title_clip.set_duration(duration)
-        self.app.gui.timeline_ui.insert_clips_on_first_layer([title_clip])
+        self.app.gui.editor.timeline_ui.insert_clips_on_first_layer([title_clip])
         # Now that the clip is inserted in the timeline, it has a source which
         # can be used to set its properties.
         source = title_clip.get_children(False)[0]
@@ -237,8 +237,8 @@ class TitleEditor(Loggable):
         assert source.set_child_property("halignment", DEFAULT_HALIGNMENT)
         # Select it so the Title editor becomes active.
         self._selection.setSelection([title_clip], SELECT)
-        self.app.gui.timeline_ui.timeline.resetSelectionGroup()
-        self.app.gui.timeline_ui.timeline.current_group.add(title_clip)
+        self.app.gui.editor.timeline_ui.timeline.resetSelectionGroup()
+        self.app.gui.editor.timeline_ui.timeline.current_group.add(title_clip)
 
     def _propertyChangedCb(self, source, unused_gstelement, pspec):
         if self._setting_props:
diff --git a/pitivi/utils/pipeline.py b/pitivi/utils/pipeline.py
index ddafd6ef..d2a622ee 100644
--- a/pitivi/utils/pipeline.py
+++ b/pitivi/utils/pipeline.py
@@ -613,7 +613,7 @@ class Pipeline(GES.Pipeline, SimplePipeline):
 
     def _busMessageCb(self, bus, message):
         if message.type == Gst.MessageType.ASYNC_DONE:
-            self.app.gui.timeline_ui.timeline.update_visible_overlays()
+            self.app.gui.editor.timeline_ui.timeline.update_visible_overlays()
 
         if message.type == Gst.MessageType.ASYNC_DONE and\
                 self._commit_wanted:
diff --git a/pitivi/utils/timeline.py b/pitivi/utils/timeline.py
index d17a124c..d4d2e905 100644
--- a/pitivi/utils/timeline.py
+++ b/pitivi/utils/timeline.py
@@ -265,7 +265,7 @@ class EditingContext(GObject.Object, Loggable):
         if self.__log_actions:
             self.app.action_log.commit("move-clip")
         self.timeline.get_asset().pipeline.commit_timeline()
-        self.timeline.ui.app.gui.viewer.clipTrimPreviewFinished()
+        self.timeline.ui.app.gui.editor.viewer.clipTrimPreviewFinished()
 
     def setMode(self, mode):
         """Sets the current editing mode.
@@ -302,10 +302,11 @@ class EditingContext(GObject.Object, Loggable):
 
         if res and self.mode == GES.EditMode.EDIT_TRIM and self.with_video:
             if self.edge == GES.Edge.EDGE_START:
-                self.timeline.ui.app.gui.viewer.clipTrimPreview(self.focus, self.focus.props.in_point)
+                self.timeline.ui.app.gui.editor.viewer.clipTrimPreview(
+                    self.focus, self.focus.props.in_point)
             elif self.edge == GES.Edge.EDGE_END:
-                self.timeline.ui.app.gui.viewer.clipTrimPreview(self.focus,
-                                                                self.focus.props.duration + 
self.focus.props.in_point)
+                self.timeline.ui.app.gui.editor.viewer.clipTrimPreview(
+                    self.focus, self.focus.props.duration + self.focus.props.in_point)
 
 
 # -------------------------- Interfaces ----------------------------------------#
diff --git a/pitivi/utils/ui.py b/pitivi/utils/ui.py
index 1461b489..857d49c3 100644
--- a/pitivi/utils/ui.py
+++ b/pitivi/utils/ui.py
@@ -95,6 +95,32 @@ NORMAL_FONT = _get_font("font-name", "Cantarell")
 DOCUMENT_FONT = _get_font("document-font-name", "Sans")
 MONOSPACE_FONT = _get_font("monospace-font-name", "Monospace")
 
+GREETER_PERSPECTIVE_CSS = """
+    #recent_projects_listbox {
+        font-weight: bold;
+        border: 1px solid alpha(@borders, 0.6);
+    }
+
+    #recent_projects_listbox row {
+        padding: 10px 200px 10px 10px;
+        border-bottom: 1px solid alpha(@borders, 0.2);
+    }
+
+    #recent_projects_listbox row:last-child {
+        border-bottom-width: 0px;
+    }
+
+    #recent_projects_labelbox {
+        color: @insensitive_fg_color;
+        font-weight: bold;
+        padding-bottom: 6px;
+    }
+
+    #recent_projects_labelbox > label:backdrop {
+        color: @unfocused_insensitive_color;
+    }
+"""
+
 TIMELINE_CSS = """
     .AudioBackground {
         background-color: #496c21;
diff --git a/pitivi/utils/widgets.py b/pitivi/utils/widgets.py
index 57c4d5ea..fc04d1e2 100644
--- a/pitivi/utils/widgets.py
+++ b/pitivi/utils/widgets.py
@@ -1310,20 +1310,20 @@ class BaseTabs(Gtk.Notebook):
         return notebook
 
     def _hideSecondHpanedInMainWindow(self):
-        self.app.gui.mainhpaned.remove(self.app.gui.secondhpaned)
-        self.app.gui.secondhpaned.remove(self.app.gui.projecttabs)
-        self.app.gui.secondhpaned.remove(self.app.gui.propertiestabs)
-        self.app.gui.mainhpaned.pack1(self.app.gui.projecttabs, resize=True,
-                                      shrink=False)
+        self.app.gui.editor.mainhpaned.remove(self.app.gui.editor.secondhpaned)
+        self.app.gui.editor.secondhpaned.remove(self.app.gui.editor.projecttabs)
+        self.app.gui.editor.secondhpaned.remove(self.app.gui.editor.propertiestabs)
+        self.app.gui.editor.mainhpaned.pack1(self.app.gui.editor.projecttabs,
+                                             resize=True, shrink=False)
 
     def _showSecondHpanedInMainWindow(self):
-        self.app.gui.mainhpaned.remove(self.app.gui.projecttabs)
-        self.app.gui.secondhpaned.pack1(self.app.gui.projecttabs,
-                                        resize=True, shrink=False)
-        self.app.gui.secondhpaned.pack2(self.app.gui.propertiestabs,
-                                        resize=True, shrink=False)
-        self.app.gui.mainhpaned.pack1(self.app.gui.secondhpaned,
-                                      resize=True, shrink=False)
+        self.app.gui.editor.mainhpaned.remove(self.app.gui.editor.projecttabs)
+        self.app.gui.editor.secondhpaned.pack1(self.app.gui.editor.projecttabs,
+                                               resize=True, shrink=False)
+        self.app.gui.editor.secondhpaned.pack2(self.app.gui.editor.propertiestabs,
+                                               resize=True, shrink=False)
+        self.app.gui.editor.mainhpaned.pack1(self.app.gui.editor.secondhpaned,
+                                             resize=True, shrink=False)
 
 
 class ZoomBox(Gtk.Grid, Zoomable):
diff --git a/pitivi/viewer/overlay.py b/pitivi/viewer/overlay.py
index 43086d26..74fa5d2c 100644
--- a/pitivi/viewer/overlay.py
+++ b/pitivi/viewer/overlay.py
@@ -55,7 +55,7 @@ class Overlay(Gtk.DrawingArea, Loggable):
 
     def _select(self):
         self.stack.selected_overlay = self
-        self.stack.app.gui.timeline_ui.timeline.selection.setSelection([self._source], SELECT)
+        self.stack.app.gui.editor.timeline_ui.timeline.selection.setSelection([self._source], SELECT)
         if isinstance(self._source, GES.TitleSource):
             page = 2
         elif isinstance(self._source, GES.VideoUriSource):
@@ -63,7 +63,7 @@ class Overlay(Gtk.DrawingArea, Loggable):
         else:
             self.warning("Unknown clip type: %s", self._source)
             return
-        self.stack.app.gui.context_tabs.set_current_page(page)
+        self.stack.app.gui.editor.context_tabs.set_current_page(page)
 
     def __source_selected_changed_cb(self, unused_source, selected):
         if not selected and self._is_selected():
diff --git a/pitivi/viewer/viewer.py b/pitivi/viewer/viewer.py
index 84940424..9a14aee9 100644
--- a/pitivi/viewer/viewer.py
+++ b/pitivi/viewer/viewer.py
@@ -291,12 +291,13 @@ class ViewerContainer(Gtk.Box, Loggable):
     def _entryActivateCb(self, unused_entry):
         nanoseconds = self.timecode_entry.getWidgetValue()
         self.app.project_manager.current_project.pipeline.simple_seek(nanoseconds)
-        self.app.gui.timeline_ui.timeline.scrollToPlayhead(align=Gtk.Align.CENTER, when_not_in_view=True)
+        self.app.gui.editor.timeline_ui.timeline.scrollToPlayhead(
+            align=Gtk.Align.CENTER, when_not_in_view=True)
 
     def _entry_key_press_event_cb(self, widget, event):
         """Handles the key press events in the timecode_entry widget."""
         if event.keyval == Gdk.KEY_Escape:
-            self.app.gui.focusTimeline()
+            self.app.gui.editor.focusTimeline()
 
     # Active Timeline calllbacks
     def _durationChangedCb(self, unused_pipeline, duration):
@@ -307,30 +308,34 @@ class ViewerContainer(Gtk.Box, Loggable):
 
     def _playButtonCb(self, unused_button, unused_playing):
         self.app.project_manager.current_project.pipeline.togglePlayback()
-        self.app.gui.focusTimeline()
+        self.app.gui.editor.focusTimeline()
 
     def _goToStartCb(self, unused_button):
         self.app.project_manager.current_project.pipeline.simple_seek(0)
-        self.app.gui.focusTimeline()
-        self.app.gui.timeline_ui.timeline.scrollToPlayhead(align=Gtk.Align.START, when_not_in_view=True)
+        self.app.gui.editor.focusTimeline()
+        self.app.gui.editor.timeline_ui.timeline.scrollToPlayhead(
+            align=Gtk.Align.START, when_not_in_view=True)
 
     def _backCb(self, unused_button):
         # Seek backwards one second
         self.app.project_manager.current_project.pipeline.seekRelative(0 - Gst.SECOND)
-        self.app.gui.focusTimeline()
-        self.app.gui.timeline_ui.timeline.scrollToPlayhead(align=Gtk.Align.END, when_not_in_view=True)
+        self.app.gui.editor.focusTimeline()
+        self.app.gui.editor.timeline_ui.timeline.scrollToPlayhead(
+            align=Gtk.Align.END, when_not_in_view=True)
 
     def _forwardCb(self, unused_button):
         # Seek forward one second
         self.app.project_manager.current_project.pipeline.seekRelative(Gst.SECOND)
-        self.app.gui.focusTimeline()
-        self.app.gui.timeline_ui.timeline.scrollToPlayhead(align=Gtk.Align.START, when_not_in_view=True)
+        self.app.gui.editor.focusTimeline()
+        self.app.gui.editor.timeline_ui.timeline.scrollToPlayhead(
+            align=Gtk.Align.START, when_not_in_view=True)
 
     def _goToEndCb(self, unused_button):
         end = self.app.project_manager.current_project.pipeline.getDuration()
         self.app.project_manager.current_project.pipeline.simple_seek(end)
-        self.app.gui.focusTimeline()
-        self.app.gui.timeline_ui.timeline.scrollToPlayhead(align=Gtk.Align.CENTER, when_not_in_view=True)
+        self.app.gui.editor.focusTimeline()
+        self.app.gui.editor.timeline_ui.timeline.scrollToPlayhead(
+            align=Gtk.Align.CENTER, when_not_in_view=True)
 
     # Public methods for controlling playback
 
diff --git a/pre-commit.hook b/pre-commit.hook
index 43a14e1f..91e8961e 100755
--- a/pre-commit.hook
+++ b/pre-commit.hook
@@ -20,8 +20,8 @@ pitivi/dialogs/depsmanager.py
 pitivi/dialogs/filelisterrordialog.py
 pitivi/dialogs/prefs.py
 pitivi/dialogs/startupwizard.py
+pitivi/editorperspective.py
 pitivi/effects.py
-pitivi/mainwindow.py
 pitivi/mediafilespreviewer.py
 pitivi/medialibrary.py
 pitivi/preset.py
diff --git a/tests/test_mainwindow.py b/tests/test_editorperspective.py
similarity index 76%
rename from tests/test_mainwindow.py
rename to tests/test_editorperspective.py
index bebf9358..ca641a3f 100644
--- a/tests/test_mainwindow.py
+++ b/tests/test_editorperspective.py
@@ -16,54 +16,53 @@
 # License along with this program; if not, write to the
 # Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 # Boston, MA 02110-1301, USA.
-"""
-Tests for pitivi/mainwindow.py
-"""
+"""Tests for pitivi/editorperspective.py"""
 from unittest import mock
 
 from gi.repository import GES
 from gi.repository import Gtk
 
-from pitivi.mainwindow import MainWindow
+from pitivi.editorperspective import EditorPerspective
 from pitivi.project import ProjectManager
 from pitivi.utils.misc import disconnectAllByFunc
 from tests import common
 
 
-class TestMainWindow(common.TestCase):
-    """Tests for the MainWindow class."""
+class TestEditorPerspective(common.TestCase):
+    """Tests for the EditorPerspective class."""
 
+    # pylint: disable=protected-access
     def test_switch_context_tab(self):
         """Checks tab switches."""
         app = common.create_pitivi_mock()
-        mainwindow = MainWindow(app)
+        editorperspective = EditorPerspective(app)
+        editorperspective.setup_ui()
         for expected_tab, b_element in [
                 (2, GES.TitleClip()),
                 (0, GES.SourceClip()),
                 (1, GES.TransitionClip())]:
-            mainwindow.switchContextTab(b_element)
-            self.assertEqual(mainwindow.context_tabs.get_current_page(),
+            editorperspective.switchContextTab(b_element)
+            self.assertEqual(editorperspective.context_tabs.get_current_page(),
                              expected_tab,
                              b_element)
             # Make sure the tab does not change when using an invalid argument.
-            mainwindow.switchContextTab("invalid")
-            self.assertEqual(mainwindow.context_tabs.get_current_page(),
+            editorperspective.switchContextTab("invalid")
+            self.assertEqual(editorperspective.context_tabs.get_current_page(),
                              expected_tab)
 
-        mainwindow.destroy()
-
     def __loading_failure(self, has_proxy):
         mainloop = common.create_main_loop()
 
         app = common.create_pitivi_mock(lastProjectFolder="/tmp",
                                         edgeSnapDeadband=32)
         app.project_manager = ProjectManager(app)
-        mainwindow = MainWindow(app)
-        mainwindow.viewer = mock.MagicMock()
+        editorperspective = EditorPerspective(app)
+        editorperspective.setup_ui()
+        editorperspective.viewer = mock.MagicMock()
 
         def __pm_missing_uri_cb(project_manager, project, error, asset):
             nonlocal mainloop
-            nonlocal mainwindow
+            nonlocal editorperspective
             nonlocal self
             nonlocal app
             nonlocal has_proxy
@@ -81,7 +80,7 @@ class TestMainWindow(common.TestCase):
                 app.proxy_manager.checkProxyLoadingSucceeded =  \
                     mock.MagicMock(return_value=has_proxy)
 
-                mainwindow._projectManagerMissingUriCb(
+                editorperspective._projectManagerMissingUriCb(
                     project_manager, project, error, asset)
 
                 self.assertTrue(dialog.called)
@@ -90,19 +89,13 @@ class TestMainWindow(common.TestCase):
 
             # pylint: disable=protected-access
             app.project_manager.connect("missing-uri",
-                                        mainwindow._projectManagerMissingUriCb)
-            # pylint: disable=protected-access
-            app.project_manager.connect("new-project-failed",
-                                        mainwindow._projectManagerNewProjectFailedCb)
+                                        editorperspective._projectManagerMissingUriCb)
 
-            mainwindow.destroy()
             mainloop.quit()
 
         # pylint: disable=protected-access
         disconnectAllByFunc(app.project_manager,
-                            mainwindow._projectManagerMissingUriCb)
-        disconnectAllByFunc(app.project_manager,
-                            mainwindow._projectManagerNewProjectFailedCb)
+                            editorperspective._projectManagerMissingUriCb)
 
         app.project_manager.connect("missing-uri", __pm_missing_uri_cb)
 
diff --git a/tests/test_project.py b/tests/test_project.py
index 67485702..258bc856 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -85,25 +85,6 @@ class TestProjectManager(common.TestCase):
         signalUri, unused_message = args
         self.assertEqual(uri, signalUri, self.signals)
 
-    def testLoadProjectClosesCurrent(self):
-        """
-        Check that new-project-failed is emitted if we can't close the current
-        project instance.
-        """
-        state = {"tried-close": False}
-
-        def close():
-            state["tried-close"] = True
-            return False
-        self.manager.closeRunningProject = close
-
-        uri = "file:///Untitled.xptv"
-        self.manager.current_project = mock.Mock()
-        self.manager.loadProject(uri)
-
-        self.assertEqual(0, len(self.signals))
-        self.assertTrue(state["tried-close"], self.signals)
-
     def testLoadProject(self):
         self.manager.newBlankProject()
 
@@ -190,18 +171,6 @@ class TestProjectManager(common.TestCase):
 
         self.assertTrue(self.manager.current_project is None)
 
-    def testNewBlankProjectCantCloseCurrent(self):
-        def closing(manager, project):
-            return False
-
-        self.manager.current_project = mock.Mock()
-        self.manager.current_project.uri = "file:///ciao"
-        self.manager.connect("closing-project", closing)
-        self.assertFalse(self.manager.newBlankProject())
-        self.assertEqual(1, len(self.signals))
-        signal, args = self.signals[0]
-        self.assertEqual("closing-project", signal)
-
     def testNewBlankProject(self):
         self.assertTrue(self.manager.newBlankProject())
         self.assertEqual(3, len(self.signals))
@@ -388,6 +357,7 @@ class TestProjectLoading(common.TestCase):
 </project>
 </ges>""" % {"uri": uris[0], "proxy_uri": proxy_uri}
         app = common.create_pitivi(proxyingStrategy=ProxyingStrategy.ALL)
+        app.recent_manager.remove_item = mock.Mock(return_value=True)
         proxy_manager = app.proxy_manager
         project_manager = app.project_manager
         medialib = medialibrary.MediaLibraryWidget(app)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]