Пример #1
0
    def __init__(self, app):
        gtksettings = Gtk.Settings.get_default()
        gtksettings.set_property("gtk-application-prefer-dark-theme", True)
        theme = gtksettings.get_property("gtk-theme-name")
        os.environ["GTK_THEME"] = theme + ":dark"

        # Pulseaudio "role"
        # (http://0pointer.de/blog/projects/tagging-audio.htm)
        os.environ["PULSE_PROP_media.role"] = "production"
        os.environ["PULSE_PROP_application.icon_name"] = "pitivi"

        Gtk.IconTheme.get_default().append_search_path(get_pixmap_dir())

        Gtk.ApplicationWindow.__init__(self)
        Loggable.__init__(self)

        self.log("Creating main window")

        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)
Пример #2
0
    def __init__(self, app):
        # Pulseaudio "role"
        # (http://0pointer.de/blog/projects/tagging-audio.htm)
        os.environ["PULSE_PROP_media.role"] = "production"
        os.environ["PULSE_PROP_application.icon_name"] = "pitivi"

        Gtk.IconTheme.get_default().append_search_path(get_pixmap_dir())

        Gtk.ApplicationWindow.__init__(self)
        Loggable.__init__(self)

        self.log("Creating main window")

        self.app = app
        self.greeter = GreeterPerspective(app)
        self.editor = EditorPerspective(app)
        self.__placed = False
        self.__perspective = None
        self.__wanted_perspective = None

        self.app.settings.connect("useDarkThemeChanged",
                                  self.__use_dark_theme_changed_cb)
        self.update_use_dark_theme()

        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)
Пример #3
0
    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)
        editorperspective = EditorPerspective(app)
        editorperspective.setup_ui()
        editorperspective.viewer = mock.MagicMock()
        editorperspective.medialibrary._import_warning_infobar = mock.MagicMock(
        )
        editorperspective.clipconfig.effect_expander._infobar = mock.MagicMock(
        )

        def __pm_missing_uri_cb(project_manager, project, error, asset):
            nonlocal mainloop
            nonlocal editorperspective
            nonlocal self
            nonlocal app
            nonlocal has_proxy

            with mock.patch.object(MissingAssetDialog,
                                   "__new__") as constructor:
                failed_cb = mock.MagicMock()
                app.project_manager.connect("new-project-failed", failed_cb)

                dialog = constructor.return_value
                dialog.get_new_uri.return_value = None

                # Call the actual callback
                app.proxy_manager.checkProxyLoadingSucceeded =  \
                    mock.MagicMock(return_value=has_proxy)

                editorperspective._projectManagerMissingUriCb(
                    project_manager, project, error, asset)

                self.assertTrue(constructor.called)
                self.assertTrue(dialog.get_new_uri.called)
                self.assertEqual(failed_cb.called, not has_proxy)

            app.project_manager.connect(
                "missing-uri", editorperspective._projectManagerMissingUriCb)
            mainloop.quit()

        disconnectAllByFunc(app.project_manager,
                            editorperspective._projectManagerMissingUriCb)

        app.project_manager.connect("missing-uri", __pm_missing_uri_cb)

        with common.cloned_sample():
            asset_uri = common.get_sample_uri("missing.png")
            with common.created_project_file(asset_uri) as uri:
                app.project_manager.load_project(uri)

        mainloop.run()
