[pitivi] ui: welcome window integration
- From: Alexandru Băluț <alexbalut src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] ui: welcome window integration
- Date: Tue, 19 Jun 2018 06:21:59 +0000 (UTC)
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]