class TestProjectManager(TestCase): def setUp(self): self.manager = ProjectManager(None) self.listener = ProjectManagerListener(self.manager) self.signals = self.listener.signals def testLoadProjectFailedUnknownFormat(self): """ Check that new-project-failed is emitted when we don't have a suitable formatter. """ uri = "file:///Untitled.meh" self.manager.loadProject(uri) # loading name, args = self.signals[0] self.assertEqual(uri, args[0], self.signals) # failed name, args = self.signals[1] self.assertEqual("new-project-failed", name) signalUri, unused_message = args self.assertEqual(uri, signalUri, self.signals) def testLoadProjectClosesCurrent(self): """ Check that new-project-failed is emited 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 = MockProject() self.manager.loadProject(uri) self.assertEqual(0, len(self.signals)) self.assertTrue(state["tried-close"], self.signals) def testLoadProject(self): self.manager.newBlankProject() name, args = self.signals[0] self.assertEqual("new-project-loading", name, self.signals) name, args = self.signals[1] self.assertEqual("new-project-created", name, self.signals) name, args = self.signals[2] self.assertEqual("new-project-loaded", name, self.signals) def testMissingUriForwarded(self): def quit(mainloop): mainloop.quit() def missingUriCb(self, project, error, clip_asset, mainloop, result): print(project, error, clip_asset, mainloop, result) result[0] = True mainloop.quit() self.mainloop = GLib.MainLoop() result = [False] self.manager.connect("missing-uri", missingUriCb, self.mainloop, result) # Load a project with a missing asset. unused, xges_path = tempfile.mkstemp() with open(xges_path, "w") as xges: xges.write("""<ges version='0.1'> <project> <ressources> <asset id='file:///icantpossiblyexist.png' extractable-type-name='GESUriClip' /> </ressources> <timeline> <track caps='video/x-raw' track-type='4' track-id='0' /> <layer priority='0'> <clip id='0' asset-id='file:///icantpossiblyexist.png' type-name='GESUriClip' layer-priority='0' track-types='4' start='0' duration='2590000000' inpoint='0' rate='0' /> </layer> </timeline> </project> </ges>""") uri = "file://%s" % xges_path try: self.assertTrue(self.manager.loadProject(uri)) GLib.timeout_add_seconds(5, quit, self.mainloop) self.mainloop.run() self.assertTrue(result[0], "missing not missing") finally: os.remove(xges_path) def testCloseRunningProjectNoProject(self): self.assertTrue(self.manager.closeRunningProject()) self.assertFalse(self.signals) def testCloseRunningProjectRefuseFromSignal(self): def closing(manager, project): return False self.manager.current_project = MockProject() self.manager.current_project.uri = "file:///ciao" self.manager.connect("closing-project", closing) self.assertFalse(self.manager.closeRunningProject()) self.assertEqual(1, len(self.signals)) name, args = self.signals[0] self.assertEqual("closing-project", name) project = args[0] self.assertTrue(project is self.manager.current_project) def testCloseRunningProject(self): current = self.manager.current_project = MockProject() self.assertTrue(self.manager.closeRunningProject()) self.assertEqual(2, len(self.signals)) name, args = self.signals[0] self.assertEqual("closing-project", name) project = args[0] self.assertTrue(project is current) name, args = self.signals[1] self.assertEqual("project-closed", name) project = args[0] self.assertTrue(project is current) self.assertTrue(self.manager.current_project is None) def testNewBlankProjectCantCloseCurrent(self): def closing(manager, project): return False self.manager.current_project = MockProject() 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)) name, args = self.signals[0] self.assertEqual("new-project-loading", name) uri = args[0] self.assertTrue(uri is None) name, args = self.signals[1] self.assertEqual("new-project-created", name) project = args[0] self.assertEqual(uri, project.uri) name, args = self.signals[2] self.assertEqual("new-project-loaded", name) project = args[0] self.assertTrue(project is self.manager.current_project) def testSaveProject(self): self.assertTrue(self.manager.newBlankProject()) unused, path = tempfile.mkstemp(suffix=".xges") unused, path2 = tempfile.mkstemp(suffix=".xges") try: uri = "file://" + os.path.abspath(path) uri2 = "file://" + os.path.abspath(path2) # Save the project. self.assertTrue(self.manager.saveProject(uri=uri, backup=False)) self.assertTrue(uri_is_reachable(uri)) # Wait a bit. time.sleep(0.1) # Save the project at a new location. self.assertTrue(self.manager.saveProject(uri2, backup=False)) self.assertTrue(uri_is_reachable(uri2)) # Make sure the old path and the new path have different mtimes. mtime = os.path.getmtime(path) mtime2 = os.path.getmtime(path2) self.assertLess(mtime, mtime2) # Wait a bit more. time.sleep(0.1) # Save project again under the new path (by omitting uri arg) self.assertTrue(self.manager.saveProject(backup=False)) # regression test for bug 594396 # make sure we didn't save to the old URI self.assertEqual(mtime, os.path.getmtime(path)) # make sure we did save to the new URI self.assertLess(mtime2, os.path.getmtime(path2)) finally: os.remove(path) os.remove(path2) def testMakeBackupUri(self): uri = "file:///tmp/x.xges" self.assertEqual(uri + "~", self.manager._makeBackupURI(uri)) def testBackupProject(self): self.manager.newBlankProject() # Assign an uri to the project where it's saved by default. unused, xges_path = tempfile.mkstemp(suffix=".xges") uri = "file://" + os.path.abspath(xges_path) self.manager.current_project.uri = uri # This is where the automatic backup file is saved. backup_uri = self.manager._makeBackupURI(uri) # Save the backup self.assertTrue( self.manager.saveProject(self.manager.current_project, backup=True)) self.assertTrue(uri_is_reachable(backup_uri)) self.manager.closeRunningProject() self.assertFalse(uri_is_reachable(backup_uri), "Backup file not deleted when project closed")
class TestProjectManager(TestCase): def setUp(self): self.manager = ProjectManager(None) self.listener = ProjectManagerListener(self.manager) self.signals = self.listener.signals def testLoadProjectFailedUnknownFormat(self): """ Check that new-project-failed is emitted when we don't have a suitable formatter. """ uri = "file:///Untitled.meh" self.manager.loadProject(uri) # loading name, args = self.signals[0] self.assertEqual(uri, args[0], self.signals) # failed name, args = self.signals[1] self.assertEqual("new-project-failed", name) signalUri, unused_message = args self.assertEqual(uri, signalUri, self.signals) def testLoadProjectClosesCurrent(self): """ Check that new-project-failed is emited 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 = MockProject() self.manager.loadProject(uri) self.assertEqual(0, len(self.signals)) self.assertTrue(state["tried-close"], self.signals) def testLoadProject(self): self.manager.newBlankProject() name, args = self.signals[0] self.assertEqual("new-project-loading", name, self.signals) name, args = self.signals[1] self.assertEqual("new-project-created", name, self.signals) name, args = self.signals[2] self.assertEqual("new-project-loaded", name, self.signals) def testMissingUriForwarded(self): def quit(mainloop): mainloop.quit() def missingUriCb(self, project, error, clip_asset, mainloop, result): print(project, error, clip_asset, mainloop, result) result[0] = True mainloop.quit() self.mainloop = GLib.MainLoop() result = [False] self.manager.connect( "missing-uri", missingUriCb, self.mainloop, result) # Load a project with a missing asset. unused, xges_path = tempfile.mkstemp() with open(xges_path, "w") as xges: xges.write("""<ges version='0.1'> <project> <ressources> <asset id='file:///icantpossiblyexist.png' extractable-type-name='GESUriClip' /> </ressources> <timeline> <track caps='video/x-raw' track-type='4' track-id='0' /> <layer priority='0'> <clip id='0' asset-id='file:///icantpossiblyexist.png' type-name='GESUriClip' layer-priority='0' track-types='4' start='0' duration='2590000000' inpoint='0' rate='0' /> </layer> </timeline> </project> </ges>""") uri = "file://%s" % xges_path try: self.assertTrue(self.manager.loadProject(uri)) GLib.timeout_add_seconds(5, quit, self.mainloop) self.mainloop.run() self.assertTrue(result[0], "missing not missing") finally: os.remove(xges_path) def testCloseRunningProjectNoProject(self): self.assertTrue(self.manager.closeRunningProject()) self.assertFalse(self.signals) def testCloseRunningProjectRefuseFromSignal(self): def closing(manager, project): return False self.manager.current_project = MockProject() self.manager.current_project.uri = "file:///ciao" self.manager.connect("closing-project", closing) self.assertFalse(self.manager.closeRunningProject()) self.assertEqual(1, len(self.signals)) name, args = self.signals[0] self.assertEqual("closing-project", name) project = args[0] self.assertTrue(project is self.manager.current_project) def testCloseRunningProject(self): current = self.manager.current_project = MockProject() self.assertTrue(self.manager.closeRunningProject()) self.assertEqual(2, len(self.signals)) name, args = self.signals[0] self.assertEqual("closing-project", name) project = args[0] self.assertTrue(project is current) name, args = self.signals[1] self.assertEqual("project-closed", name) project = args[0] self.assertTrue(project is current) self.assertTrue(self.manager.current_project is None) def testNewBlankProjectCantCloseCurrent(self): def closing(manager, project): return False self.manager.current_project = MockProject() 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)) name, args = self.signals[0] self.assertEqual("new-project-loading", name) uri = args[0] self.assertTrue(uri is None) name, args = self.signals[1] self.assertEqual("new-project-created", name) project = args[0] self.assertEqual(uri, project.uri) name, args = self.signals[2] self.assertEqual("new-project-loaded", name) project = args[0] self.assertTrue(project is self.manager.current_project) def testSaveProject(self): self.assertTrue(self.manager.newBlankProject()) unused, path = tempfile.mkstemp(suffix=".xges") unused, path2 = tempfile.mkstemp(suffix=".xges") try: uri = "file://" + os.path.abspath(path) uri2 = "file://" + os.path.abspath(path2) # Save the project. self.assertTrue(self.manager.saveProject(uri=uri, backup=False)) self.assertTrue(uri_is_reachable(uri)) # Wait a bit. time.sleep(0.1) # Save the project at a new location. self.assertTrue(self.manager.saveProject(uri2, backup=False)) self.assertTrue(uri_is_reachable(uri2)) # Make sure the old path and the new path have different mtimes. mtime = os.path.getmtime(path) mtime2 = os.path.getmtime(path2) self.assertLess(mtime, mtime2) # Wait a bit more. time.sleep(0.1) # Save project again under the new path (by omitting uri arg) self.assertTrue(self.manager.saveProject(backup=False)) # regression test for bug 594396 # make sure we didn't save to the old URI self.assertEqual(mtime, os.path.getmtime(path)) # make sure we did save to the new URI self.assertLess(mtime2, os.path.getmtime(path2)) finally: os.remove(path) os.remove(path2) def testMakeBackupUri(self): uri = "file:///tmp/x.xges" self.assertEqual(uri + "~", self.manager._makeBackupURI(uri)) def testBackupProject(self): self.manager.newBlankProject() # Assign an uri to the project where it's saved by default. unused, xges_path = tempfile.mkstemp(suffix=".xges") uri = "file://" + os.path.abspath(xges_path) self.manager.current_project.uri = uri # This is where the automatic backup file is saved. backup_uri = self.manager._makeBackupURI(uri) # Save the backup self.assertTrue(self.manager.saveProject( self.manager.current_project, backup=True)) self.assertTrue(uri_is_reachable(backup_uri)) self.manager.closeRunningProject() self.assertFalse(uri_is_reachable(backup_uri), "Backup file not deleted when project closed")
class TestProjectManager(common.TestCase): def setUp(self): super(TestProjectManager, self).setUp() self.setupApp() def setupApp(self, app=None): if not app: app = mock.MagicMock() self.manager = ProjectManager(app) self.listener = ProjectManagerListener(self.manager) self.signals = self.listener.signals def testLoadProjectFailedUnknownFormat(self): """Checks new-project-failed is emitted for unsuitable formatters.""" uri = "file:///Untitled.meh" self.manager.load_project(uri) # loading name, args = self.signals[0] self.assertEqual(uri, args[0].get_uri(), self.signals) # failed name, args = self.signals[1] self.assertEqual("new-project-failed", name) signalUri, unused_message = args self.assertEqual(uri, signalUri, self.signals) def testLoadProject(self): self.manager.new_blank_project() name, args = self.signals[0] self.assertEqual("new-project-loading", name, self.signals) name, args = self.signals[1] self.assertEqual("new-project-created", name, self.signals) name, args = self.signals[2] self.assertEqual("new-project-loaded", name, self.signals) def testMissingUriForwarded(self): self.setupApp(app=common.create_pitivi_mock()) mainloop = common.create_main_loop() def missingUriCb(self, project, error, clip_asset, result): result[0] = True mainloop.quit() result = [False] self.manager.connect("missing-uri", missingUriCb, result) with common.cloned_sample(): asset_uri = common.get_sample_uri("missing.png") with common.created_project_file(asset_uri) as uri: self.assertIsNotNone(self.manager.load_project(uri)) mainloop.run() self.assertTrue(result[0], "missing-uri has not been emitted") def testLoaded(self): mainloop = common.create_main_loop() def new_project_loaded_cb(project_manager, project): mainloop.quit() self.manager.connect("new-project-loaded", new_project_loaded_cb) with common.cloned_sample("flat_colour1_640x480.png"): asset_uri = common.get_sample_uri("flat_colour1_640x480.png") with common.created_project_file(asset_uri=asset_uri) as uri: project = self.manager.load_project(uri) self.assertIsNotNone(project) mainloop.run() self.assertFalse(project.at_least_one_asset_missing) self.assertTrue(project.loaded) self.assertFalse(project.hasUnsavedModifications()) def testCloseRunningProjectNoProject(self): self.assertTrue(self.manager.closeRunningProject()) self.assertFalse(self.signals) def testCloseRunningProjectRefuseFromSignal(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.closeRunningProject()) self.assertEqual(1, len(self.signals)) name, args = self.signals[0] self.assertEqual("closing-project", name) project = args[0] self.assertTrue(project is self.manager.current_project) def testCloseRunningProject(self): current = mock.Mock() current.uri = None self.manager.current_project = current self.assertTrue(self.manager.closeRunningProject()) self.assertEqual(2, len(self.signals)) name, args = self.signals[0] self.assertEqual("closing-project", name) project = args[0] self.assertTrue(project is current) name, args = self.signals[1] self.assertEqual("project-closed", name) project = args[0] self.assertTrue(project is current) self.assertTrue(self.manager.current_project is None) def testNewBlankProject(self): self.assertIsNotNone(self.manager.new_blank_project()) self.assertEqual(3, len(self.signals)) name, args = self.signals[0] self.assertEqual("new-project-loading", name) project = args[0] self.assertTrue(project.get_uri() is None) name, args = self.signals[1] self.assertEqual("new-project-created", name) project = args[0] self.assertEqual(project.get_uri(), project.uri) name, args = self.signals[2] self.assertEqual("new-project-loaded", name) project = args[0] self.assertTrue(project is self.manager.current_project) def testSaveProject(self): self.manager.new_blank_project() unused, path = tempfile.mkstemp(suffix=".xges") unused, path2 = tempfile.mkstemp(suffix=".xges") try: uri = "file://" + os.path.abspath(path) uri2 = "file://" + os.path.abspath(path2) # Save the project. self.assertTrue(self.manager.saveProject(uri=uri, backup=False)) self.assertTrue(os.path.isfile(path)) # Wait a bit. time.sleep(0.1) # Save the project at a new location. self.assertTrue(self.manager.saveProject(uri2, backup=False)) self.assertTrue(os.path.isfile(path2)) # Make sure the old path and the new path have different mtimes. mtime = os.path.getmtime(path) mtime2 = os.path.getmtime(path2) self.assertLess(mtime, mtime2) # Wait a bit more. time.sleep(0.1) # Save project again under the new path (by omitting uri arg) self.assertTrue(self.manager.saveProject(backup=False)) # regression test for bug 594396 # make sure we didn't save to the old URI self.assertEqual(mtime, os.path.getmtime(path)) # make sure we did save to the new URI self.assertLess(mtime2, os.path.getmtime(path2)) finally: os.remove(path) os.remove(path2) def testMakeBackupUri(self): uri = "file:///tmp/x.xges" self.assertEqual(uri + "~", self.manager._makeBackupURI(uri)) def testBackupProject(self): self.manager.new_blank_project() # Assign an uri to the project where it's saved by default. unused, xges_path = tempfile.mkstemp(suffix=".xges") uri = "file://" + os.path.abspath(xges_path) self.manager.current_project.uri = uri # This is where the automatic backup file is saved. backup_uri = self.manager._makeBackupURI(uri) # Save the backup self.assertTrue( self.manager.saveProject(self.manager.current_project, backup=True)) self.assertTrue(os.path.isfile(path_from_uri(backup_uri))) self.manager.closeRunningProject() self.assertFalse(os.path.isfile(path_from_uri(backup_uri)), "Backup file not deleted when project closed")
class TestProjectManager(common.TestCase): def setUp(self): super(TestProjectManager, self).setUp() self.setupApp() def setupApp(self, app=None): if not app: app = mock.MagicMock() self.manager = ProjectManager(app) self.listener = ProjectManagerListener(self.manager) self.signals = self.listener.signals def testLoadProjectFailedUnknownFormat(self): """Checks new-project-failed is emitted for unsuitable formatters.""" uri = "file:///Untitled.meh" self.manager.load_project(uri) # loading name, args = self.signals[0] self.assertEqual(uri, args[0].get_uri(), self.signals) # failed name, args = self.signals[1] self.assertEqual("new-project-failed", name) signalUri, unused_message = args self.assertEqual(uri, signalUri, self.signals) def testLoadProject(self): self.manager.new_blank_project() name, args = self.signals[0] self.assertEqual("new-project-loading", name, self.signals) name, args = self.signals[1] self.assertEqual("new-project-created", name, self.signals) name, args = self.signals[2] self.assertEqual("new-project-loaded", name, self.signals) def testMissingUriForwarded(self): self.setupApp(app=common.create_pitivi_mock()) mainloop = common.create_main_loop() def missingUriCb(self, project, error, clip_asset, result): result[0] = True mainloop.quit() result = [False] self.manager.connect("missing-uri", missingUriCb, result) with common.cloned_sample(): asset_uri = common.get_sample_uri("missing.png") with common.created_project_file(asset_uri) as uri: self.assertIsNotNone(self.manager.load_project(uri)) mainloop.run() self.assertTrue(result[0], "missing-uri has not been emitted") def testLoaded(self): mainloop = common.create_main_loop() def new_project_loaded_cb(project_manager, project): mainloop.quit() self.manager.connect("new-project-loaded", new_project_loaded_cb) with common.cloned_sample("flat_colour1_640x480.png"): asset_uri = common.get_sample_uri("flat_colour1_640x480.png") with common.created_project_file(asset_uri=asset_uri) as uri: project = self.manager.load_project(uri) self.assertIsNotNone(project) mainloop.run() self.assertFalse(project.at_least_one_asset_missing) self.assertTrue(project.loaded) self.assertFalse(project.hasUnsavedModifications()) def testCloseRunningProjectNoProject(self): self.assertTrue(self.manager.closeRunningProject()) self.assertFalse(self.signals) def testCloseRunningProjectRefuseFromSignal(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.closeRunningProject()) self.assertEqual(1, len(self.signals)) name, args = self.signals[0] self.assertEqual("closing-project", name) project = args[0] self.assertTrue(project is self.manager.current_project) def testCloseRunningProject(self): current = mock.Mock() current.uri = None self.manager.current_project = current self.assertTrue(self.manager.closeRunningProject()) self.assertEqual(2, len(self.signals)) name, args = self.signals[0] self.assertEqual("closing-project", name) project = args[0] self.assertTrue(project is current) name, args = self.signals[1] self.assertEqual("project-closed", name) project = args[0] self.assertTrue(project is current) self.assertTrue(self.manager.current_project is None) def testNewBlankProject(self): self.assertIsNotNone(self.manager.new_blank_project()) self.assertEqual(3, len(self.signals)) name, args = self.signals[0] self.assertEqual("new-project-loading", name) project = args[0] self.assertTrue(project.get_uri() is None) name, args = self.signals[1] self.assertEqual("new-project-created", name) project = args[0] self.assertEqual(project.get_uri(), project.uri) name, args = self.signals[2] self.assertEqual("new-project-loaded", name) project = args[0] self.assertTrue(project is self.manager.current_project) def testSaveProject(self): self.manager.new_blank_project() unused, path = tempfile.mkstemp(suffix=".xges") unused, path2 = tempfile.mkstemp(suffix=".xges") try: uri = "file://" + os.path.abspath(path) uri2 = "file://" + os.path.abspath(path2) # Save the project. self.assertTrue(self.manager.saveProject(uri=uri, backup=False)) self.assertTrue(os.path.isfile(path)) # Wait a bit. time.sleep(0.1) # Save the project at a new location. self.assertTrue(self.manager.saveProject(uri2, backup=False)) self.assertTrue(os.path.isfile(path2)) # Make sure the old path and the new path have different mtimes. mtime = os.path.getmtime(path) mtime2 = os.path.getmtime(path2) self.assertLess(mtime, mtime2) # Wait a bit more. time.sleep(0.1) # Save project again under the new path (by omitting uri arg) self.assertTrue(self.manager.saveProject(backup=False)) # regression test for bug 594396 # make sure we didn't save to the old URI self.assertEqual(mtime, os.path.getmtime(path)) # make sure we did save to the new URI self.assertLess(mtime2, os.path.getmtime(path2)) finally: os.remove(path) os.remove(path2) def testMakeBackupUri(self): uri = "file:///tmp/x.xges" self.assertEqual(uri + "~", self.manager._makeBackupURI(uri)) def testBackupProject(self): self.manager.new_blank_project() # Assign an uri to the project where it's saved by default. unused, xges_path = tempfile.mkstemp(suffix=".xges") uri = "file://" + os.path.abspath(xges_path) self.manager.current_project.uri = uri # This is where the automatic backup file is saved. backup_uri = self.manager._makeBackupURI(uri) # Save the backup self.assertTrue(self.manager.saveProject( self.manager.current_project, backup=True)) self.assertTrue(os.path.isfile(path_from_uri(backup_uri))) self.manager.closeRunningProject() self.assertFalse(os.path.isfile(path_from_uri(backup_uri)), "Backup file not deleted when project closed")
class Pitivi(Gtk.Application, Loggable): """Hello world. Attributes: 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. """ __gsignals__ = { "version-info-received": (GObject.SignalFlags.RUN_LAST, None, (object, )) } def __init__(self): Gtk.Application.__init__(self, application_id="org.pitivi.Pitivi", flags=Gio.ApplicationFlags.NON_UNIQUE | Gio.ApplicationFlags.HANDLES_OPEN) Loggable.__init__(self) self.settings = None self.threads = None self.effects = None self.system = None self.project_manager = ProjectManager(self) self.action_log = None self.project_observer = None self._last_action_time = Gst.util_get_timestamp() self.gui = None self.recent_manager = Gtk.RecentManager.get_default() self.__inhibit_cookies = {} self._version_information = {} self._scenario_file = None self._first_action = True Zoomable.app = self self.shortcuts = ShortcutsManager(self) def write_action(self, action, **kwargs): if self._scenario_file is None: return if self._first_action: self._scenario_file.write( "description, seek=true, handles-states=true\n") self._first_action = False now = Gst.util_get_timestamp() if now - self._last_action_time > 0.05 * Gst.SECOND: # We need to make sure that the waiting time was more than 50 ms. st = Gst.Structure.new_empty("wait") st["duration"] = float((now - self._last_action_time) / Gst.SECOND) self._scenario_file.write(st.to_string()) self._scenario_file.write("\n") self._last_action_time = now if not isinstance(action, Gst.Structure): structure = Gst.Structure.new_empty(action) for key, value in kwargs.items(): key = key.replace("_", "-") structure[key] = value action = structure self._scenario_file.write(action.to_string()) self._scenario_file.write("\n") self._scenario_file.flush() def do_startup(self): Gtk.Application.do_startup(self) # Init logging as early as possible so we can log startup code enable_color = os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') not in ('', '1') # Let's show a human-readable Pitivi debug output by default, and only # show a crazy unreadable mess when surrounded by gst debug statements. enable_crack_output = "GST_DEBUG" in os.environ loggable.init('PITIVI_DEBUG', enable_color, enable_crack_output) self.info('starting up') self._setup() self._check_version() def _setup(self): # pylint: disable=attribute-defined-outside-init self.settings = GlobalSettings() self.threads = ThreadMaster() self.effects = EffectsManager() self.proxy_manager = ProxyManager(self) self.system = get_system() self.plugin_manager = PluginManager(self) self.project_manager.connect("new-project-loaded", self._new_project_loaded_cb) self.project_manager.connect_after("project-closed", self._project_closed_cb) self.project_manager.connect("project-saved", self.__project_saved_cb) self._create_actions() self._sync_do_undo() def _create_actions(self): self.shortcuts.register_group("app", _("General"), position=10) # pylint: disable=attribute-defined-outside-init self.undo_action = Gio.SimpleAction.new("undo", None) self.undo_action.connect("activate", self._undo_cb) self.add_action(self.undo_action) self.shortcuts.add("app.undo", ["<Primary>z"], self.undo_action, _("Undo the most recent action")) self.redo_action = Gio.SimpleAction.new("redo", None) self.redo_action.connect("activate", self._redo_cb) self.add_action(self.redo_action) self.shortcuts.add("app.redo", ["<Primary><Shift>z"], self.redo_action, _("Redo the most recent action")) self.quit_action = Gio.SimpleAction.new("quit", None) self.quit_action.connect("activate", self._quit_cb) self.add_action(self.quit_action) self.shortcuts.add("app.quit", ["<Primary>q"], self.quit_action, _("Quit")) self.show_shortcuts_action = Gio.SimpleAction.new( "shortcuts_window", None) self.show_shortcuts_action.connect("activate", self._show_shortcuts_cb) self.add_action(self.show_shortcuts_action) self.shortcuts.add("app.shortcuts_window", ["<Primary>F1", "<Primary>question"], self.show_shortcuts_action, _("Show the Shortcuts Window")) def do_activate(self): if self.gui: # The app is already started and the window already created. # Present the already existing window. if self.system.has_x11(): # TODO: Use present() instead of present_with_time() when # https://bugzilla.gnome.org/show_bug.cgi?id=688830 is fixed. from gi.repository import GdkX11 x11_server_time = GdkX11.x11_get_server_time( self.gui.get_window()) self.gui.present_with_time(x11_server_time) else: # On Wayland or Quartz (Mac OS X) backend there is no GdkX11, # so just use present() directly here. self.gui.present() # No need to show the welcome wizard. return self.create_main_window() self.gui.show_perspective(self.gui.greeter) def create_main_window(self): if self.gui: return self.gui = MainWindow(self) self.gui.setup_ui() self.add_window(self.gui) def do_open(self, giofiles, unused_count, unused_hint): assert giofiles self.create_main_window() if len(giofiles) > 1: self.warning( "Opening only one project at a time. Ignoring the rest!") project_file = giofiles[0] self.project_manager.load_project(quote_uri(project_file.get_uri())) return True def shutdown(self): """Closes the app. Returns: bool: True if successful, False otherwise. """ self.debug("shutting down") # Refuse to close if we are not done with the current project. if not self.project_manager.close_running_project(): self.warning( "Not closing since running project doesn't want to close") return False if self.gui: self.gui.destroy() self.threads.wait_all_threads() self.settings.store_settings() self.quit() return True def _set_scenario_file(self, uri): if uri: project_path = path_from_uri(uri) else: # New project. project_path = None if 'PITIVI_SCENARIO_FILE' in os.environ: scenario_path = os.environ['PITIVI_SCENARIO_FILE'] else: cache_dir = xdg_cache_home("scenarios") scenario_name = str(time.strftime("%Y%m%d-%H%M%S")) if project_path: scenario_name += os.path.splitext( project_path.replace(os.sep, "_"))[0] scenario_path = os.path.join(cache_dir, scenario_name + ".scenario") scenario_path = path_from_uri(quote_uri(scenario_path)) self._scenario_file = open(scenario_path, "w") if project_path and not project_path.endswith(".scenario"): # It's an xges file probably. with open(project_path) as project: content = project.read().replace("\n", "") self.write_action("load-project", serialized_content=content) def _new_project_loaded_cb(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. try: self.recent_manager.remove_item(uri) except GLib.Error as e: if e.domain != "gtk-recent-manager-error-quark": raise e 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._action_log_commit) self.action_log.connect("move", self._action_log_move_cb) self.project_observer = ProjectObserver(project, self.action_log) self._set_scenario_file(project.get_uri()) def __project_saved_cb(self, unused_project_manager, unused_project, uri): if uri: self.recent_manager.add_item(uri) def _project_closed_cb(self, unused_project_manager, project): if project.loaded: self.action_log = None self._sync_do_undo() if self._scenario_file: self.write_action("stop") self._scenario_file.close() self._scenario_file = None def _check_version(self): """Checks online for new versions of the app.""" self.info("Requesting version information async") giofile = Gio.File.new_for_uri(RELEASES_URL) giofile.load_contents_async(None, self._version_info_received_cb, None) def _version_info_received_cb(self, giofile, result, user_data): # pylint: disable=broad-except try: raw = giofile.load_contents_finish(result)[1] if not isinstance(raw, str): raw = raw.decode() raw = raw.split("\n") # Split line at '=' if the line is not empty or a comment line data = [ element.split("=") for element in raw if element and not element.startswith("#") ] # search newest version and status status = "UNSUPPORTED" current_version = None for version, version_status in data: if VERSION == version: status = version_status if version_status.upper() == "CURRENT": # This is the latest. current_version = version self.info("Latest software version is %s", current_version) version_split = [int(i) for i in VERSION.split(".")] current_version_split = [ int(i) for i in current_version.split(".") ] if version_split > current_version_split: status = "CURRENT" self.info( "Running version %s, which is newer than the latest known version. Considering it as the latest current version.", VERSION) elif status == "UNSUPPORTED": self.warning("Using an outdated version of Pitivi (%s)", VERSION) self._version_information["current"] = current_version self._version_information["status"] = status self.emit("version-info-received", self._version_information) except Exception as e: self.warning("Version info could not be read: %s", e) def is_latest(self): """Whether the app's version is the latest as far as we know.""" status = self._version_information.get("status") return status is None or status.upper() == "CURRENT" def get_latest(self): """Get the latest version of the app or None.""" return self._version_information.get("current") def _quit_cb(self, unused_action, unused_param): self.shutdown() def _undo_cb(self, unused_action, unused_param): self.action_log.undo() def _redo_cb(self, unused_action, unused_param): self.action_log.redo() def _show_shortcuts_cb(self, unused_action, unused_param): show_shortcuts(self) def _action_log_pre_push_cb(self, unused_action_log, action): scenario_action = action.as_scenario_action() if scenario_action: self.write_action(scenario_action) def _action_log_commit(self, action_log, unused_stack): if action_log.is_in_transaction(): return self._sync_do_undo() def _action_log_move_cb(self, action_log, unused_stack): self._sync_do_undo() def _sync_do_undo(self): can_undo = self.action_log and bool(self.action_log.undo_stacks) self.undo_action.set_enabled(bool(can_undo)) can_redo = self.action_log and bool(self.action_log.redo_stacks) self.redo_action.set_enabled(bool(can_redo)) if not self.project_manager.current_project: return dirty = self.action_log and self.action_log.dirty() self.project_manager.current_project.set_modification_state(dirty) # In the tests we do not want to create any gui if self.gui is not None: self.gui.editor.show_project_status() def simple_inhibit(self, reason, flags): """Informs the session manager about actions to be inhibited. Keeps track of the reasons received. A specific reason should always be accompanied by the same flags. Calling the method a second time with the same reason has no effect unless `simple_uninhibit` has been called in the meanwhile. Args: reason (str): The reason for which to perform the inhibition. flags (Gtk.ApplicationInhibitFlags): What should be inhibited. """ if reason in self.__inhibit_cookies: self.debug("Inhibit reason already active: %s", reason) return self.debug("Inhibiting %s for %s", flags, reason) cookie = self.inhibit(self.gui, flags, reason) self.__inhibit_cookies[reason] = cookie def simple_uninhibit(self, reason): """Informs the session manager that an inhibition is not needed anymore. Args: reason (str): The reason which is not valid anymore. """ try: cookie = self.__inhibit_cookies.pop(reason) except KeyError: self.debug("Inhibit reason not active: %s", reason) return self.debug("Uninhibiting %s", reason) self.uninhibit(cookie)
class Pitivi(Gtk.Application, Loggable): """Hello world. Attributes: 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. 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. """ __gsignals__ = { "version-info-received": (GObject.SIGNAL_RUN_LAST, None, (object,)) } def __init__(self): Gtk.Application.__init__(self, application_id="org.pitivi", flags=Gio.ApplicationFlags.HANDLES_OPEN) Loggable.__init__(self) self.settings = None self.threads = None self.effects = None self.system = None self.project_manager = ProjectManager(self) self.action_log = None self.project_observer = None self._last_action_time = Gst.util_get_timestamp() self.gui = None self.__welcome_wizard = None self._version_information = {} self._scenario_file = None self._first_action = True Zoomable.app = self self.shortcuts = ShortcutsManager(self) def write_action(self, action, **kwargs): if self._scenario_file is None: return if self._first_action: self._scenario_file.write( "description, seek=true, handles-states=true\n") self._first_action = False now = Gst.util_get_timestamp() if now - self._last_action_time > 0.05 * Gst.SECOND: # We need to make sure that the waiting time was more than 50 ms. st = Gst.Structure.new_empty("wait") st["duration"] = float((now - self._last_action_time) / Gst.SECOND) self._scenario_file.write(st.to_string() + "\n") self._last_action_time = now if not isinstance(action, Gst.Structure): structure = Gst.Structure.new_empty(action) for key, value in kwargs.items(): key = key.replace("_", "-") structure[key] = value action = structure self._scenario_file.write(action.to_string() + "\n") self._scenario_file.flush() def do_startup(self): Gtk.Application.do_startup(self) # Init logging as early as possible so we can log startup code enable_color = not os.environ.get( 'PITIVI_DEBUG_NO_COLOR', '0') in ('', '1') # Let's show a human-readable Pitivi debug output by default, and only # show a crazy unreadable mess when surrounded by gst debug statements. enable_crack_output = "GST_DEBUG" in os.environ loggable.init('PITIVI_DEBUG', enable_color, enable_crack_output) self.info('starting up') self._setup() self._checkVersion() def _setup(self): self.settings = GlobalSettings() self.threads = ThreadMaster() self.effects = EffectsManager() self.proxy_manager = ProxyManager(self) self.system = get_system() self.project_manager.connect( "new-project-loading", self._newProjectLoadingCb) self.project_manager.connect( "new-project-loaded", self._newProjectLoaded) self.project_manager.connect("project-closed", self._projectClosed) self._createActions() self._syncDoUndo() def _createActions(self): self.shortcuts.register_group("app", _("General"), position=10) self.undo_action = Gio.SimpleAction.new("undo", None) self.undo_action.connect("activate", self._undoCb) self.add_action(self.undo_action) self.shortcuts.add("app.undo", ["<Primary>z"], _("Undo the most recent action")) self.redo_action = Gio.SimpleAction.new("redo", None) self.redo_action.connect("activate", self._redoCb) self.add_action(self.redo_action) self.shortcuts.add("app.redo", ["<Primary><Shift>z"], _("Redo the most recent action")) self.quit_action = Gio.SimpleAction.new("quit", None) self.quit_action.connect("activate", self._quitCb) self.add_action(self.quit_action) self.shortcuts.add("app.quit", ["<Primary>q"], _("Quit")) self.show_shortcuts_action = Gio.SimpleAction.new("shortcuts_window", None) self.show_shortcuts_action.connect("activate", self._show_shortcuts_cb) self.add_action(self.show_shortcuts_action) self.shortcuts.add("app.shortcuts_window", ["<Primary>F1", "<Primary>question"], _("Show the Shortcuts Window")) def do_activate(self): if self.gui: # The app is already started and the window already created. # Present the already existing window. if self.system.has_x11(): # TODO: Use present() instead of present_with_time() when # https://bugzilla.gnome.org/show_bug.cgi?id=688830 is fixed. from gi.repository import GdkX11 x11_server_time = GdkX11.x11_get_server_time(self.gui.get_window()) self.gui.present_with_time(x11_server_time) else: # On Wayland or Quartz (Mac OS X) backend there is no GdkX11, # so just use present() directly here. 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 def createMainWindow(self): if self.gui: return self.gui = MainWindow(self) 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() if len(giofiles) > 1: self.warning( "Can open only one project file at a time. Ignoring the rest!") project_file = giofiles[0] self.project_manager.loadProject(quote_uri(project_file.get_uri())) return True def shutdown(self): """Closes the app. Returns: bool: True if successful, False otherwise. """ self.debug("shutting down") # Refuse to close if we are not done with the current project. if not self.project_manager.closeRunningProject(): 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() self.settings.storeSettings() self.quit() return True def _setScenarioFile(self, uri): if uri: project_path = path_from_uri(uri) else: # New project. project_path = None if 'PITIVI_SCENARIO_FILE' in os.environ: scenario_path = os.environ['PITIVI_SCENARIO_FILE'] else: cache_dir = get_dir(os.path.join(xdg_cache_home(), "scenarios")) scenario_name = str(time.strftime("%Y%m%d-%H%M%S")) if project_path: scenario_name += os.path.splitext(project_path.replace(os.sep, "_"))[0] scenario_path = os.path.join(cache_dir, scenario_name + ".scenario") scenario_path = path_from_uri(quote_uri(scenario_path)) self._scenario_file = open(scenario_path, "w") if project_path and not project_path.endswith(".scenario"): # It's an xges file probably. with open(project_path) as project: content = project.read().replace("\n", "") self.write_action("load-project", serialized_content=content) def _newProjectLoadingCb(self, unused_project_manager, project): self._setScenarioFile(project.get_uri()) def _newProjectLoaded(self, unused_project_manager, project): 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 _projectClosed(self, unused_project_manager, project): if project.loaded: self.action_log = None self._syncDoUndo() if self._scenario_file: self.write_action("stop") self._scenario_file.close() self._scenario_file = None def _checkVersion(self): """Checks online for new versions of the app.""" self.info("Requesting version information async") giofile = Gio.File.new_for_uri(RELEASES_URL) giofile.load_contents_async(None, self._versionInfoReceivedCb, None) def _versionInfoReceivedCb(self, giofile, result, user_data): try: raw = giofile.load_contents_finish(result)[1] if not isinstance(raw, str): raw = raw.decode() raw = raw.split("\n") # Split line at '=' if the line is not empty or a comment line data = [element.split("=") for element in raw if element and not element.startswith("#")] # search newest version and status status = "UNSUPPORTED" current_version = None for version, version_status in data: if VERSION == version: status = version_status if version_status.upper() == "CURRENT": # This is the latest. current_version = version self.info("Latest software version is %s", current_version) VERSION_split = [int(i) for i in VERSION.split(".")] current_version_split = [int(i) for i in current_version.split(".")] if VERSION_split > current_version_split: status = "CURRENT" self.info( "Running version %s, which is newer than the latest known version. Considering it as the latest current version.", VERSION) elif status is "UNSUPPORTED": self.warning( "Using an outdated version of Pitivi (%s)", VERSION) self._version_information["current"] = current_version self._version_information["status"] = status self.emit("version-info-received", self._version_information) except Exception as e: self.warning("Version info could not be read: %s", e) def isLatest(self): """Whether the app's version is the latest as far as we know.""" status = self._version_information.get("status") return status is None or status.upper() == "CURRENT" def getLatest(self): """Get the latest version of the app or None.""" return self._version_information.get("current") def _quitCb(self, unused_action, unused_param): self.shutdown() def _undoCb(self, unused_action, unused_param): self.action_log.undo() def _redoCb(self, unused_action, unused_param): self.action_log.redo() def _show_shortcuts_cb(self, unused_action, unused_param): show_shortcuts(self) def _action_log_pre_push_cb(self, unused_action_log, action): try: st = action.asScenarioAction() except NotImplementedError: self.warning("No serialization method for action %s", action) return if st: self.write_action(st) def _actionLogCommit(self, action_log, unused_stack): if action_log.is_in_transaction(): return self._syncDoUndo() def _action_log_move_cb(self, action_log, unused_stack): self._syncDoUndo() def _syncDoUndo(self): can_undo = self.action_log and bool(self.action_log.undo_stacks) self.undo_action.set_enabled(bool(can_undo)) can_redo = self.action_log and bool(self.action_log.redo_stacks) self.redo_action.set_enabled(bool(can_redo)) if not self.project_manager.current_project: return dirty = self.action_log and self.action_log.dirty() 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()
class Pitivi(Gtk.Application, Loggable): """ Pitivi's application. @type effects: L{EffectsManager} @ivar gui: The main window of the app. @type gui: L{PitiviMainWindow} @ivar project_manager: The project manager object used in the application @type project_manager: L{ProjectManager} @ivar settings: Application-wide settings. @type settings: L{GlobalSettings}. """ __gsignals__ = { "version-info-received": (GObject.SIGNAL_RUN_LAST, None, (object, )) } def __init__(self): Gtk.Application.__init__(self, application_id="org.pitivi", flags=Gio.ApplicationFlags.HANDLES_OPEN) Loggable.__init__(self) self.settings = None self.threads = None self.effects = None self.system = None self.project_manager = ProjectManager(self) self.action_log = UndoableActionLog() self.timeline_log_observer = None self.project_log_observer = None self.gui = None self.welcome_wizard = None self._version_information = {} self.connect("startup", self._startupCb) self.connect("activate", self._activateCb) self.connect("open", self.openCb) def _startupCb(self, unused_app): # Init logging as early as possible so we can log startup code enable_color = not os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '1') # Let's show a human-readable Pitivi debug output by default, and only # show a crazy unreadable mess when surrounded by gst debug statements. enable_crack_output = "GST_DEBUG" in os.environ log.init('PITIVI_DEBUG', enable_color, enable_crack_output) self.info('starting up') self.settings = GlobalSettings() self.threads = ThreadMaster() self.effects = EffectsManager() self.system = getSystem() self.action_log.connect("commit", self._actionLogCommit) self.action_log.connect("undo", self._actionLogUndo) self.action_log.connect("redo", self._actionLogRedo) self.action_log.connect("cleaned", self._actionLogCleaned) self.timeline_log_observer = TimelineLogObserver(self.action_log) self.project_log_observer = ProjectLogObserver(self.action_log) self.project_manager.connect("new-project-loaded", self._newProjectLoaded) self.project_manager.connect("project-closed", self._projectClosed) self._createActions() self._checkVersion() def _createActions(self): self.undo_action = Gio.SimpleAction.new("undo", None) self.undo_action.connect("activate", self._undoCb) self.add_action(self.undo_action) self.add_accelerator("<Control>z", "app.undo", None) self.redo_action = Gio.SimpleAction.new("redo", None) self.redo_action.connect("activate", self._redoCb) self.add_action(self.redo_action) self.add_accelerator("<Control><Shift>z", "app.redo", None) self.quit_action = Gio.SimpleAction.new("quit", None) self.quit_action.connect("activate", self._quitCb) self.add_action(self.quit_action) self.add_accelerator("<Control>q", "app.quit", None) def _activateCb(self, unused_app): if self.gui: # The app is already started and the window already created. # Present the already existing window. # TODO: Use present() instead of present_with_time() when # https://bugzilla.gnome.org/show_bug.cgi?id=688830 is fixed. x11_server_time = GdkX11.x11_get_server_time(self.gui.get_window()) self.gui.present_with_time(x11_server_time) # No need to show the welcome wizard. return self.createMainWindow() self.welcome_wizard = StartUpWizard(self) self.welcome_wizard.show() def createMainWindow(self): if self.gui: return self.gui = PitiviMainWindow(self) self.add_window(self.gui) # We might as well show it. self.gui.show() def openCb(self, unused_app, giofiles, unused_count, unused_hint): assert giofiles self.createMainWindow() if len(giofiles) > 1: self.warning( "Can open only one project file at a time. Ignoring the rest!") project_file = giofiles[0] self.project_manager.loadProject(quote_uri(project_file.get_uri())) return True def shutdown(self): """ Close Pitivi. @return: C{True} if Pitivi was successfully closed, else C{False}. @rtype: C{bool} """ self.debug("shutting down") # Refuse to close if we are not done with the current project. if not self.project_manager.closeRunningProject(): 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() self.settings.storeSettings() self.quit() return True def _newProjectLoaded(self, unused_project_manager, project, unused_fully_loaded): self.action_log.clean() self.timeline_log_observer.startObserving(project.timeline) self.project_log_observer.startObserving(project) def _projectClosed(self, unused_project_manager, project): self.project_log_observer.stopObserving(project) self.timeline_log_observer.stopObserving(project.timeline) def _checkVersion(self): """ Check online for release versions information. """ self.info("Requesting version information async") giofile = Gio.File.new_for_uri(RELEASES_URL) giofile.load_contents_async(None, self._versionInfoReceivedCb, None) def _versionInfoReceivedCb(self, giofile, result, user_data): try: raw = giofile.load_contents_finish(result)[1] if not isinstance(raw, str): raw = raw.decode() raw = raw.split("\n") # Split line at '=' if the line is not empty or a comment line data = [ element.split("=") for element in raw if element and not element.startswith("#") ] # search newest version and status status = "UNSUPPORTED" current_version = None for version, version_status in data: if VERSION == version: status = version_status if version_status.upper() == "CURRENT": # This is the latest. current_version = version self.info("Latest software version is %s", current_version) VERSION_split = [int(i) for i in VERSION.split(".")] current_version_split = [ int(i) for i in current_version.split(".") ] if VERSION_split > current_version_split: status = "CURRENT" self.info( "Running version %s, which is newer than the latest known version. Considering it as the latest current version.", VERSION) elif status is "UNSUPPORTED": self.warning("Using an outdated version of Pitivi (%s)", VERSION) self._version_information["current"] = current_version self._version_information["status"] = status self.emit("version-info-received", self._version_information) except Exception as e: self.warning("Version info could not be read: %s", e) def isLatest(self): """ Whether the app's version is the latest as far as we know. """ status = self._version_information.get("status") return status is None or status.upper() == "CURRENT" def getLatest(self): """ Get the latest version of the app or None. """ return self._version_information.get("current") def _quitCb(self, unused_action, unused_param): self.shutdown() def _undoCb(self, unused_action, unused_param): self.action_log.undo() def _redoCb(self, unused_action, unused_param): self.action_log.redo() def _actionLogCommit(self, action_log, unused_stack, nested): if nested: return self._syncDoUndo(action_log) def _actionLogUndo(self, action_log, unused_stack): self._syncDoUndo(action_log) def _actionLogRedo(self, action_log, unused_stack): self._syncDoUndo(action_log) def _actionLogCleaned(self, action_log): self._syncDoUndo(action_log) def _syncDoUndo(self, action_log): can_undo = bool(action_log.undo_stacks) self.undo_action.set_enabled(can_undo) can_redo = bool(action_log.redo_stacks) self.redo_action.set_enabled(can_redo) dirty = action_log.dirty() 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()
class Pitivi(Gtk.Application, Loggable): """ Pitivi's application. @type effects: L{EffectsManager} @ivar gui: The main window of the app. @type gui: L{PitiviMainWindow} @ivar project_manager: The project manager object used in the application @type project_manager: L{ProjectManager} @ivar settings: Application-wide settings. @type settings: L{GlobalSettings}. """ __gsignals__ = { "version-info-received": (GObject.SIGNAL_RUN_LAST, None, (object, )) } def __init__(self): Gtk.Application.__init__(self, application_id="org.pitivi", flags=Gio.ApplicationFlags.HANDLES_OPEN) Loggable.__init__(self) self.settings = None self.threads = None self.effects = None self.system = None self.project_manager = ProjectManager(self) self.action_log = UndoableActionLog(self) self.timeline_log_observer = None self.project_log_observer = None self._last_action_time = Gst.util_get_timestamp() self.gui = None self.welcome_wizard = None self._version_information = {} self._scenario_file = None self._first_action = True self.connect("startup", self._startupCb) self.connect("activate", self._activateCb) self.connect("open", self.openCb) def write_action(self, action, properties={}): if self._first_action: self._scenario_file.write( "description, seek=true, handles-states=true\n") self._first_action = False now = Gst.util_get_timestamp() if now - self._last_action_time > 0.05 * Gst.SECOND: # We need to make sure that the waiting time was more than 50 ms. st = Gst.Structure.new_empty("wait") st["duration"] = float((now - self._last_action_time) / Gst.SECOND) self._scenario_file.write(st.to_string() + "\n") self._last_action_time = now if not isinstance(action, Gst.Structure): structure = Gst.Structure.new_empty(action) for key, value in properties.items(): structure[key] = value action = structure self._scenario_file.write(action.to_string() + "\n") self._scenario_file.flush() def _startupCb(self, unused_app): # Init logging as early as possible so we can log startup code enable_color = not os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '1') # Let's show a human-readable Pitivi debug output by default, and only # show a crazy unreadable mess when surrounded by gst debug statements. enable_crack_output = "GST_DEBUG" in os.environ log.init('PITIVI_DEBUG', enable_color, enable_crack_output) self.info('starting up') self.settings = GlobalSettings() self.threads = ThreadMaster() self.effects = EffectsManager() self.system = getSystem() self.action_log.connect("commit", self._actionLogCommit) self.action_log.connect("undo", self._actionLogUndo) self.action_log.connect("redo", self._actionLogRedo) self.action_log.connect("cleaned", self._actionLogCleaned) self.timeline_log_observer = TimelineLogObserver(self.action_log) self.project_log_observer = ProjectLogObserver(self.action_log) self.project_manager.connect("new-project-loading", self._newProjectLoadingCb) self.project_manager.connect("new-project-loaded", self._newProjectLoaded) self.project_manager.connect("project-closed", self._projectClosed) self._createActions() self._checkVersion() def _createActions(self): self.undo_action = Gio.SimpleAction.new("undo", None) self.undo_action.connect("activate", self._undoCb) self.add_action(self.undo_action) self.add_accelerator("<Control>z", "app.undo", None) self.redo_action = Gio.SimpleAction.new("redo", None) self.redo_action.connect("activate", self._redoCb) self.add_action(self.redo_action) self.add_accelerator("<Control><Shift>z", "app.redo", None) self.quit_action = Gio.SimpleAction.new("quit", None) self.quit_action.connect("activate", self._quitCb) self.add_action(self.quit_action) self.add_accelerator("<Control>q", "app.quit", None) def _activateCb(self, unused_app): if self.gui: # The app is already started and the window already created. # Present the already existing window. try: # TODO: Use present() instead of present_with_time() when # https://bugzilla.gnome.org/show_bug.cgi?id=688830 is fixed. from gi.repository import GdkX11 x11_server_time = GdkX11.x11_get_server_time( self.gui.get_window()) self.gui.present_with_time(x11_server_time) except ImportError: # On Wayland or Quartz (Mac OS X) backend there is no GdkX11, # so just use present() directly here. self.gui.present() # No need to show the welcome wizard. return self.createMainWindow() self.welcome_wizard = StartUpWizard(self) self.welcome_wizard.show() def createMainWindow(self): if self.gui: return self.gui = PitiviMainWindow(self) self.add_window(self.gui) # We might as well show it. self.gui.show() def openCb(self, unused_app, giofiles, unused_count, unused_hint): assert giofiles self.createMainWindow() if len(giofiles) > 1: self.warning( "Can open only one project file at a time. Ignoring the rest!") project_file = giofiles[0] self.project_manager.loadProject(quote_uri(project_file.get_uri())) return True def shutdown(self): """ Close Pitivi. @return: C{True} if Pitivi was successfully closed, else C{False}. @rtype: C{bool} """ self.debug("shutting down") # Refuse to close if we are not done with the current project. if not self.project_manager.closeRunningProject(): 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() self.settings.storeSettings() self.quit() return True self._first_action = True def _setScenarioFile(self, uri): if 'PITIVI_SCENARIO_FILE' in os.environ: uri = quote_uri(os.environ['PITIVI_SCENARIO_FILE']) else: cache_dir = get_dir(os.path.join(xdg_cache_home(), "scenarios")) scenario_name = str(time.strftime("%Y%m%d-%H%M%S")) project_path = None if uri: project_path = path_from_uri(uri) scenario_name += os.path.splitext( project_path.replace(os.sep, "_"))[0] uri = os.path.join(cache_dir, scenario_name + ".scenario") uri = quote_uri(uri) self._scenario_file = open(path_from_uri(uri), "w") if project_path: f = open(project_path) content = f.read() if not uri.endswith(".scenario"): self.write_action( "load-project", {"serialized-content": "%s" % content.replace("\n", "")}) f.close() def _newProjectLoadingCb(self, unused_project_manager, uri): self._setScenarioFile(uri) def _newProjectLoaded(self, unused_project_manager, project, unused_fully_loaded): self.action_log.clean() self.timeline_log_observer.startObserving(project.timeline) self.project_log_observer.startObserving(project) def _projectClosed(self, unused_project_manager, project): self.project_log_observer.stopObserving(project) self.timeline_log_observer.stopObserving(project.timeline) if self._scenario_file: self.write_action("stop") self._scenario_file.close() self._scenario_file = None def _checkVersion(self): """ Check online for release versions information. """ self.info("Requesting version information async") giofile = Gio.File.new_for_uri(RELEASES_URL) giofile.load_contents_async(None, self._versionInfoReceivedCb, None) def _versionInfoReceivedCb(self, giofile, result, user_data): try: raw = giofile.load_contents_finish(result)[1] if not isinstance(raw, str): raw = raw.decode() raw = raw.split("\n") # Split line at '=' if the line is not empty or a comment line data = [ element.split("=") for element in raw if element and not element.startswith("#") ] # search newest version and status status = "UNSUPPORTED" current_version = None for version, version_status in data: if VERSION == version: status = version_status if version_status.upper() == "CURRENT": # This is the latest. current_version = version self.info("Latest software version is %s", current_version) VERSION_split = [int(i) for i in VERSION.split(".")] current_version_split = [ int(i) for i in current_version.split(".") ] if VERSION_split > current_version_split: status = "CURRENT" self.info( "Running version %s, which is newer than the latest known version. Considering it as the latest current version.", VERSION) elif status is "UNSUPPORTED": self.warning("Using an outdated version of Pitivi (%s)", VERSION) self._version_information["current"] = current_version self._version_information["status"] = status self.emit("version-info-received", self._version_information) except Exception as e: self.warning("Version info could not be read: %s", e) def isLatest(self): """ Whether the app's version is the latest as far as we know. """ status = self._version_information.get("status") return status is None or status.upper() == "CURRENT" def getLatest(self): """ Get the latest version of the app or None. """ return self._version_information.get("current") def _quitCb(self, unused_action, unused_param): self.shutdown() def _undoCb(self, unused_action, unused_param): self.action_log.undo() def _redoCb(self, unused_action, unused_param): self.action_log.redo() def _actionLogCommit(self, action_log, unused_stack, nested): if nested: return self._syncDoUndo(action_log) def _actionLogUndo(self, action_log, unused_stack): self._syncDoUndo(action_log) def _actionLogRedo(self, action_log, unused_stack): self._syncDoUndo(action_log) def _actionLogCleaned(self, action_log): self._syncDoUndo(action_log) def _syncDoUndo(self, action_log): can_undo = bool(action_log.undo_stacks) self.undo_action.set_enabled(can_undo) can_redo = bool(action_log.redo_stacks) self.redo_action.set_enabled(can_redo) dirty = action_log.dirty() 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()
class Pitivi(Gtk.Application, Loggable): """ Pitivi's application. @type effects: L{EffectsManager} @ivar gui: The main window of the app. @type gui: L{PitiviMainWindow} @ivar project_manager: The project manager object used in the application @type project_manager: L{ProjectManager} @ivar settings: Application-wide settings. @type settings: L{GlobalSettings}. """ __gsignals__ = { "version-info-received": (GObject.SIGNAL_RUN_LAST, None, (object,)) } def __init__(self): Gtk.Application.__init__(self, application_id="org.pitivi", flags=Gio.ApplicationFlags.HANDLES_OPEN) Loggable.__init__(self) self.settings = None self.threads = None self.effects = None self.system = None self.project_manager = ProjectManager(self) self.action_log = UndoableActionLog(self) self.timeline_log_observer = None self.project_log_observer = None self._last_action_time = Gst.util_get_timestamp() self.gui = None self.welcome_wizard = None self._version_information = {} self._scenario_file = None self._first_action = True self.connect("startup", self._startupCb) self.connect("activate", self._activateCb) self.connect("open", self.openCb) def write_action(self, action, properties={}): if self._first_action: self._scenario_file.write( "description, seek=true, handles-states=true\n") self._first_action = False now = Gst.util_get_timestamp() if now - self._last_action_time > 0.05 * Gst.SECOND: # We need to make sure that the waiting time was more than 50 ms. st = Gst.Structure.new_empty("wait") st["duration"] = float((now - self._last_action_time) / Gst.SECOND) self._scenario_file.write(st.to_string() + "\n") self._last_action_time = now if not isinstance(action, Gst.Structure): structure = Gst.Structure.new_empty(action) for key, value in properties.items(): structure[key] = value action = structure self._scenario_file.write(action.to_string() + "\n") self._scenario_file.flush() def _startupCb(self, unused_app): # Init logging as early as possible so we can log startup code enable_color = not os.environ.get( 'PITIVI_DEBUG_NO_COLOR', '0') in ('', '1') # Let's show a human-readable Pitivi debug output by default, and only # show a crazy unreadable mess when surrounded by gst debug statements. enable_crack_output = "GST_DEBUG" in os.environ log.init('PITIVI_DEBUG', enable_color, enable_crack_output) self.info('starting up') self.settings = GlobalSettings() self.threads = ThreadMaster() self.effects = EffectsManager() self.system = getSystem() self.action_log.connect("commit", self._actionLogCommit) self.action_log.connect("undo", self._actionLogUndo) self.action_log.connect("redo", self._actionLogRedo) self.action_log.connect("cleaned", self._actionLogCleaned) self.timeline_log_observer = TimelineLogObserver(self.action_log) self.project_log_observer = ProjectLogObserver(self.action_log) self.project_manager.connect( "new-project-loading", self._newProjectLoadingCb) self.project_manager.connect( "new-project-loaded", self._newProjectLoaded) self.project_manager.connect("project-closed", self._projectClosed) self._createActions() self._checkVersion() def _createActions(self): self.undo_action = Gio.SimpleAction.new("undo", None) self.undo_action.connect("activate", self._undoCb) self.add_action(self.undo_action) self.add_accelerator("<Control>z", "app.undo", None) self.redo_action = Gio.SimpleAction.new("redo", None) self.redo_action.connect("activate", self._redoCb) self.add_action(self.redo_action) self.add_accelerator("<Control><Shift>z", "app.redo", None) self.quit_action = Gio.SimpleAction.new("quit", None) self.quit_action.connect("activate", self._quitCb) self.add_action(self.quit_action) self.add_accelerator("<Control>q", "app.quit", None) def _activateCb(self, unused_app): if self.gui: # The app is already started and the window already created. # Present the already existing window. try: # TODO: Use present() instead of present_with_time() when # https://bugzilla.gnome.org/show_bug.cgi?id=688830 is fixed. from gi.repository import GdkX11 x11_server_time = GdkX11.x11_get_server_time(self.gui.get_window()) self.gui.present_with_time(x11_server_time) except ImportError: # On Wayland or Quartz (Mac OS X) backend there is no GdkX11, # so just use present() directly here. self.gui.present() # No need to show the welcome wizard. return self.createMainWindow() self.welcome_wizard = StartUpWizard(self) self.welcome_wizard.show() def createMainWindow(self): if self.gui: return self.gui = PitiviMainWindow(self) self.add_window(self.gui) # We might as well show it. self.gui.show() def openCb(self, unused_app, giofiles, unused_count, unused_hint): assert giofiles self.createMainWindow() if len(giofiles) > 1: self.warning( "Can open only one project file at a time. Ignoring the rest!") project_file = giofiles[0] self.project_manager.loadProject(quote_uri(project_file.get_uri())) return True def shutdown(self): """ Close Pitivi. @return: C{True} if Pitivi was successfully closed, else C{False}. @rtype: C{bool} """ self.debug("shutting down") # Refuse to close if we are not done with the current project. if not self.project_manager.closeRunningProject(): 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() self.settings.storeSettings() self.quit() return True self._first_action = True def _setScenarioFile(self, uri): if 'PITIVI_SCENARIO_FILE' in os.environ: uri = quote_uri(os.environ['PITIVI_SCENARIO_FILE']) else: cache_dir = get_dir(os.path.join(xdg_cache_home(), "scenarios")) scenario_name = str(time.strftime("%Y%m%d-%H%M%S")) project_path = None if uri: project_path = path_from_uri(uri) scenario_name += os.path.splitext(project_path.replace(os.sep, "_"))[0] uri = os.path.join(cache_dir, scenario_name + ".scenario") uri = quote_uri(uri) self._scenario_file = open(path_from_uri(uri), "w") if project_path: f = open(project_path) content = f.read() if not project_path.endswith(".scenario"): self.write_action("load-project", {"serialized-content": "%s" % content.replace("\n", "")}) f.close() def _newProjectLoadingCb(self, unused_project_manager, uri): self._setScenarioFile(uri) def _newProjectLoaded(self, unused_project_manager, project, unused_fully_loaded): self.action_log.clean() self.timeline_log_observer.startObserving(project.timeline) self.project_log_observer.startObserving(project) def _projectClosed(self, unused_project_manager, project): self.project_log_observer.stopObserving(project) self.timeline_log_observer.stopObserving(project.timeline) if self._scenario_file: self.write_action("stop") self._scenario_file.close() self._scenario_file = None def _checkVersion(self): """ Check online for release versions information. """ self.info("Requesting version information async") giofile = Gio.File.new_for_uri(RELEASES_URL) giofile.load_contents_async(None, self._versionInfoReceivedCb, None) def _versionInfoReceivedCb(self, giofile, result, user_data): try: raw = giofile.load_contents_finish(result)[1] if not isinstance(raw, str): raw = raw.decode() raw = raw.split("\n") # Split line at '=' if the line is not empty or a comment line data = [element.split("=") for element in raw if element and not element.startswith("#")] # search newest version and status status = "UNSUPPORTED" current_version = None for version, version_status in data: if VERSION == version: status = version_status if version_status.upper() == "CURRENT": # This is the latest. current_version = version self.info("Latest software version is %s", current_version) VERSION_split = [int(i) for i in VERSION.split(".")] current_version_split = [int(i) for i in current_version.split(".")] if VERSION_split > current_version_split: status = "CURRENT" self.info( "Running version %s, which is newer than the latest known version. Considering it as the latest current version.", VERSION) elif status is "UNSUPPORTED": self.warning( "Using an outdated version of Pitivi (%s)", VERSION) self._version_information["current"] = current_version self._version_information["status"] = status self.emit("version-info-received", self._version_information) except Exception as e: self.warning("Version info could not be read: %s", e) def isLatest(self): """ Whether the app's version is the latest as far as we know. """ status = self._version_information.get("status") return status is None or status.upper() == "CURRENT" def getLatest(self): """ Get the latest version of the app or None. """ return self._version_information.get("current") def _quitCb(self, unused_action, unused_param): self.shutdown() def _undoCb(self, unused_action, unused_param): self.action_log.undo() def _redoCb(self, unused_action, unused_param): self.action_log.redo() def _actionLogCommit(self, action_log, unused_stack, nested): if nested: return self._syncDoUndo(action_log) def _actionLogUndo(self, action_log, unused_stack): self._syncDoUndo(action_log) def _actionLogRedo(self, action_log, unused_stack): self._syncDoUndo(action_log) def _actionLogCleaned(self, action_log): self._syncDoUndo(action_log) def _syncDoUndo(self, action_log): can_undo = bool(action_log.undo_stacks) self.undo_action.set_enabled(can_undo) can_redo = bool(action_log.redo_stacks) self.redo_action.set_enabled(can_redo) dirty = action_log.dirty() 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()
class Pitivi(Gtk.Application, Loggable): """ Pitivi's application. @type effects: L{EffectsManager} @ivar gui: The main window of the app. @type gui: L{PitiviMainWindow} @ivar project_manager: The project manager object used in the application @type project_manager: L{ProjectManager} @ivar settings: Application-wide settings. @type settings: L{GlobalSettings}. """ __gsignals__ = { "version-info-received": (GObject.SIGNAL_RUN_LAST, None, (object,)) } def __init__(self): Gtk.Application.__init__(self, application_id="org.pitivi", flags=Gio.ApplicationFlags.HANDLES_OPEN) Loggable.__init__(self) self.settings = None self.threads = None self.effects = None self.system = None self.project_manager = ProjectManager(self) self.action_log = UndoableActionLog() self.timeline_log_observer = None self.project_log_observer = None self.gui = None self.welcome_wizard = None self._version_information = {} self.connect("startup", self._startupCb) self.connect("activate", self._activateCb) self.connect("open", self.openCb) def _startupCb(self, unused_app): # Init logging as early as possible so we can log startup code enable_color = not os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '1') # Let's show a human-readable Pitivi debug output by default, and only # show a crazy unreadable mess when surrounded by gst debug statements. enable_crack_output = "GST_DEBUG" in os.environ log.init('PITIVI_DEBUG', enable_color, enable_crack_output) self.info('starting up') self.settings = GlobalSettings() self.threads = ThreadMaster() self.effects = EffectsManager() self.system = getSystem() self.action_log.connect("commit", self._actionLogCommit) self.action_log.connect("undo", self._actionLogUndo) self.action_log.connect("redo", self._actionLogRedo) self.action_log.connect("cleaned", self._actionLogCleaned) self.timeline_log_observer = TimelineLogObserver(self.action_log) self.project_log_observer = ProjectLogObserver(self.action_log) self.project_manager.connect("new-project-loaded", self._newProjectLoaded) self.project_manager.connect("project-closed", self._projectClosed) self._createActions() self._checkVersion() def _createActions(self): self.undo_action = Gio.SimpleAction.new("undo", None) self.undo_action.connect("activate", self._undoCb) self.add_action(self.undo_action) self.add_accelerator("<Control>z", "app.undo", None) self.redo_action = Gio.SimpleAction.new("redo", None) self.redo_action.connect("activate", self._redoCb) self.add_action(self.redo_action) self.add_accelerator("<Control><Shift>z", "app.redo", None) self.quit_action = Gio.SimpleAction.new("quit", None) self.quit_action.connect("activate", self._quitCb) self.add_action(self.quit_action) self.add_accelerator("<Control>q", "app.quit", None) def _activateCb(self, unused_app): if self.gui: # The app is already started and the window already created. # Present the already existing window. # TODO: Use present() instead of present_with_time() when # https://bugzilla.gnome.org/show_bug.cgi?id=688830 is fixed. x11_server_time = GdkX11.x11_get_server_time(self.gui.get_window()) self.gui.present_with_time(x11_server_time) # No need to show the welcome wizard. return self.createMainWindow() self.welcome_wizard = StartUpWizard(self) self.welcome_wizard.show() def createMainWindow(self): if self.gui: return self.gui = PitiviMainWindow(self) self.add_window(self.gui) # We might as well show it. self.gui.show() def openCb(self, unused_app, giofiles, unused_count, unused_hint): assert giofiles self.createMainWindow() if len(giofiles) > 1: self.warning("Can open only one project file at a time. Ignoring the rest!") project_file = giofiles[0] self.project_manager.loadProject(quote_uri(project_file.get_uri())) return True def shutdown(self): """ Close Pitivi. @return: C{True} if Pitivi was successfully closed, else C{False}. @rtype: C{bool} """ self.debug("shutting down") # Refuse to close if we are not done with the current project. if not self.project_manager.closeRunningProject(): 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() self.settings.storeSettings() self.quit() return True def _newProjectLoaded(self, unused_project_manager, project, unused_fully_loaded): self.action_log.clean() self.timeline_log_observer.startObserving(project.timeline) self.project_log_observer.startObserving(project) def _projectClosed(self, unused_project_manager, project): self.project_log_observer.stopObserving(project) self.timeline_log_observer.stopObserving(project.timeline) def _checkVersion(self): """ Check online for release versions information. """ self.info("Requesting version information async") giofile = Gio.File.new_for_uri(RELEASES_URL) giofile.load_contents_async(None, self._versionInfoReceivedCb, None) def _versionInfoReceivedCb(self, giofile, result, user_data): try: raw = giofile.load_contents_finish(result)[1] if not isinstance(raw, str): raw = raw.decode() raw = raw.split("\n") # Split line at '=' if the line is not empty or a comment line data = [element.split("=") for element in raw if element and not element.startswith("#")] # search newest version and status status = "UNSUPPORTED" current_version = None for version, version_status in data: if VERSION == version: status = version_status if version_status.upper() == "CURRENT": # This is the latest. current_version = version self.info("Latest software version is %s", current_version) VERSION_split = [int(i) for i in VERSION.split(".")] current_version_split = [int(i) for i in current_version.split(".")] if VERSION_split > current_version_split: status = "CURRENT" self.info("Running version %s, which is newer than the latest known version. Considering it as the latest current version.", VERSION) elif status is "UNSUPPORTED": self.warning("Using an outdated version of Pitivi (%s)", VERSION) self._version_information["current"] = current_version self._version_information["status"] = status self.emit("version-info-received", self._version_information) except Exception as e: self.warning("Version info could not be read: %s", e) def isLatest(self): """ Whether the app's version is the latest as far as we know. """ status = self._version_information.get("status") return status is None or status.upper() == "CURRENT" def getLatest(self): """ Get the latest version of the app or None. """ return self._version_information.get("current") def _quitCb(self, unused_action, unused_param): self.shutdown() def _undoCb(self, unused_action, unused_param): self.action_log.undo() def _redoCb(self, unused_action, unused_param): self.action_log.redo() def _actionLogCommit(self, action_log, unused_stack, nested): if nested: return self._syncDoUndo(action_log) def _actionLogUndo(self, action_log, unused_stack): self._syncDoUndo(action_log) def _actionLogRedo(self, action_log, unused_stack): self._syncDoUndo(action_log) def _actionLogCleaned(self, action_log): self._syncDoUndo(action_log) def _syncDoUndo(self, action_log): can_undo = bool(action_log.undo_stacks) self.undo_action.set_enabled(can_undo) can_redo = bool(action_log.redo_stacks) self.redo_action.set_enabled(can_redo) dirty = action_log.dirty() 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()