Пример #4
0
    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)
        editorperspective = EditorPerspective(app)
        editorperspective.setup_ui()
        editorperspective.viewer = mock.MagicMock()
        editorperspective.medialibrary._import_warning_infobar = mock.MagicMock()
        editorperspective.clipconfig.effect_expander._infobar = mock.MagicMock()

        def __pm_missing_uri_cb(project_manager, project, error, asset):
            nonlocal mainloop
            nonlocal editorperspective
            nonlocal self
            nonlocal app
            nonlocal has_proxy

            with mock.patch.object(MissingAssetDialog, "__new__") as constructor:
                failed_cb = mock.MagicMock()
                app.project_manager.connect("new-project-failed", failed_cb)

                dialog = constructor.return_value
                dialog.get_new_uri.return_value = None

                # Call the actual callback
                app.proxy_manager.checkProxyLoadingSucceeded =  \
                    mock.MagicMock(return_value=has_proxy)

                editorperspective._projectManagerMissingUriCb(
                    project_manager, project, error, asset)

                self.assertTrue(constructor.called)
                self.assertTrue(dialog.get_new_uri.called)
                self.assertEqual(failed_cb.called, not has_proxy)

            app.project_manager.connect("missing-uri",
                                        editorperspective._projectManagerMissingUriCb)
            mainloop.quit()

        disconnectAllByFunc(app.project_manager,
                            editorperspective._projectManagerMissingUriCb)

        app.project_manager.connect("missing-uri", __pm_missing_uri_cb)

        with common.cloned_sample():
            asset_uri = common.get_sample_uri("missing.png")
            with common.created_project_file(asset_uri) as uri:
                app.project_manager.load_project(uri)

        mainloop.run()
Пример #5
0
    def __init__(self, app):
        gtksettings = Gtk.Settings.get_default()
        gtksettings.set_property("gtk-application-prefer-dark-theme", True)
        theme = gtksettings.get_property("gtk-theme-name")
        os.environ["GTK_THEME"] = theme + ":dark"

        # Pulseaudio "role"
        # (http://0pointer.de/blog/projects/tagging-audio.htm)
        os.environ["PULSE_PROP_media.role"] = "production"
        os.environ["PULSE_PROP_application.icon_name"] = "pitivi"

        Gtk.IconTheme.get_default().append_search_path(get_pixmap_dir())

        Gtk.ApplicationWindow.__init__(self)
        Loggable.__init__(self)

        self.log("Creating main window")

        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)
Пример #6
0
 def test_switch_context_tab(self):
     """Checks tab switches."""
     app = common.create_pitivi_mock()
     editorperspective = EditorPerspective(app)
     editorperspective.setup_ui()
     for expected_tab, b_element in [(2, GES.TitleClip()),
                                     (0, GES.SourceClip()),
                                     (1, GES.TransitionClip())]:
         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.
         editorperspective.switchContextTab("invalid")
         self.assertEqual(editorperspective.context_tabs.get_current_page(),
                          expected_tab)
Пример #7
0
 def test_switch_context_tab(self):
     """Checks tab switches."""
     app = common.create_pitivi_mock()
     editorperspective = EditorPerspective(app)
     editorperspective.setup_ui()
     for expected_tab, b_element in [
             (2, GES.TitleClip()),
             (0, GES.SourceClip()),
             (1, GES.TransitionClip())]:
         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.
         editorperspective.switchContextTab("invalid")
         self.assertEqual(editorperspective.context_tabs.get_current_page(),
                          expected_tab)
Пример #8
0
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.
    """
    def __init__(self, app):
        gtksettings = Gtk.Settings.get_default()
        gtksettings.set_property("gtk-application-prefer-dark-theme", True)
        theme = gtksettings.get_property("gtk-theme-name")
        os.environ["GTK_THEME"] = theme + ":dark"

        # Pulseaudio "role"
        # (http://0pointer.de/blog/projects/tagging-audio.htm)
        os.environ["PULSE_PROP_media.role"] = "production"
        os.environ["PULSE_PROP_application.icon_name"] = "pitivi"

        Gtk.IconTheme.get_default().append_search_path(get_pixmap_dir())

        Gtk.ApplicationWindow.__init__(self)
        Loggable.__init__(self)

        self.log("Creating main window")

        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.")

        self.set_icon_name("pitivi")
        self.__check_screen_constraints()
        self.__set_keyboard_shortcuts()

        self.greeter.setup_ui()
        self.editor.setup_ui()

        width = self.app.settings.mainWindowWidth
        height = self.app.settings.mainWindowHeight

        if height == -1 and width == -1:
            self.maximize()
        else:
            self.set_default_size(width, height)
            self.move(self.app.settings.mainWindowX,
                      self.app.settings.mainWindowY)

        self.connect("configure-event", self.__configure_cb)
        self.connect("delete-event", self.__delete_cb)

    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.
        """
        # This code works, but keep in mind get_preferred_size's output
        # 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, _ = 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)
        self.debug("Screen size is %sx%s", screen_width, screen_height)
        if min_size.width >= 0.9 * screen_width:
            self.medialibrary.activateCompactMode()
            self.viewer.activateCompactMode()
            min_size, _ = self.get_preferred_size()
            self.info("Minimum UI size has been reduced to %sx%s",
                      min_size.width, min_size.height)

    def __set_keyboard_shortcuts(self):
        self.app.shortcuts.register_group("win", _("Project"), position=20)

        self.help_action = Gio.SimpleAction.new("help", None)
        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.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")

        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")

    @staticmethod
    def __user_manual_cb(unused_action, unused_param):
        show_user_manual()

    def __about_cb(self, unused_action, unused_param):
        about_dialog = AboutDialog(self.app)
        about_dialog.show()

    def __menu_cb(self, unused_action, unused_param):
        self.__perspective.menu_button.set_active(
            not self.__perspective.menu_button.get_active())

    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.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.app.settings.mainWindowWidth = size.width
        self.app.settings.mainWindowHeight = size.height

    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(
        )

        if not self.app.shutdown():
            return True
        return False

    def __new_project_loading_cb(self, unused_project_manager, unused_project):
        self.show_perspective(self.editor)

    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,
                                   message_type=Gtk.MessageType.ERROR,
                                   buttons=Gtk.ButtonsType.OK,
                                   text=_('Unable to load project "%s"') %
                                   project_filename)
        dialog.set_property("secondary-use-markup", True)
        dialog.set_property("secondary-text", unquote(str(reason)))
        dialog.set_transient_for(self)
        dialog.run()
        dialog.destroy()
        self.show_perspective(self.greeter)

    def __project_closed_cb(self, unused_project_manager, unused_project):
        self.show_perspective(self.greeter)

    def show_perspective(self, perspective):
        """Displays the specified perspective."""
        if self.__perspective is perspective:
            return
        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()
Пример #9
0
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.
    """
    def __init__(self, app):
        # Pulseaudio "role"
        # (http://0pointer.de/blog/projects/tagging-audio.htm)
        os.environ["PULSE_PROP_media.role"] = "production"
        os.environ["PULSE_PROP_application.icon_name"] = "pitivi"

        Gtk.IconTheme.get_default().append_search_path(get_pixmap_dir())

        Gtk.ApplicationWindow.__init__(self)
        Loggable.__init__(self)

        self.log("Creating main window")

        self.app = app
        self.greeter = GreeterPerspective(app)
        self.editor = EditorPerspective(app)
        self.__placed = False
        self.__perspective = None
        self.__wanted_perspective = None

        self.app.settings.connect("useDarkThemeChanged",
                                  self.__use_dark_theme_changed_cb)
        self.update_use_dark_theme()

        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 update_use_dark_theme(self):
        gtksettings = Gtk.Settings.get_default()
        use_dark_theme = self.app.settings.useDarkTheme
        gtksettings.set_property("gtk-application-prefer-dark-theme",
                                 use_dark_theme)

    def __use_dark_theme_changed_cb(self, unused_settings):
        self.update_use_dark_theme()

    def setup_ui(self):
        """Sets up the various perspectives's UI."""
        self.log("Setting up the perspectives.")

        self.set_icon_name("pitivi")
        self._create_actions()

        self.greeter.setup_ui()
        self.editor.setup_ui()

        width = self.app.settings.mainWindowWidth
        height = self.app.settings.mainWindowHeight
        if height == -1 and width == -1:
            self.__placed = True
            self.maximize()
        else:
            # Wait until placing the window, to avoid the window manager
            # ignoring the call. See the documentation for Gtk.Window.move.
            # If you change this, pay attention opening `pitivi` works
            # a bit different than opening `pitivi file.xges`. For example
            # connecting to the "realize" signal instead of idle_add-ing
            # fails to restore the position when directly loading a project.
            GLib.idle_add(self.__initial_placement_cb,
                          self.app.settings.mainWindowX,
                          self.app.settings.mainWindowY, width, height)

        self.check_screen_constraints()

        self.connect("configure-event", self.__configure_cb)
        self.connect("delete-event", self.__delete_cb)

    def __initial_placement_cb(self, x, y, width, height):
        self.__placed = True
        self.resize(width, height)
        self.move(x, y)
        if self.__wanted_perspective:
            self.show_perspective(self.__wanted_perspective)
            self.__wanted_perspective = None

    def check_screen_constraints(self):
        """Shrinks some widgets to fit better on smaller screen resolutions."""
        if self._small_screen():
            self.greeter.activate_compact_mode()
            self.editor.activate_compact_mode()
            min_size, _ = self.get_preferred_size()
            self.info("Minimum UI size has been reduced to %sx%s",
                      min_size.width, min_size.height)

    def _small_screen(self):
        # This code works, but keep in mind get_preferred_size's output
        # 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, _ = 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)
        self.debug("Screen size is %sx%s", screen_width, screen_height)
        return min_size.width >= 0.9 * screen_width

    def _create_actions(self):
        self.app.shortcuts.register_group("win", _("Project"), position=20)

        # pylint: disable=attribute-defined-outside-init
        self.help_action = Gio.SimpleAction.new("help", None)
        self.help_action.connect("activate", self.__user_manual_cb)
        self.add_action(self.help_action)
        self.app.shortcuts.add("win.help", ["F1"],
                               self.help_action,
                               _("Help"),
                               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"],
                               self.about_action,
                               _("About"),
                               group="app")

        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"],
                               self.main_menu_action,
                               _("Show the menu button content"),
                               group="app")

        self.preferences_action = Gio.SimpleAction.new("preferences", None)
        self.preferences_action.connect("activate", self.__preferences_cb)
        self.add_action(self.preferences_action)
        self.app.shortcuts.add("win.preferences", ["<Primary>comma"],
                               self.preferences_action,
                               _("Preferences"),
                               group="app")

    @staticmethod
    def __user_manual_cb(unused_action, unused_param):
        show_user_manual()

    def __about_cb(self, unused_action, unused_param):
        about_dialog = AboutDialog(self.app)
        about_dialog.show()

    def __menu_cb(self, unused_action, unused_param):
        active = not self.__perspective.menu_button.get_active()
        self.__perspective.menu_button.set_active(active)

    def __preferences_cb(self, unused_action, unused_param):
        PreferencesDialog(self.app).run()

    def __configure_cb(self, unused_widget, unused_event):
        """Saves the main window position and size."""
        position = self.get_position()
        self.app.settings.mainWindowX = position.root_x
        self.app.settings.mainWindowY = position.root_y

        size = self.get_size()
        self.app.settings.mainWindowWidth = size.width
        self.app.settings.mainWindowHeight = size.height

    def __delete_cb(self, unused_widget, unused_event):
        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(
        )

        if not self.app.shutdown():
            return True
        return False

    def __new_project_loading_cb(self, unused_project_manager, unused_project):
        self.show_perspective(self.editor)

    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,
                                   message_type=Gtk.MessageType.ERROR,
                                   buttons=Gtk.ButtonsType.OK,
                                   text=_('Unable to load project "%s"') %
                                   project_filename)
        dialog.set_property("secondary-use-markup", True)
        dialog.set_property("secondary-text", unquote(str(reason)))
        dialog.set_transient_for(self)
        dialog.run()
        dialog.destroy()
        self.show_perspective(self.greeter)

    def __project_closed_cb(self, unused_project_manager, unused_project):
        self.show_perspective(self.greeter)

    def show_perspective(self, perspective):
        """Displays the specified perspective."""
        if not self.__placed:
            self.log(
                "Postponing the perspective setting until the window is placed"
            )
            self.__wanted_perspective = perspective
            return

        if self.__perspective is perspective:
            return

        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)
        # The window must be shown only after setting the headerbar with
        # set_titlebar. Otherwise we get a warning things can go wrong.
        self.show()
        self.add(perspective.toplevel_widget)
        perspective.refresh()
Пример #10
0
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.
    """

    def __init__(self, app):
        gtksettings = Gtk.Settings.get_default()
        gtksettings.set_property("gtk-application-prefer-dark-theme", True)
        theme = gtksettings.get_property("gtk-theme-name")
        os.environ["GTK_THEME"] = theme + ":dark"

        # Pulseaudio "role"
        # (http://0pointer.de/blog/projects/tagging-audio.htm)
        os.environ["PULSE_PROP_media.role"] = "production"
        os.environ["PULSE_PROP_application.icon_name"] = "pitivi"

        Gtk.IconTheme.get_default().append_search_path(get_pixmap_dir())

        Gtk.ApplicationWindow.__init__(self)
        Loggable.__init__(self)

        self.log("Creating main window")

        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.")

        self.set_icon_name("pitivi")
        self.__check_screen_constraints()
        self.__set_keyboard_shortcuts()

        self.greeter.setup_ui()
        self.editor.setup_ui()

        width = self.app.settings.mainWindowWidth
        height = self.app.settings.mainWindowHeight

        if height == -1 and width == -1:
            self.maximize()
        else:
            self.set_default_size(width, height)
            self.move(self.app.settings.mainWindowX, self.app.settings.mainWindowY)

        self.connect("configure-event", self.__configure_cb)
        self.connect("delete-event", self.__delete_cb)

    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.
        """
        # This code works, but keep in mind get_preferred_size's output
        # 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, _ = 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)
        self.debug("Screen size is %sx%s", screen_width, screen_height)
        if min_size.width >= 0.9 * screen_width:
            self.medialibrary.activateCompactMode()
            self.viewer.activateCompactMode()
            min_size, _ = self.get_preferred_size()
            self.info("Minimum UI size has been reduced to %sx%s",
                      min_size.width, min_size.height)

    def __set_keyboard_shortcuts(self):
        self.app.shortcuts.register_group("win", _("Project"), position=20)

        self.help_action = Gio.SimpleAction.new("help", None)
        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.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")

        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")

        self.preferences_action = Gio.SimpleAction.new("preferences", None)
        self.preferences_action.connect("activate", self.__preferences_cb)
        self.add_action(self.preferences_action)
        self.app.shortcuts.add("win.preferences", ["<Primary>comma"],
                               _("Preferences"), group="app")

    @staticmethod
    def __user_manual_cb(unused_action, unused_param):
        show_user_manual()

    def __about_cb(self, unused_action, unused_param):
        about_dialog = AboutDialog(self.app)
        about_dialog.show()

    def __menu_cb(self, unused_action, unused_param):
        active = not self.__perspective.menu_button.get_active()
        self.__perspective.menu_button.set_active(active)

    def __preferences_cb(self, unused_action, unused_param):
        PreferencesDialog(self.app).run()

    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.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.app.settings.mainWindowWidth = size.width
        self.app.settings.mainWindowHeight = size.height

    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()

        if not self.app.shutdown():
            return True
        return False

    def __new_project_loading_cb(self, unused_project_manager, unused_project):
        self.show_perspective(self.editor)

    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,
                                   message_type=Gtk.MessageType.ERROR,
                                   buttons=Gtk.ButtonsType.OK,
                                   text=_('Unable to load project "%s"') % project_filename)
        dialog.set_property("secondary-use-markup", True)
        dialog.set_property("secondary-text", unquote(str(reason)))
        dialog.set_transient_for(self)
        dialog.run()
        dialog.destroy()
        self.show_perspective(self.greeter)

    def __project_closed_cb(self, unused_project_manager, unused_project):
        self.show_perspective(self.greeter)

    def show_perspective(self, perspective):
        """Displays the specified perspective."""
        if self.__perspective is perspective:
            return
        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()