Пример #1
0
    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)
Пример #2
0
 def setUp(self):
     self.timeline = GES.Timeline.new_audio_video()
     self.layer = GES.Layer()
     self.timeline.add_layer(self.layer)
     self.action_log = UndoableActionLog()
     self.observer = TimelineLogObserverSpy(self.action_log)
     self.observer.startObserving(self.timeline)
Пример #3
0
    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)
Пример #4
0
    def setUp(self):
        app = Pitivi()
        app._startupCb(app)
        app.project_manager.newBlankProject()

        self.timeline = app.project_manager.current_project.timeline
        self.layer = GES.Layer()
        self.timeline.add_layer(self.layer)
        self.action_log = UndoableActionLog()
        self.observer = TimelineLogObserverSpy(self.action_log)
        self.observer.startObserving(self.timeline)
Пример #5
0
 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)
Пример #6
0
    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 = EffectsHandler()
        self.system = getSystem()

        self.action_log = UndoableActionLog()
        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()
Пример #7
0
    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)
Пример #8
0
    def __init__(self):
        Loggable.__init__(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
        log.init('PITIVI_DEBUG', enable_color, enable_crack_output)

        self.info('starting up')

        self.settings = GlobalSettings()
        self.threads = ThreadMaster()
        self.effects = EffectsHandler()
        self.system = getSystem()

        self.current_project = None
        self.projectManager = ProjectManager(self)
        self._connectToProjectManager(self.projectManager)

        self.action_log = UndoableActionLog()
        self.debug_action_log_observer = DebugActionLogObserver()
        self.debug_action_log_observer.startObserving(self.action_log)
        # TODO reimplement the observing after GES port
        #self.timelineLogObserver = TimelineLogObserver(self.action_log)
        self.projectLogObserver = ProjectLogObserver(self.action_log)

        self._version_information = {}
        self._checkVersion()
Пример #9
0
    def test_keyframe_toggle(self):
        """Checks keyframes toggling at the playhead position."""
        timeline_container = create_timeline_container()
        timeline_container.app.action_log = UndoableActionLog()
        timeline = timeline_container.timeline
        ges_layer = timeline.ges_timeline.append_layer()
        ges_clip1 = self.add_clip(ges_layer, 0, duration=2 * Gst.SECOND)
        ges_clip2 = self.add_clip(ges_layer, 10, duration=2 * Gst.SECOND)
        ges_clip3 = self.add_clip(ges_layer,
                                  20,
                                  inpoint=100,
                                  duration=2 * Gst.SECOND)
        # For variety, add TitleClip to the list of clips.
        ges_clip4 = create_test_clip(GES.TitleClip)
        ges_clip4.props.start = 30
        ges_clip4.props.duration = int(0.9 * Gst.SECOND)
        ges_layer.add_clip(ges_clip4)

        self.check_keyframe_toggle(ges_clip1, timeline_container)
        self.check_keyframe_toggle(ges_clip2, timeline_container)
        self.check_keyframe_toggle(ges_clip3, timeline_container)
        self.check_keyframe_toggle(ges_clip4, timeline_container)

        for ges_clip in [ges_clip1, ges_clip2, ges_clip3, ges_clip4]:
            self.check_keyframe_ui_toggle(ges_clip, timeline_container)
Пример #10
0
    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

        Zoomable.app = self
        self.connect("startup", self._startupCb)
        self.connect("activate", self._activateCb)
        self.connect("open", self.openCb)
Пример #11
0
    def test_keyframe_toggle(self):
        """Checks keyframes toggling at the playhead position."""
        timeline_container = common.create_timeline_container()
        timeline_container.app.action_log = UndoableActionLog()
        timeline = timeline_container.timeline
        ges_layer = timeline.ges_timeline.append_layer()
        ges_clip1 = self.add_clip(ges_layer, 0, duration=Gst.SECOND)
        ges_clip2 = self.add_clip(ges_layer, Gst.SECOND, duration=Gst.SECOND)
        ges_clip3 = self.add_clip(ges_layer,
                                  2 * Gst.SECOND,
                                  inpoint=Gst.SECOND,
                                  duration=Gst.SECOND)

        # For variety, add TitleClip to the list of clips.
        ges_clip4 = common.create_test_clip(GES.TitleClip)
        ges_clip4.props.start = 3 * Gst.SECOND
        ges_clip4.props.duration = Gst.SECOND
        self.assertTrue(ges_layer.add_clip(ges_clip4))

        self.check_keyframe_toggle(ges_clip1, timeline_container)
        self.check_keyframe_toggle(ges_clip2, timeline_container)
        self.check_keyframe_toggle(ges_clip3, timeline_container)
        self.check_keyframe_toggle(ges_clip4, timeline_container)

        self.check_keyframe_ui_toggle(ges_clip1, timeline_container)
        self.check_keyframe_ui_toggle(ges_clip2, timeline_container)
        self.check_keyframe_ui_toggle(ges_clip3, timeline_container)
        self.check_keyframe_ui_toggle(ges_clip4, timeline_container)
Пример #12
0
    def __init__(self):
        """
        initialize pitivi with the command line arguments
        """
        Loggable.__init__(self)

        # init logging as early as possible so we can log startup code
        enable_color = os.environ.get('PITIVI_DEBUG_NO_COLOR',
                                      '0') in ('', '0')
        # 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')

        # store ourself in the instance global
        if instance.PiTiVi:
            raise RuntimeWarning(
                _("There is already a %s instance, please inform "
                  "the developers by filing a bug at "
                  "http://bugzilla.gnome.org/enter_bug.cgi?product=pitivi") %
                APPNAME)
        instance.PiTiVi = self

        self.current = None

        # get settings
        self.settings = GlobalSettings()
        self.threads = ThreadMaster()
        #self.screencast = False

        self.effects = EffectsHandler()
        self.system = getSystem()

        self.projectManager = ProjectManager(self)
        self._connectToProjectManager(self.projectManager)

        self.action_log = UndoableActionLog()
        self.debug_action_log_observer = DebugActionLogObserver()
        self.debug_action_log_observer.startObserving(self.action_log)
        # TODO reimplement the observing after GES port
        #self.timelineLogObserver = TimelineLogObserver(self.action_log)
        self.projectLogObserver = ProjectLogObserver(self.action_log)

        self.version_information = {}
        self._checkVersion()
Пример #13
0
 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.
         try:
             self.recent_manager.remove_item(uri)
         except GLib.Error as e:
             if e.domain != "gtk-recent-manager-error-quark":
                 raise e
             pass
         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)
Пример #14
0
    def setUp(self):
        super().setUp()

        timeline_container = common.create_timeline_container()
        self.ges_timeline = timeline_container.timeline.ges_timeline

        timeline_container.app.action_log = UndoableActionLog()
        self.timeline_observer = TimelineObserver(
            timeline_container.ges_timeline, timeline_container.app.action_log)
Пример #15
0
    def test_property_change(self):
        action_log = UndoableActionLog()
        action_log.begin("complex stuff")
        stack, = action_log.stacks

        clip = GES.TitleClip()
        unused_observer = GObjectObserver(clip, ["start"], action_log)

        self.assertEqual(len(stack.done_actions), 0)
        clip.props.start = 2
        self.assertEqual(len(stack.done_actions), 1)

        clip.props.start = 2
        self.assertEqual(len(stack.done_actions), 1)

        clip.props.start = 4
        self.assertEqual(len(stack.done_actions), 2)
        action = stack.done_actions[-1]
        self.assertEqual(action.old_value, 2)
        self.assertEqual(action.new_value, 4)
Пример #16
0
    def test_property_change(self):
        action_log = UndoableActionLog()
        action_log.begin("complex stuff")
        stack, = action_log.stacks

        clip = GES.TitleClip()
        unused_observer = GObjectObserver(clip, ["start"], action_log)

        self.assertEqual(len(stack.done_actions), 0)
        clip.props.start = 2
        self.assertEqual(len(stack.done_actions), 1)

        clip.props.start = 2
        self.assertEqual(len(stack.done_actions), 1)

        clip.props.start = 4
        self.assertEqual(len(stack.done_actions), 2)
        action = stack.done_actions[-1]
        self.assertEqual(action.old_value, 2)
        self.assertEqual(action.new_value, 4)
Пример #17
0
    def setUp(self):
        app = Pitivi()
        app._startupCb(app)
        app.project_manager.newBlankProject()

        self.timeline = app.project_manager.current_project.timeline
        self.layer = GES.Layer()
        self.timeline.add_layer(self.layer)
        self.action_log = UndoableActionLog()
        self.observer = TimelineLogObserverSpy(self.action_log)
        self.observer.startObserving(self.timeline)
Пример #18
0
    def __init__(self):
        """
        initialize pitivi with the command line arguments
        """
        Loggable.__init__(self)

        # init logging as early as possible so we can log startup code
        enable_color = os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '0')
        # 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')

        # store ourself in the instance global
        if instance.PiTiVi:
            raise RuntimeWarning(_("There is already a %s instance, please inform "
                "the developers by filing a bug at "
                "http://bugzilla.gnome.org/enter_bug.cgi?product=pitivi")
                % APPNAME)
        instance.PiTiVi = self

        self.current = None

        # get settings
        self.settings = GlobalSettings()
        self.threads = ThreadMaster()
        #self.screencast = False

        self.effects = EffectsHandler()
        self.system = getSystem()

        self.projectManager = ProjectManager(self.effects)
        self._connectToProjectManager(self.projectManager)

        self.action_log = UndoableActionLog()
        self.debug_action_log_observer = DebugActionLogObserver()
        self.debug_action_log_observer.startObserving(self.action_log)
        # TODO reimplement the observing after GES port
        #self.timelineLogObserver = TimelineLogObserver(self.action_log)
        self.projectLogObserver = ProjectLogObserver(self.action_log)
        self.medialibrary_log_observer = MediaLibraryLogObserver(self.action_log)

        self.version_information = {}
        self._checkVersion()
Пример #19
0
class TestUndoableActionLog(common.TestCase):
    """Tests for the UndoableActionLog class."""

    def setUp(self):
        self.log = UndoableActionLog()
        self._connect_to_undoable_action_log(self.log)
        self.signals = []

    def tearDown(self):
        self._disconnect_from_undoable_action_log()

    def check_signals(self, *expected_signals):
        signals = [item[0] for item in self.signals]
        self.assertListEqual(signals, list(expected_signals))

    def _undo_action_log_signal_cb(self, log, *args):
        args = list(args)
        signal_name = args.pop(-1)
        self.signals.append((signal_name, args))

    def _connect_to_undoable_action_log(self, log):
        for signal_name in ("begin", "push", "rollback", "commit", "move"):
            log.connect(signal_name, self._undo_action_log_signal_cb, signal_name)

    def _disconnect_from_undoable_action_log(self):
        self.log.disconnect_by_func(self._undo_action_log_signal_cb)

    def test_rollback_wrong_state(self):
        self.assertRaises(UndoWrongStateError, self.log.rollback)

    def test_commit_wrong_state(self):
        self.assertRaises(UndoWrongStateError, self.log.commit, "")

    def test_push_wrong_state(self):
        # no error in this case
        self.log.push(None)

    def test_undo_wrong_state(self):
        self.assertRaises(UndoWrongStateError, self.log.undo)

    def test_redo_wrong_state(self):
        self.assertRaises(UndoWrongStateError, self.log.redo)

    def test_checkpoint(self):
        self.log.begin("meh")
        self.log.push(mock.Mock(spec=UndoableAction))
        self.assertRaises(UndoWrongStateError, self.log.checkpoint)
        self.log.rollback()
        self.log.checkpoint()
        self.assertNotEqual(self.log._checkpoint, None)

    def test_dirty(self):
        self.assertFalse(self.log.dirty())
        self.log.begin("meh")
        self.log.push(mock.Mock(spec=UndoableAction))
        self.log.commit("meh")
        self.assertTrue(self.log.dirty())
        self.log.checkpoint()
        self.assertFalse(self.log.dirty())
        self.log.undo()
        self.assertTrue(self.log.dirty())
        self.log.redo()
        self.assertFalse(self.log.dirty())

    def test_commit(self):
        """Checks committing a stack."""
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (_stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(self.log.undo_stacks, [])
        self.log.push(mock.Mock(spec=UndoableAction))
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 3)
        name, (_stack, _action) = self.signals[1]
        self.assertEqual(name, "push")
        name, (_stack,) = self.signals[2]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_commit_proper(self):
        self.log.begin("meh")
        self.assertRaises(UndoWrongStateError, self.log.commit, "notmeh")

    def test_nested_commit(self):
        """Checks two nested commits."""
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (_stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (_stack,) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(self.log.undo_stacks, [])
        self.log.push(mock.Mock(spec=UndoableAction))
        self.log.commit("nested")
        self.assertEqual(len(self.signals), 4)
        name, (_stack, _action) = self.signals[2]
        self.assertEqual(name, "push")
        name, (_stack,) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertTrue(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 5)
        name, (_stack,) = self.signals[4]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_finalizing_action(self):
        action1 = mock.Mock()
        action2 = mock.Mock()
        with self.log.started("one", finalizing_action=action1):
            self.log.push(mock.Mock(spec=UndoableAction))
            with self.log.started("two", finalizing_action=action2):
                self.log.push(mock.Mock(spec=UndoableAction))
        action1.do.assert_called_once_with()
        # For now, we call the finalizing action only for the top stack.
        action2.do.assert_not_called()

    def test_rollback(self):
        """Checks a rollback."""
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.check_signals("begin")
        name, (_stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        action = mock.Mock(spec=UndoableAction)
        self.log.push(action)

        self.log.rollback()

        action.undo.assert_called_once_with()

        self.check_signals("begin", "push", "rollback")
        name, (_stack,) = self.signals[2]
        self.assertEqual(name, "rollback")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_rollback_noop(self):
        """Checks a rollback which does not act."""
        self.log.begin("meh")

        action = mock.Mock(spec=UndoableAction)
        self.log.push(action)

        self.log.rollback(undo=False)
        action.undo.assert_not_called()

    def test_nested_rollback(self):
        """Checks two nested rollbacks."""
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (_stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (_stack,) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.log.rollback()
        self.assertEqual(len(self.signals), 3)
        name, (_stack,) = self.signals[2]
        self.assertEqual(name, "rollback")
        self.assertTrue(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.log.rollback()
        self.assertEqual(len(self.signals), 4)
        name, (_stack,) = self.signals[3]
        self.assertEqual(name, "rollback")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_undo_redo(self):
        """Tries an undo() redo() sequence."""
        # begin
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (_stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        # push two actions
        action1 = mock.Mock(spec=UndoableAction)
        action1.expand.return_value = False
        self.log.push(action1)
        self.assertEqual(len(self.signals), 2)
        name, (_stack, signal_action) = self.signals[1]
        self.assertEqual(name, "push")
        self.assertTrue(action1 is signal_action)

        action2 = mock.Mock(spec=UndoableAction)
        self.log.push(action2)
        self.assertEqual(len(self.signals), 3)
        name, (_stack, signal_action) = self.signals[2]
        self.assertEqual(name, "push")
        self.assertTrue(action2 is signal_action)

        # commit
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 4)
        name, (_stack,) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.assertEqual(action1.do.call_count, 0)
        self.assertEqual(action1.undo.call_count, 0)
        self.assertEqual(action2.do.call_count, 0)
        self.assertEqual(action2.undo.call_count, 0)

        # undo what we just committed
        self.log.undo()
        self.assertEqual(len(self.signals), 5)
        name, _args = self.signals[4]
        self.assertEqual(name, "move")
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 1)
        self.assertEqual(action1.do.call_count, 0)
        self.assertEqual(action1.undo.call_count, 1)
        self.assertEqual(action2.do.call_count, 0)
        self.assertEqual(action2.undo.call_count, 1)

        # redo
        self.log.redo()
        self.assertEqual(len(self.signals), 6)
        name, _args = self.signals[5]
        self.assertEqual(name, "move")
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.assertEqual(action1.do.call_count, 1)
        self.assertEqual(action1.undo.call_count, 1)
        self.assertEqual(action2.do.call_count, 1)
        self.assertEqual(action2.undo.call_count, 1)

    def test_order(self):
        """Checks actions are undone and redone in the correct order."""
        order = mock.Mock()
        order.action1 = mock.Mock(spec=UndoableAction)
        order.action1.expand.return_value = False
        order.action2 = mock.Mock(spec=UndoableAction)
        order.action2.expand.return_value = False
        order.action3 = mock.Mock(spec=UndoableAction)
        order.action3.expand.return_value = False

        with self.log.started("meh"):
            self.log.push(order.action1)
            with self.log.started("nested"):
                self.log.push(order.action2)
            self.log.push(order.action3)

        self.log.undo()
        order.assert_has_calls([mock.call.action3.undo(),
                                mock.call.action2.undo(),
                                mock.call.action1.undo()])

        self.log.redo()
        order.assert_has_calls([mock.call.action1.do(),
                                mock.call.action2.do(),
                                mock.call.action3.do()])

        self.log.undo()
        order.assert_has_calls([mock.call.action3.undo(),
                                mock.call.action2.undo(),
                                mock.call.action1.undo()])

    def test_toplevel_operation(self):
        """Checks the toplevel operations nesting."""
        self.log.begin("one", toplevel=False)
        self.log.commit("one")

        self.log.begin("two", toplevel=True)
        self.log.commit("two")

        self.log.begin("three")
        self.assertRaises(UndoWrongStateError,
                          self.log.begin,
                          "four", toplevel=True)
        self.log.begin("nested1")
        self.log.begin("nested2", toplevel=False)

    def test_failing_operation_rollback(self):
        """Checks that failing operations are rolled back."""
        action = mock.Mock(spec=UndoableAction)

        class WatchingError(Exception):
            pass

        with self.assertRaises(WatchingError):
            with self.log.started("failing_op"):
                self.log.push(action)
                raise WatchingError()

        # Check the rollback happened
        self.assertEqual(action.do.call_count, 0)
        self.assertEqual(action.undo.call_count, 1)
        # Check the undo and redo stacks are empty
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_merging(self):
        with self.log.started("one", mergeable=False):
            action = mock.Mock(spec=UndoableAction)
            action.expand.side_effect = Exception("should not have been called")
            self.log.push(action)
        self.assertEqual(len(self.log.undo_stacks), 1)

        with self.log.started("one", mergeable=True):
            action = mock.Mock(spec=UndoableAction)
            action.expand.return_value = False
            self.log.push(action)
        self.assertEqual(len(self.log.undo_stacks), 2)

        with self.log.started("one", mergeable=True):
            action = mock.Mock(spec=UndoableAction)
            action.expand.return_value = True
            self.log.push(action)
        self.assertEqual(len(self.log.undo_stacks), 3)

        with self.log.started("one", mergeable=True):
            action = mock.Mock(spec=UndoableAction)
            action.expand.return_value = True
            self.log.push(action)
        self.assertEqual(len(self.log.undo_stacks), 3)

        with self.log.started("one", mergeable=False):
            action = mock.Mock(spec=UndoableAction)
            action.expand.side_effect = Exception("should not have been called")
            self.log.push(action)
        self.assertEqual(len(self.log.undo_stacks), 4)
Пример #20
0
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()
Пример #21
0
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)
Пример #22
0
class TestTimelineUndo(TestCase):

    def setUp(self):
        app = Pitivi()
        app._startupCb(app)
        app.project_manager.newBlankProject()

        self.timeline = app.project_manager.current_project.timeline
        self.layer = GES.Layer()
        self.timeline.add_layer(self.layer)
        self.action_log = UndoableActionLog()
        self.observer = TimelineLogObserverSpy(self.action_log)
        self.observer.startObserving(self.timeline)

    def getTimelineClips(self):
        for layer in self.timeline.layers:
            for clip in layer.get_clips():
                yield clip

    @staticmethod
    def commitCb(action_log, stack, nested, stacks):
        stacks.append(stack)

    def testAddClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.action_log.begin("add clip")
        self.layer.add_clip(clip1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions))
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, ClipAdded))
        self.assertTrue(clip1 in self.getTimelineClips())

        self.action_log.undo()
        self.assertFalse(clip1 in self.getTimelineClips())

        self.action_log.redo()
        self.assertTrue(clip1 in self.getTimelineClips())

    def testRemoveClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)
        self.action_log.begin("remove clip")
        self.layer.remove_clip(clip1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions))
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, ClipRemoved))
        self.assertFalse(clip1 in self.getTimelineClips())

        self.action_log.undo()
        self.assertTrue(clip1 in self.getTimelineClips())

        self.action_log.redo()
        self.assertFalse(clip1 in self.getTimelineClips())

    def testAddEffectToClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)

        effect1 = GES.Effect.new("agingtv")
        self.action_log.begin("add effect")
        clip1.add(effect1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions), stack.done_actions)
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, TrackElementAdded))

        self.assertTrue(effect1 in clip1.get_children(True))
        self.assertEqual(1, len([effect for effect in
                                 clip1.get_children(True)
                                 if isinstance(effect, GES.Effect)]))

        self.action_log.undo()
        self.assertFalse(effect1 in clip1.get_children(True))

        self.action_log.redo()
        self.assertEqual(1, len([effect for effect in
                                 clip1.get_children(True)
                                 if isinstance(effect, GES.Effect)]))

    def testRemoveEffectFromClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)

        effect1 = GES.Effect.new("agingtv")
        self.action_log.begin("add effect")
        clip1.add(effect1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions), stack.done_actions)
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, TrackElementAdded))

        self.assertTrue(effect1 in clip1.get_children(True))
        self.assertEqual(1, len([effect for effect in
                                 clip1.get_children(True)
                                 if isinstance(effect, GES.Effect)]))

        self.action_log.begin("remove effect")
        clip1.remove(effect1)
        self.action_log.commit()

        self.assertEqual(0, len([effect for effect in
                                 clip1.get_children(True)
                                 if isinstance(effect, GES.Effect)]))

        self.action_log.undo()
        self.assertEqual(1, len([effect for effect in
                                 clip1.get_children(True)
                                 if isinstance(effect, GES.Effect)]))

        self.action_log.redo()
        self.assertEqual(0, len([effect for effect in
                                 clip1.get_children(True)
                                 if isinstance(effect, GES.Effect)]))

    def testChangeEffectProperty(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)

        effect1 = GES.Effect.new("agingtv")
        self.action_log.begin("add effect")
        clip1.add(effect1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions), stack.done_actions)
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, TrackElementAdded))

        self.assertTrue(effect1 in clip1.get_children(True))
        self.assertEqual(1, len([effect for effect in
                                 clip1.get_children(True)
                                 if isinstance(effect, GES.Effect)]))

        self.action_log.begin("change child property")
        effect1.set_child_property("scratch-lines", 0)
        self.action_log.commit()

        self.assertEqual(effect1.get_child_property("scratch-lines")[1], 0)
        self.action_log.undo()
        self.assertEqual(effect1.get_child_property("scratch-lines")[1], 7)
        self.action_log.redo()
        self.assertEqual(effect1.get_child_property("scratch-lines")[1], 0)
        self.action_log.undo()
        self.assertTrue(effect1 in clip1.get_children(True))
        self.action_log.undo()
        self.assertFalse(effect1 in clip1.get_children(True))

    def testClipPropertyChange(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)
        clip1.set_start(5 * Gst.SECOND)
        clip1.set_duration(20 * Gst.SECOND)
        self.layer.add_clip(clip1)
        self.action_log.begin("modify clip")
        clip1.set_start(10 * Gst.SECOND)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions))
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, ClipPropertyChanged))
        self.assertEqual(10 * Gst.SECOND, clip1.get_start())

        self.action_log.undo()
        self.assertEqual(5 * Gst.SECOND, clip1.get_start())
        self.action_log.redo()
        self.assertEqual(10 * Gst.SECOND, clip1.get_start())

        clip1.set_priority(10)
        self.action_log.begin("priority change")
        clip1.set_priority(20)
        self.action_log.commit()

        self.assertEqual(20, clip1.get_priority())
        self.action_log.undo()
        self.assertEqual(10, clip1.get_priority())
        self.action_log.redo()
        self.assertEqual(20, clip1.get_priority())

    def testUngroup(self):
        uri = common.getSampleUri("tears_of_steel.webm")
        asset = GES.UriClipAsset.request_sync(uri)
        clip1 = asset.extract()
        self.layer.add_clip(clip1)

        clip1.set_start(5 * Gst.SECOND)
        clip1.set_duration(0.5 * Gst.SECOND)
        timeline_clips = list(self.getTimelineClips())
        self.assertEqual(1, len(timeline_clips), timeline_clips)
        self.assertEqual(5 * Gst.SECOND, timeline_clips[0].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[0].get_duration())

        self.action_log.begin("ungroup")
        ungrouped = GES.Container.ungroup(clip1, False)
        self.assertEqual(2, len(ungrouped), ungrouped)
        self.action_log.commit()
        timeline_clips = list(self.getTimelineClips())
        self.assertEqual(2, len(timeline_clips), timeline_clips)
        self.assertEqual(5 * Gst.SECOND, timeline_clips[0].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[0].get_duration())
        self.assertEqual(5 * Gst.SECOND, timeline_clips[1].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[1].get_duration())

        self.action_log.undo()
        timeline_clips = list(self.getTimelineClips())
        self.assertEqual(1, len(timeline_clips))
        self.assertEqual(5 * Gst.SECOND, timeline_clips[0].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[0].get_duration())

    def testSplitClip(self):
        clip = GES.TitleClip()
        clip.set_start(0 * Gst.SECOND)
        clip.set_duration(20 * Gst.SECOND)

        self.layer.add_clip(clip)

        self.action_log.begin("split clip")
        clip1 = clip.split(10 * Gst.SECOND)
        self.assertEqual(2, len(self.layer.get_clips()))
        self.action_log.commit()

        self.action_log.begin("split clip")
        clip2 = clip1.split(15 * Gst.SECOND)
        self.assertEqual(3, len(self.layer.get_clips()))
        self.action_log.commit()

        self.action_log.undo()
        self.assertEqual(2, len(self.layer.get_clips()))
        self.action_log.undo()
        self.assertEqual(1, len(self.layer.get_clips()))

        self.action_log.redo()
        self.assertEqual(2, len(self.layer.get_clips()))
        self.action_log.redo()
        self.assertEqual(3, len(self.layer.get_clips()))
Пример #23
0
    def test_axis_lock(self):
        """Checks keyframes moving."""
        timeline_container = common.create_timeline_container()
        timeline_container.app.action_log = UndoableActionLog()
        timeline = timeline_container.timeline
        timeline.get_window = mock.Mock()
        pipeline = timeline._project.pipeline
        ges_layer = timeline.ges_timeline.append_layer()
        ges_clip = self.add_clip(ges_layer, 0, duration=Gst.SECOND)

        start = ges_clip.props.start
        inpoint = ges_clip.props.in_point
        duration = ges_clip.props.duration
        timeline.selection.select([ges_clip])

        ges_video_source = ges_clip.find_track_element(None, GES.VideoSource)
        binding = ges_video_source.get_control_binding("alpha")
        control_source = binding.props.control_source
        keyframe_curve = ges_video_source.ui.keyframe_curve
        values = [item.timestamp for item in control_source.get_all()]
        self.assertEqual(values, [inpoint, inpoint + duration])

        # Add a keyframe.
        position = start + int(duration / 2)
        with mock.patch.object(pipeline, "get_position") as get_position:
            get_position.return_value = position
            timeline_container._keyframe_cb(None, None)

        # Start dragging the keyframe.
        x, y = keyframe_curve._ax.transData.transform((position, 1))
        event = MouseEvent(
            name="button_press_event",
            canvas=keyframe_curve,
            x=x,
            y=y,
            button=MouseButton.LEFT
        )
        event.guiEvent = Gdk.Event.new(Gdk.EventType.BUTTON_PRESS)
        self.assertIsNone(keyframe_curve._offset)
        keyframe_curve._mpl_button_press_event_cb(event)
        self.assertIsNotNone(keyframe_curve._offset)

        # Drag and make sure x and y are not locked.
        timeline_container.control_mask = False
        event = mock.Mock(
            x=x + 1,
            y=y + 1,
            xdata=position + 1,
            ydata=0.9,
        )
        with mock.patch.object(keyframe_curve,
                               "_move_keyframe") as _move_keyframe:
            keyframe_curve._mpl_motion_event_cb(event)
            # Check the keyframe is moved exactly where the cursor is.
            _move_keyframe.assert_called_once_with(position, position + 1, 0.9)

        # Drag locked horizontally.
        timeline_container.control_mask = True
        event = mock.Mock(
            x=x + 1,
            y=y + 2,
            xdata=position + 2,
            ydata=0.8,
        )
        with mock.patch.object(keyframe_curve,
                               "_move_keyframe") as _move_keyframe:
            keyframe_curve._mpl_motion_event_cb(event)
            # Check the keyframe is kept on the same timestamp.
            _move_keyframe.assert_called_once_with(position + 1, position, 0.8)

        # Drag locked vertically.
        timeline_container.control_mask = True
        event = mock.Mock(
            x=x + 2,
            y=y + 1,
            xdata=position + 3,
            ydata=0.7,
        )
        with mock.patch.object(keyframe_curve,
                               "_move_keyframe") as _move_keyframe:
            keyframe_curve._mpl_motion_event_cb(event)
            # Check the keyframe is kept on the same value.
            _move_keyframe.assert_called_once_with(position, position + 3, 1)
Пример #24
0
class Pitivi(Loggable, Signallable):
    """
    Pitivi's main application class.

    Signals:
     - C{new-project} : A new C{Project} is loaded and ready to use.

     - C{new-project-loading} : Pitivi is attempting to load a new project.
     - C{new-project-loaded} : A new L{Project} has been loaded, and the UI should refresh it's view.
     - C{new-project-failed} : A new L{Project} failed to load.
     - C{closing-project} :  pitivi would like to close a project. handlers should return false
     if they do not want this project to close. by default, assumes
     true. This signal should only be used by classes that might want to abort
     the closing of a project.
     - C{project-closed} : The project is closed, it will be freed when the callback returns.
     Classes should connect to this instance when they want to know that
     data related to that project is no longer going to be used.
     - C{shutdown} : Used internally, do not use this signal.`

    @ivar settings: Application-wide settings.
    @type settings: L{GlobalSettings}.
    @ivar current: Currently used project.
    @type current: L{Project}.
    """

    __signals__ = {
        "new-project": ["project"],
        "new-project-loading": ["uri"],
        "new-project-created": ["project"],
        "new-project-loaded": ["project"],
        "new-project-failed": ["uri", "exception"],
        "closing-project": ["project"],
        "project-closed": ["project"],
        "missing-uri": ["formatter", "uri"],
        "version-info-received": ["versions"],
        "shutdown": None}

    def __init__(self):
        """
        initialize pitivi with the command line arguments
        """
        Loggable.__init__(self)

        # init logging as early as possible so we can log startup code
        enable_color = os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '0')
        # 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')

        # store ourself in the instance global
        if instance.PiTiVi:
            raise RuntimeWarning(_("There is already a %s instance, please inform "
                "the developers by filing a bug at "
                "http://bugzilla.gnome.org/enter_bug.cgi?product=pitivi")
                % APPNAME)
        instance.PiTiVi = self

        self.current = None

        # get settings
        self.settings = GlobalSettings()
        self.threads = ThreadMaster()
        #self.screencast = False

        self.effects = EffectsHandler()
        self.system = getSystem()

        self.projectManager = ProjectManager(self.effects)
        self._connectToProjectManager(self.projectManager)

        self.action_log = UndoableActionLog()
        self.debug_action_log_observer = DebugActionLogObserver()
        self.debug_action_log_observer.startObserving(self.action_log)
        # TODO reimplement the observing after GES port
        #self.timelineLogObserver = TimelineLogObserver(self.action_log)
        self.projectLogObserver = ProjectLogObserver(self.action_log)
        self.medialibrary_log_observer = MediaLibraryLogObserver(self.action_log)

        self.version_information = {}
        self._checkVersion()

    def shutdown(self):
        """
        Close PiTiVi.

        @return: C{True} if PiTiVi was successfully closed, else C{False}.
        @rtype: C{bool}
        """
        self.debug("shutting down")
        # we refuse to close if we're running a user interface and the user
        # doesn't want us to close the current project.
        if self.current and not self.projectManager.closeRunningProject():
            self.warning("Not closing since running project doesn't want to close")
            return False
        self.threads.stopAllThreads()
        self.settings.storeSettings()
        self.current = None
        instance.PiTiVi = None
        self.emit("shutdown")
        return True

    def _connectToProjectManager(self, projectManager):
        projectManager.connect("new-project-loading",
                self._projectManagerNewProjectLoading)
        projectManager.connect("new-project-created",
                self._projectManagerNewProjectCreated)
        projectManager.connect("new-project-loaded",
                self._projectManagerNewProjectLoaded)
        projectManager.connect("new-project-failed",
                self._projectManagerNewProjectFailed)
        projectManager.connect("closing-project",
                self._projectManagerClosingProject)
        projectManager.connect("project-closed",
                self._projectManagerProjectClosed)

    def _projectManagerNewProjectLoading(self, projectManager, uri):
        self.emit("new-project-loading", uri)

    def _projectManagerNewProjectCreated(self, projectManager, project):
        self.current = project
        self.emit("new-project-created", project)

    def _newProjectLoaded(self, project):
        pass

    def _projectManagerNewProjectLoaded(self, projectManager, project):
        self.current = project
        self.action_log.clean()
        #self.timelineLogObserver.startObserving(project.timeline)
        self.projectLogObserver.startObserving(project)
        self.medialibrary_log_observer.startObserving(project.medialibrary)
        self._newProjectLoaded(project)
        self.emit("new-project-loaded", project)

    def _projectManagerNewProjectFailed(self, projectManager, uri, exception):
        self.emit("new-project-failed", uri, exception)

    def _projectManagerClosingProject(self, projectManager, project):
        return self.emit("closing-project", project)

    def _projectManagerProjectClosed(self, projectManager, project):
        #self.timelineLogObserver.stopObserving(project.timeline)
        self.projectLogObserver.stopObserving(project)
        self.current = None
        self.emit("project-closed", project)

    # check if for version information online
    def _checkVersion(self):
        giofile = Gio.File.new_for_uri(RELEASES_URL)
        self.info("Requesting version information")
        giofile.load_contents_async(None, self._versionInfoReceivedCb, None)

    def _versionInfoReceivedCb(self, giofile, result, data):
        try:
            # split data in lines
            raw = giofile.load_contents_finish(result)[0].split("\n")
            # split line at '=' if not empty or comment
            data = [element.split("=") for element in raw
                    if element and not element.startswith("#")]

            # search newest version and status
            status = "UNSUPPORTED"
            for version, version_status in data:
                if pitivi_version == version:
                    status = version_status
                if version_status.upper() == "CURRENT":
                    current_version = version

            self.info("Version information received")
            self.version_information["current"] = current_version
            self.version_information["status"] = status
            self.emit("version-info-received", self.version_information)
        except:
            self.warning("Version information could not be read")
Пример #25
0
class Pitivi(Loggable, Signallable):
    """
    Pitivi's main application class.

    Signals:
     - C{new-project} : A new C{Project} is loaded and ready to use.

     - C{new-project-loading} : Pitivi is attempting to load a new project.
     - C{new-project-loaded} : A new L{Project} has been loaded, and the UI should refresh it's view.
     - C{new-project-failed} : A new L{Project} failed to load.
     - C{project-closed} : The project is closed, it will be freed when the callback returns.
     Classes should connect to this instance when they want to know that
     data related to that project is no longer going to be used.
     - C{shutdown} : Used internally, do not use this signal.`

    @ivar settings: Application-wide settings.
    @type settings: L{GlobalSettings}.
    @ivar current: Currently used project.
    @type current: L{Project}.
    """

    __signals__ = {
        "new-project": ["project"],
        "new-project-loading": ["uri"],
        "new-project-created": ["project"],
        "new-project-loaded": ["project"],
        "new-project-failed": ["uri", "exception"],
        "project-closed": ["project"],
        "missing-uri": ["formatter", "uri"],
        "version-info-received": ["versions"],
        "shutdown": None}

    def __init__(self):
        Loggable.__init__(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
        log.init('PITIVI_DEBUG', enable_color, enable_crack_output)

        self.info('starting up')

        self.settings = GlobalSettings()
        self.threads = ThreadMaster()
        self.effects = EffectsHandler()
        self.system = getSystem()

        self.current_project = None
        self.projectManager = ProjectManager(self)
        self._connectToProjectManager(self.projectManager)

        self.action_log = UndoableActionLog()
        self.debug_action_log_observer = DebugActionLogObserver()
        self.debug_action_log_observer.startObserving(self.action_log)
        # TODO reimplement the observing after GES port
        #self.timelineLogObserver = TimelineLogObserver(self.action_log)
        self.projectLogObserver = ProjectLogObserver(self.action_log)

        self._version_information = {}
        self._checkVersion()

    def shutdown(self):
        """
        Close Pitivi.

        @return: C{True} if Pitivi was successfully closed, else C{False}.
        @rtype: C{bool}
        """
        self.debug("shutting down")
        # we refuse to close if we're running a user interface and the user
        # doesn't want us to close the current project.
        if self.current_project and not self.projectManager.closeRunningProject():
            self.warning("Not closing since running project doesn't want to close")
            return False
        self.threads.stopAllThreads()
        self.settings.storeSettings()
        self.current_project = None
        self.emit("shutdown")
        return True

    def _connectToProjectManager(self, projectManager):
        pm = projectManager
        pm.connect("new-project-loading", self._projectManagerNewProjectLoading)
        pm.connect("new-project-created", self._projectManagerNewProjectCreated)
        pm.connect("new-project-loaded", self._projectManagerNewProjectLoaded)
        pm.connect("new-project-failed", self._projectManagerNewProjectFailed)
        pm.connect("project-closed", self._projectManagerProjectClosed)

    def _projectManagerNewProjectLoading(self, unused_project_manager, uri):
        self.emit("new-project-loading", uri)

    def _projectManagerNewProjectCreated(self, unused_project_manager, project):
        self.current_project = project
        self.emit("new-project-created", project)

    def _newProjectLoaded(self, unused_project):
        pass

    def _projectManagerNewProjectLoaded(self, unused_project_manager, project, unused_fully_loaded):
        self.current_project = project
        self.action_log.clean()
        #self.timelineLogObserver.startObserving(project.timeline)
        self.projectLogObserver.startObserving(project)
        self._newProjectLoaded(project)
        self.emit("new-project-loaded", project)

    def _projectManagerNewProjectFailed(self, unused_project_manager, uri, exception):
        self.emit("new-project-failed", uri, exception)

    def _projectManagerProjectClosed(self, unused_project_manager, project):
        #self.timelineLogObserver.stopObserving(project.timeline)
        self.projectLogObserver.stopObserving(project)
        self.current_project = None
        self.emit("project-closed", project)

    def _checkVersion(self):
        # Check online for release versions information
        giofile = Gio.File.new_for_uri(RELEASES_URL)
        self.info("Requesting version information")
        giofile.load_contents_async(None, self._versionInfoReceivedCb, None)

    def _versionInfoReceivedCb(self, giofile, result, user_data):
        try:
            raw = giofile.load_contents_finish(result)[1]
            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)
            if 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, e:
            self.warning("Version info could not be read: %s", e)
Пример #26
0
 def setUp(self):
     self.action_log = UndoableActionLog()
     self.observer = TimelineLogObserverSpy(self.action_log)
Пример #27
0
class TestUndoableActionLog(TestCase):

    def setUp(self):
        self.log = UndoableActionLog()
        self._connectToUndoableActionLog(self.log)
        self.signals = []

    def tearDown(self):
        self._disconnectFromUndoableActionLog(self.log)

    def _undoActionLogSignalCb(self, log, *args):
        args = list(args)
        signalName = args.pop(-1)
        self.signals.append((signalName, args))

    def _connectToUndoableActionLog(self, log):
        for signalName in ("begin", "push", "rollback", "commit", "move"):
            log.connect(signalName, self._undoActionLogSignalCb, signalName)

    def _disconnectFromUndoableActionLog(self, log):
        self.log.disconnect_by_func(self._undoActionLogSignalCb)

    def testRollbackWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.rollback)

    def testCommitWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.commit, "")

    def testPushWrongState(self):
        # no error in this case
        self.log.push(None)

    def testUndoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.undo)

    def testRedoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.redo)

    def testCheckpoint(self):
        self.log.begin("meh")
        self.log.push(mock.Mock(spec=UndoableAction))
        self.assertRaises(UndoWrongStateError, self.log.checkpoint)
        self.log.rollback()
        self.log.checkpoint()
        self.assertNotEqual(self.log._checkpoint, None)

    def testDirty(self):
        self.assertFalse(self.log.dirty())
        self.log.begin("meh")
        self.log.push(mock.Mock(spec=UndoableAction))
        self.log.commit("meh")
        self.assertTrue(self.log.dirty())
        self.log.checkpoint()
        self.assertFalse(self.log.dirty())
        self.log.undo()
        self.assertTrue(self.log.dirty())
        self.log.redo()
        self.assertFalse(self.log.dirty())

    def testCommit(self):
        """
        Commit a stack.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(self.log.undo_stacks, [])
        self.log.push(mock.Mock(spec=UndoableAction))
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 3)
        name, (stack, action) = self.signals[1]
        self.assertEqual(name, "push")
        name, (stack,) = self.signals[2]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_commit_proper(self):
        self.log.begin("meh")
        self.assertRaises(UndoWrongStateError, self.log.commit, "notmeh")

    def testNestedCommit(self):
        """
        Do two nested commits.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack,) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(self.log.undo_stacks, [])
        self.log.push(mock.Mock(spec=UndoableAction))
        self.log.commit("nested")
        self.assertEqual(len(self.signals), 4)
        name, (stack, action) = self.signals[2]
        self.assertEqual(name, "push")
        name, (stack,) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertTrue(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 5)
        name, (stack,) = self.signals[4]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_finalizing_action(self):
        action1 = mock.Mock()
        action2 = mock.Mock()
        with self.log.started("one", finalizing_action=action1):
            self.log.push(mock.Mock(spec=UndoableAction))
            with self.log.started("two", finalizing_action=action2):
                self.log.push(mock.Mock(spec=UndoableAction))
        action1.do.assert_called_once_with()
        # For now, we call the finalizing action only for the top stack.
        action2.do.assert_not_called()

    def testRollback(self):
        """
        Test a rollback.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.log.rollback()
        self.assertEqual(len(self.signals), 2)
        name, (stack,) = self.signals[1]
        self.assertEqual(name, "rollback")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testNestedRollback(self):
        """
        Test two nested rollbacks.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack,) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.log.rollback()
        self.assertEqual(len(self.signals), 3)
        name, (stack,) = self.signals[2]
        self.assertEqual(name, "rollback")
        self.assertTrue(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.log.rollback()
        self.assertEqual(len(self.signals), 4)
        name, (stack,) = self.signals[3]
        self.assertEqual(name, "rollback")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testUndoRedo(self):
        """
        Try an undo() redo() sequence.
        """
        # begin
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        # push two actions
        action1 = mock.Mock(spec=UndoableAction)
        action1.expand.return_value = False
        self.log.push(action1)
        self.assertEqual(len(self.signals), 2)
        name, (stack, signalAction) = self.signals[1]
        self.assertEqual(name, "push")
        self.assertTrue(action1 is signalAction)

        action2 = mock.Mock(spec=UndoableAction)
        self.log.push(action2)
        self.assertEqual(len(self.signals), 3)
        name, (stack, signalAction) = self.signals[2]
        self.assertEqual(name, "push")
        self.assertTrue(action2 is signalAction)

        # commit
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 4)
        name, (stack,) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.assertEqual(action1.do.call_count, 0)
        self.assertEqual(action1.undo.call_count, 0)
        self.assertEqual(action2.do.call_count, 0)
        self.assertEqual(action2.undo.call_count, 0)

        # undo what we just committed
        self.log.undo()
        self.assertEqual(len(self.signals), 5)
        name, stack = self.signals[4]
        self.assertEqual(name, "move")
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 1)
        self.assertEqual(action1.do.call_count, 0)
        self.assertEqual(action1.undo.call_count, 1)
        self.assertEqual(action2.do.call_count, 0)
        self.assertEqual(action2.undo.call_count, 1)

        # redo
        self.log.redo()
        self.assertEqual(len(self.signals), 6)
        name, stack = self.signals[5]
        self.assertEqual(name, "move")
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.assertEqual(action1.do.call_count, 1)
        self.assertEqual(action1.undo.call_count, 1)
        self.assertEqual(action2.do.call_count, 1)
        self.assertEqual(action2.undo.call_count, 1)

    def testOrder(self):
        """
        Test that actions are undone and redone in the correct order.
        """
        order = mock.Mock()
        order.action1 = mock.Mock(spec=UndoableAction)
        order.action1.expand.return_value = False
        order.action2 = mock.Mock(spec=UndoableAction)
        order.action2.expand.return_value = False
        order.action3 = mock.Mock(spec=UndoableAction)
        order.action3.expand.return_value = False

        with self.log.started("meh"):
            self.log.push(order.action1)
            with self.log.started("nested"):
                self.log.push(order.action2)
            self.log.push(order.action3)

        self.log.undo()
        order.assert_has_calls([mock.call.action3.undo(),
                                mock.call.action2.undo(),
                                mock.call.action1.undo()])

        self.log.redo()
        order.assert_has_calls([mock.call.action1.do(),
                                mock.call.action2.do(),
                                mock.call.action3.do()])

        self.log.undo()
        order.assert_has_calls([mock.call.action3.undo(),
                                mock.call.action2.undo(),
                                mock.call.action1.undo()])

    def test_toplevel_operation(self):
        """Checks the toplevel operations nesting."""
        self.log.begin("one", toplevel=False)
        self.log.commit("one")

        self.log.begin("two", toplevel=True)
        self.log.commit("two")

        self.log.begin("three")
        self.assertRaises(UndoWrongStateError,
                          self.log.begin,
                          "four", toplevel=True)
        self.log.begin("nested1")
        self.log.begin("nested2", toplevel=False)
Пример #28
0
class TestUndoableActionLog(TestCase):
    def setUp(self):
        self.log = UndoableActionLog()
        self._connectToUndoableActionLog(self.log)
        self.signals = []

    def tearDown(self):
        self._disconnectFromUndoableActionLog(self.log)

    def _undoActionLogSignalCb(self, log, *args):
        args = list(args)
        signalName = args.pop(-1)
        self.signals.append((signalName, args))

    def _connectToUndoableActionLog(self, log):
        for signalName in ("begin", "push", "rollback", "commit", "undo",
                           "redo"):
            log.connect(signalName, self._undoActionLogSignalCb, signalName)

    def _disconnectFromUndoableActionLog(self, log):
        self.log.disconnect_by_func(self._undoActionLogSignalCb)

    def testRollbackWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.rollback)

    def testCommitWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.commit)

    def testPushWrongState(self):
        # no error in this case
        self.log.push(None)

    def testUndoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.undo)

    def testRedoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.redo)

    def testCheckpoint(self):
        self.log.begin("meh")
        self.log.push(DummyUndoableAction())
        self.assertRaises(UndoWrongStateError, self.log.checkpoint)
        self.log.rollback()
        self.log.checkpoint()
        self.assertNotEqual(self.log._checkpoint, None)

    def testDirty(self):
        self.assertFalse(self.log.dirty())
        self.log.begin("meh")
        self.log.push(DummyUndoableAction())
        self.log.commit()
        self.assertTrue(self.log.dirty())
        self.log.checkpoint()
        self.assertFalse(self.log.dirty())
        self.log.undo()
        self.assertTrue(self.log.dirty())
        self.log.redo()
        self.assertFalse(self.log.dirty())

    def testCommit(self):
        """
        Commit a stack.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit()
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "commit")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testNestedCommit(self):
        """
        Do two nested commits.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(nested)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit()
        self.assertEqual(len(self.signals), 3)
        name, (stack, nested) = self.signals[2]
        self.assertEqual(name, "commit")
        self.assertTrue(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit()
        self.assertEqual(len(self.signals), 4)
        name, (stack, nested) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testRollback(self):
        """
        Test a rollback.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.log.rollback()
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "rollback")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testNestedRollback(self):
        """
        Test two nested rollbacks.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(nested)

        self.log.rollback()
        self.assertEqual(len(self.signals), 3)
        name, (stack, nested) = self.signals[2]
        self.assertEqual(name, "rollback")
        self.assertTrue(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.log.rollback()
        self.assertEqual(len(self.signals), 4)
        name, (stack, nested) = self.signals[3]
        self.assertEqual(name, "rollback")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testUndoRedo(self):
        """
        Try an undo() redo() sequence.
        """
        # begin
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        # push two actions
        action1 = DummyUndoableAction()
        self.log.push(action1)
        self.assertEqual(len(self.signals), 2)
        name, (stack, signalAction) = self.signals[1]
        self.assertEqual(name, "push")
        self.assertTrue(action1 is signalAction)

        action2 = DummyUndoableAction()
        self.log.push(action2)
        self.assertEqual(len(self.signals), 3)
        name, (stack, signalAction) = self.signals[2]
        self.assertEqual(name, "push")
        self.assertTrue(action2 is signalAction)

        # commit
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.commit()
        self.assertEqual(len(self.signals), 4)
        name, (stack, nested) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertTrue(action1.done_)
        self.assertTrue(action2.done_)

        # undo what we just committed
        self.log.undo()
        self.assertEqual(len(self.signals), 5)
        name, stack = self.signals[4]
        self.assertEqual(name, "undo")
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 1)

        self.assertFalse(action1.done_)
        self.assertFalse(action2.done_)

        # redo
        self.log.redo()
        self.assertEqual(len(self.signals), 6)
        name, stack = self.signals[5]
        self.assertEqual(name, "redo")
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertTrue(action1.done_)
        self.assertTrue(action2.done_)

    def testOrder(self):
        """
        Test that actions are undone and redone in the correct order.
        """
        call_sequence = []

        class Action(UndoableAction):
            def __init__(self, n):
                UndoableAction.__init__(self)
                self.n = n

            def do(self):
                call_sequence.append("do%s" % self.n)
                self._done()

            def undo(self):
                call_sequence.append("undo%s" % self.n)
                self._undone()

        action1 = Action(1)
        action2 = Action(2)
        action3 = Action(3)

        self.log.begin("meh")
        self.log.push(action1)
        self.log.begin("nested")
        self.log.push(action2)
        self.log.commit()
        self.log.push(action3)
        self.log.commit()

        self.log.undo()
        self.assertEqual(call_sequence, ["undo3", "undo2", "undo1"])

        call_sequence[:] = []
        self.log.redo()
        self.assertEqual(call_sequence, ["do1", "do2", "do3"])

        call_sequence[:] = []
        self.log.undo()
        self.assertEqual(call_sequence, ["undo3", "undo2", "undo1"])
Пример #29
0
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()
Пример #30
0
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()
Пример #31
0
 def setUp(self):
     self.log = UndoableActionLog()
     self._connect_to_undoable_action_log(self.log)
     self.signals = []
Пример #32
0
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()
Пример #33
0
class TestUndoableActionLog(TestCase):
    def setUp(self):
        self.log = UndoableActionLog()
        self._connectToUndoableActionLog(self.log)
        self.signals = []

    def tearDown(self):
        self._disconnectFromUndoableActionLog(self.log)

    def _undoActionLogSignalCb(self, log, *args):
        args = list(args)
        signalName = args.pop(-1)
        self.signals.append((signalName, args))

    def _connectToUndoableActionLog(self, log):
        for signalName in ("begin", "push", "rollback", "commit", "move"):
            log.connect(signalName, self._undoActionLogSignalCb, signalName)

    def _disconnectFromUndoableActionLog(self, log):
        self.log.disconnect_by_func(self._undoActionLogSignalCb)

    def testRollbackWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.rollback)

    def testCommitWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.commit, "")

    def testPushWrongState(self):
        # no error in this case
        self.log.push(None)

    def testUndoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.undo)

    def testRedoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.redo)

    def testCheckpoint(self):
        self.log.begin("meh")
        self.log.push(mock.Mock(spec=UndoableAction))
        self.assertRaises(UndoWrongStateError, self.log.checkpoint)
        self.log.rollback()
        self.log.checkpoint()
        self.assertNotEqual(self.log._checkpoint, None)

    def testDirty(self):
        self.assertFalse(self.log.dirty())
        self.log.begin("meh")
        self.log.push(mock.Mock(spec=UndoableAction))
        self.log.commit("meh")
        self.assertTrue(self.log.dirty())
        self.log.checkpoint()
        self.assertFalse(self.log.dirty())
        self.log.undo()
        self.assertTrue(self.log.dirty())
        self.log.redo()
        self.assertFalse(self.log.dirty())

    def testCommit(self):
        """
        Commit a stack.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, ) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(self.log.undo_stacks, [])
        self.log.push(mock.Mock())
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 3)
        name, (stack, action) = self.signals[1]
        self.assertEqual(name, "push")
        name, (stack, ) = self.signals[2]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_commit_proper(self):
        self.log.begin("meh")
        self.assertRaises(UndoWrongStateError, self.log.commit, "notmeh")

    def testNestedCommit(self):
        """
        Do two nested commits.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, ) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack, ) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(self.log.undo_stacks, [])
        self.log.push(mock.Mock())
        self.log.commit("nested")
        self.assertEqual(len(self.signals), 4)
        name, (stack, action) = self.signals[2]
        self.assertEqual(name, "push")
        name, (stack, ) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertTrue(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 5)
        name, (stack, ) = self.signals[4]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_finalizing_action(self):
        action1 = mock.Mock()
        action2 = mock.Mock()
        with self.log.started("one", finalizing_action=action1):
            self.log.push(mock.Mock())
            with self.log.started("two", finalizing_action=action2):
                self.log.push(mock.Mock())
        action1.do.assert_called_once_with()
        # For now, we call the finalizing action only for the top stack.
        action2.do.assert_not_called()

    def testRollback(self):
        """
        Test a rollback.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, ) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.log.rollback()
        self.assertEqual(len(self.signals), 2)
        name, (stack, ) = self.signals[1]
        self.assertEqual(name, "rollback")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testNestedRollback(self):
        """
        Test two nested rollbacks.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, ) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack, ) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.log.rollback()
        self.assertEqual(len(self.signals), 3)
        name, (stack, ) = self.signals[2]
        self.assertEqual(name, "rollback")
        self.assertTrue(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.log.rollback()
        self.assertEqual(len(self.signals), 4)
        name, (stack, ) = self.signals[3]
        self.assertEqual(name, "rollback")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testUndoRedo(self):
        """
        Try an undo() redo() sequence.
        """
        # begin
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, ) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        # push two actions
        action1 = mock.Mock(spec=UndoableAction)
        self.log.push(action1)
        self.assertEqual(len(self.signals), 2)
        name, (stack, signalAction) = self.signals[1]
        self.assertEqual(name, "push")
        self.assertTrue(action1 is signalAction)

        action2 = mock.Mock(spec=UndoableAction)
        self.log.push(action2)
        self.assertEqual(len(self.signals), 3)
        name, (stack, signalAction) = self.signals[2]
        self.assertEqual(name, "push")
        self.assertTrue(action2 is signalAction)

        # commit
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 4)
        name, (stack, ) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.assertEqual(action1.do.call_count, 0)
        self.assertEqual(action1.undo.call_count, 0)
        self.assertEqual(action2.do.call_count, 0)
        self.assertEqual(action2.undo.call_count, 0)

        # undo what we just committed
        self.log.undo()
        self.assertEqual(len(self.signals), 5)
        name, stack = self.signals[4]
        self.assertEqual(name, "move")
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 1)
        self.assertEqual(action1.do.call_count, 0)
        self.assertEqual(action1.undo.call_count, 1)
        self.assertEqual(action2.do.call_count, 0)
        self.assertEqual(action2.undo.call_count, 1)

        # redo
        self.log.redo()
        self.assertEqual(len(self.signals), 6)
        name, stack = self.signals[5]
        self.assertEqual(name, "move")
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.assertEqual(action1.do.call_count, 1)
        self.assertEqual(action1.undo.call_count, 1)
        self.assertEqual(action2.do.call_count, 1)
        self.assertEqual(action2.undo.call_count, 1)

    def testOrder(self):
        """
        Test that actions are undone and redone in the correct order.
        """
        order = mock.Mock()
        order.action1 = mock.Mock(spec=UndoableAction)
        order.action2 = mock.Mock(spec=UndoableAction)
        order.action3 = mock.Mock(spec=UndoableAction)

        with self.log.started("meh"):
            self.log.push(order.action1)
            with self.log.started("nested"):
                self.log.push(order.action2)
            self.log.push(order.action3)

        self.log.undo()
        order.assert_has_calls([
            mock.call.action3.undo(),
            mock.call.action2.undo(),
            mock.call.action1.undo()
        ])

        self.log.redo()
        order.assert_has_calls([
            mock.call.action1.do(),
            mock.call.action2.do(),
            mock.call.action3.do()
        ])

        self.log.undo()
        order.assert_has_calls([
            mock.call.action3.undo(),
            mock.call.action2.undo(),
            mock.call.action1.undo()
        ])
Пример #34
0
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()
Пример #35
0
class Pitivi(Loggable, Signallable):
    """
    Pitivi's main application class.

    Signals:
     - C{new-project} : A new C{Project} is loaded and ready to use.

     - C{new-project-loading} : Pitivi is attempting to load a new project.
     - C{new-project-loaded} : A new L{Project} has been loaded, and the UI should refresh it's view.
     - C{new-project-failed} : A new L{Project} failed to load.
     - C{closing-project} :  pitivi would like to close a project. handlers should return false
     if they do not want this project to close. by default, assumes
     true. This signal should only be used by classes that might want to abort
     the closing of a project.
     - C{project-closed} : The project is closed, it will be freed when the callback returns.
     Classes should connect to this instance when they want to know that
     data related to that project is no longer going to be used.
     - C{shutdown} : Used internally, do not use this signal.`

    @ivar settings: Application-wide settings.
    @type settings: L{GlobalSettings}.
    @ivar current: Currently used project.
    @type current: L{Project}.
    """

    __signals__ = {
        "new-project": ["project"],
        "new-project-loading": ["uri"],
        "new-project-created": ["project"],
        "new-project-loaded": ["project"],
        "new-project-failed": ["uri", "exception"],
        "closing-project": ["project"],
        "project-closed": ["project"],
        "missing-uri": ["formatter", "uri"],
        "version-info-received": ["versions"],
        "shutdown": None
    }

    def __init__(self):
        """
        initialize pitivi with the command line arguments
        """
        Loggable.__init__(self)

        # init logging as early as possible so we can log startup code
        enable_color = os.environ.get('PITIVI_DEBUG_NO_COLOR',
                                      '0') in ('', '0')
        # 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')

        # store ourself in the instance global
        if instance.PiTiVi:
            raise RuntimeWarning(
                _("There is already a %s instance, please inform "
                  "the developers by filing a bug at "
                  "http://bugzilla.gnome.org/enter_bug.cgi?product=pitivi") %
                APPNAME)
        instance.PiTiVi = self

        self.current = None

        # get settings
        self.settings = GlobalSettings()
        self.threads = ThreadMaster()
        #self.screencast = False

        self.effects = EffectsHandler()
        self.system = getSystem()

        self.projectManager = ProjectManager(self)
        self._connectToProjectManager(self.projectManager)

        self.action_log = UndoableActionLog()
        self.debug_action_log_observer = DebugActionLogObserver()
        self.debug_action_log_observer.startObserving(self.action_log)
        # TODO reimplement the observing after GES port
        #self.timelineLogObserver = TimelineLogObserver(self.action_log)
        self.projectLogObserver = ProjectLogObserver(self.action_log)

        self.version_information = {}
        self._checkVersion()

    def shutdown(self):
        """
        Close PiTiVi.

        @return: C{True} if PiTiVi was successfully closed, else C{False}.
        @rtype: C{bool}
        """
        self.debug("shutting down")
        # we refuse to close if we're running a user interface and the user
        # doesn't want us to close the current project.
        if self.current and not self.projectManager.closeRunningProject():
            self.warning(
                "Not closing since running project doesn't want to close")
            return False
        self.threads.stopAllThreads()
        self.settings.storeSettings()
        self.current = None
        instance.PiTiVi = None
        self.emit("shutdown")
        return True

    def _connectToProjectManager(self, projectManager):
        pm = projectManager
        pm.connect("new-project-loading",
                   self._projectManagerNewProjectLoading)
        pm.connect("new-project-created",
                   self._projectManagerNewProjectCreated)
        pm.connect("new-project-loaded", self._projectManagerNewProjectLoaded)
        pm.connect("new-project-failed", self._projectManagerNewProjectFailed)
        pm.connect("closing-project", self._projectManagerClosingProject)
        pm.connect("project-closed", self._projectManagerProjectClosed)

    def _projectManagerNewProjectLoading(self, projectManager, uri):
        self.emit("new-project-loading", uri)

    def _projectManagerNewProjectCreated(self, projectManager, project):
        self.current = project
        self.emit("new-project-created", project)

    def _newProjectLoaded(self, project):
        pass

    def _projectManagerNewProjectLoaded(self, projectManager, project,
                                        unused_fully_loaded):
        self.current = project
        self.action_log.clean()
        #self.timelineLogObserver.startObserving(project.timeline)
        self.projectLogObserver.startObserving(project)
        self._newProjectLoaded(project)
        self.emit("new-project-loaded", project)

    def _projectManagerNewProjectFailed(self, projectManager, uri, exception):
        self.emit("new-project-failed", uri, exception)

    def _projectManagerClosingProject(self, projectManager, project):
        return self.emit("closing-project", project)

    def _projectManagerProjectClosed(self, projectManager, project):
        #self.timelineLogObserver.stopObserving(project.timeline)
        self.projectLogObserver.stopObserving(project)
        self.current = None
        self.emit("project-closed", project)

    # check if for version information online
    def _checkVersion(self):
        giofile = Gio.File.new_for_uri(RELEASES_URL)
        self.info("Requesting version information")
        giofile.load_contents_async(None, self._versionInfoReceivedCb, None)

    def _versionInfoReceivedCb(self, giofile, result, data):
        try:
            # split data in lines
            raw = giofile.load_contents_finish(result)[0].split("\n")
            # split line at '=' if not empty or comment
            data = [
                element.split("=") for element in raw
                if element and not element.startswith("#")
            ]

            # search newest version and status
            status = "UNSUPPORTED"
            for version, version_status in data:
                if pitivi_version == version:
                    status = version_status
                if version_status.upper() == "CURRENT":
                    current_version = version

            self.info("Version information received")
            self.version_information["current"] = current_version
            self.version_information["status"] = status
            self.emit("version-info-received", self.version_information)
        except:
            self.warning("Version information could not be read")
Пример #36
0
 def setUp(self):
     self.log = UndoableActionLog()
     self._connectToUndoableActionLog(self.log)
     self.signals = []
Пример #37
0
class TestUndoableActionLog(TestCase):
    def setUp(self):
        self.log = UndoableActionLog()
        self._connectToUndoableActionLog(self.log)
        self.signals = []

    def tearDown(self):
        self._disconnectFromUndoableActionLog(self.log)

    def _undoActionLogSignalCb(self, log, *args):
        args = list(args)
        signalName = args.pop(-1)
        self.signals.append((signalName, args))

    def _connectToUndoableActionLog(self, log):
        for signalName in ("begin", "push", "rollback", "commit",
                    "undo", "redo"):
            log.connect(signalName, self._undoActionLogSignalCb, signalName)

    def _disconnectFromUndoableActionLog(self, log):
        self.log.disconnect_by_func(self._undoActionLogSignalCb)

    def testRollbackWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.rollback)

    def testCommitWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.commit)

    def testPushWrongState(self):
        # no error in this case
        self.log.push(None)

    def testUndoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.undo)

    def testRedoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.redo)

    def testCheckpoint(self):
        self.log.begin("meh")
        self.log.push(DummyUndoableAction())
        self.assertRaises(UndoWrongStateError, self.log.checkpoint)
        self.log.rollback()
        self.log.checkpoint()
        self.assertNotEqual(self.log._checkpoint, None)

    def testDirty(self):
        self.assertFalse(self.log.dirty())
        self.log.begin("meh")
        self.log.push(DummyUndoableAction())
        self.log.commit()
        self.assertTrue(self.log.dirty())
        self.log.checkpoint()
        self.assertFalse(self.log.dirty())
        self.log.undo()
        self.assertTrue(self.log.dirty())
        self.log.redo()
        self.assertFalse(self.log.dirty())

    def testCommit(self):
        """
        Commit a stack.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit()
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "commit")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testNestedCommit(self):
        """
        Do two nested commits.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(nested)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit()
        self.assertEqual(len(self.signals), 3)
        name, (stack, nested) = self.signals[2]
        self.assertEqual(name, "commit")
        self.assertTrue(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit()
        self.assertEqual(len(self.signals), 4)
        name, (stack, nested) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testRollback(self):
        """
        Test a rollback.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.log.rollback()
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "rollback")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testNestedRollback(self):
        """
        Test two nested rollbacks.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(nested)

        self.log.rollback()
        self.assertEqual(len(self.signals), 3)
        name, (stack, nested) = self.signals[2]
        self.assertEqual(name, "rollback")
        self.assertTrue(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.log.rollback()
        self.assertEqual(len(self.signals), 4)
        name, (stack, nested) = self.signals[3]
        self.assertEqual(name, "rollback")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testUndoRedo(self):
        """
        Try an undo() redo() sequence.
        """
        # begin
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        # push two actions
        action1 = DummyUndoableAction()
        self.log.push(action1)
        self.assertEqual(len(self.signals), 2)
        name, (stack, signalAction) = self.signals[1]
        self.assertEqual(name, "push")
        self.assertTrue(action1 is signalAction)

        action2 = DummyUndoableAction()
        self.log.push(action2)
        self.assertEqual(len(self.signals), 3)
        name, (stack, signalAction) = self.signals[2]
        self.assertEqual(name, "push")
        self.assertTrue(action2 is signalAction)

        # commit
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.commit()
        self.assertEqual(len(self.signals), 4)
        name, (stack, nested) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertTrue(action1.done_)
        self.assertTrue(action2.done_)

        # undo what we just committed
        self.log.undo()
        self.assertEqual(len(self.signals), 5)
        name, stack = self.signals[4]
        self.assertEqual(name, "undo")
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 1)

        self.assertFalse(action1.done_)
        self.assertFalse(action2.done_)

        # redo
        self.log.redo()
        self.assertEqual(len(self.signals), 6)
        name, stack = self.signals[5]
        self.assertEqual(name, "redo")
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertTrue(action1.done_)
        self.assertTrue(action2.done_)

    def testOrder(self):
        """
        Test that actions are undone and redone in the correct order.
        """
        call_sequence = []

        class Action(UndoableAction):
            def __init__(self, n):
                self.n = n

            def do(self):
                call_sequence.append("do%s" % self.n)
                self._done()

            def undo(self):
                call_sequence.append("undo%s" % self.n)
                self._undone()

        action1 = Action(1)
        action2 = Action(2)
        action3 = Action(3)

        self.log.begin("meh")
        self.log.push(action1)
        self.log.begin("nested")
        self.log.push(action2)
        self.log.commit()
        self.log.push(action3)
        self.log.commit()

        self.log.undo()
        self.assertEqual(call_sequence, ["undo3", "undo2", "undo1"])

        call_sequence[:] = []
        self.log.redo()
        self.assertEqual(call_sequence, ["do1", "do2", "do3"])

        call_sequence[:] = []
        self.log.undo()
        self.assertEqual(call_sequence, ["undo3", "undo2", "undo1"])
Пример #38
0
 def setUp(self):
     self.log = UndoableActionLog()
     self._connectToUndoableActionLog(self.log)
     self.signals = []
Пример #39
0
class TestTimelineUndo(TestCase):
    def setUp(self):
        app = Pitivi()
        app._startupCb(app)
        app.project_manager.newBlankProject()

        self.timeline = app.project_manager.current_project.timeline
        self.layer = GES.Layer()
        self.timeline.add_layer(self.layer)
        self.action_log = UndoableActionLog()
        self.observer = TimelineLogObserverSpy(self.action_log)
        self.observer.startObserving(self.timeline)

    def getTimelineClips(self):
        for layer in self.timeline.layers:
            for clip in layer.get_clips():
                yield clip

    @staticmethod
    def commitCb(action_log, stack, nested, stacks):
        stacks.append(stack)

    def testAddClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.action_log.begin("add clip")
        self.layer.add_clip(clip1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(3, len(stack.done_actions))
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, ClipAdded))
        self.assertTrue(clip1 in self.getTimelineClips())

        self.action_log.undo()
        self.assertFalse(clip1 in self.getTimelineClips())

        self.action_log.redo()
        self.assertTrue(clip1 in self.getTimelineClips())

    def testRemoveClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)
        self.action_log.begin("remove clip")
        self.layer.remove_clip(clip1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions))
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, ClipRemoved))
        self.assertFalse(clip1 in self.getTimelineClips())

        self.action_log.undo()
        self.assertTrue(clip1 in self.getTimelineClips())

        self.action_log.redo()
        self.assertFalse(clip1 in self.getTimelineClips())

    def testAddEffectToClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)

        effect1 = GES.Effect.new("agingtv")
        self.action_log.begin("add effect")
        clip1.add(effect1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions), stack.done_actions)
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, TrackElementAdded))

        self.assertTrue(effect1 in clip1.get_children(True))
        self.assertEqual(
            1,
            len([
                effect for effect in clip1.get_children(True)
                if isinstance(effect, GES.Effect)
            ]))

        self.action_log.undo()
        self.assertFalse(effect1 in clip1.get_children(True))

        self.action_log.redo()
        self.assertEqual(
            1,
            len([
                effect for effect in clip1.get_children(True)
                if isinstance(effect, GES.Effect)
            ]))

    def testRemoveEffectFromClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)

        effect1 = GES.Effect.new("agingtv")
        self.action_log.begin("add effect")
        clip1.add(effect1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions), stack.done_actions)
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, TrackElementAdded))

        self.assertTrue(effect1 in clip1.get_children(True))
        self.assertEqual(
            1,
            len([
                effect for effect in clip1.get_children(True)
                if isinstance(effect, GES.Effect)
            ]))

        self.action_log.begin("remove effect")
        clip1.remove(effect1)
        self.action_log.commit()

        self.assertEqual(
            0,
            len([
                effect for effect in clip1.get_children(True)
                if isinstance(effect, GES.Effect)
            ]))

        self.action_log.undo()
        self.assertEqual(
            1,
            len([
                effect for effect in clip1.get_children(True)
                if isinstance(effect, GES.Effect)
            ]))

        self.action_log.redo()
        self.assertEqual(
            0,
            len([
                effect for effect in clip1.get_children(True)
                if isinstance(effect, GES.Effect)
            ]))

    def testChangeEffectProperty(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)

        effect1 = GES.Effect.new("agingtv")
        self.action_log.begin("add effect")
        clip1.add(effect1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions), stack.done_actions)
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, TrackElementAdded))

        self.assertTrue(effect1 in clip1.get_children(True))
        self.assertEqual(
            1,
            len([
                effect for effect in clip1.get_children(True)
                if isinstance(effect, GES.Effect)
            ]))

        self.action_log.begin("change child property")
        effect1.set_child_property("scratch-lines", 0)
        self.action_log.commit()

        self.assertEqual(effect1.get_child_property("scratch-lines")[1], 0)
        self.action_log.undo()
        self.assertEqual(effect1.get_child_property("scratch-lines")[1], 7)
        self.action_log.redo()
        self.assertEqual(effect1.get_child_property("scratch-lines")[1], 0)
        self.action_log.undo()
        self.assertTrue(effect1 in clip1.get_children(True))
        self.action_log.undo()
        self.assertFalse(effect1 in clip1.get_children(True))

    def testClipPropertyChange(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)
        clip1.set_start(5 * Gst.SECOND)
        clip1.set_duration(20 * Gst.SECOND)
        self.layer.add_clip(clip1)
        self.action_log.begin("modify clip")
        clip1.set_start(10 * Gst.SECOND)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions))
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, ClipPropertyChanged))
        self.assertEqual(10 * Gst.SECOND, clip1.get_start())

        self.action_log.undo()
        self.assertEqual(5 * Gst.SECOND, clip1.get_start())
        self.action_log.redo()
        self.assertEqual(10 * Gst.SECOND, clip1.get_start())

        clip1.set_priority(10)
        self.action_log.begin("priority change")
        clip1.set_priority(20)
        self.action_log.commit()

        self.assertEqual(20, clip1.get_priority())
        self.action_log.undo()
        self.assertEqual(10, clip1.get_priority())
        self.action_log.redo()
        self.assertEqual(20, clip1.get_priority())

    def testUngroup(self):
        uri = common.TestCase.getSampleUri("tears_of_steel.webm")
        asset = GES.UriClipAsset.request_sync(uri)
        clip1 = asset.extract()
        self.layer.add_clip(clip1)

        clip1.set_start(5 * Gst.SECOND)
        clip1.set_duration(0.5 * Gst.SECOND)
        timeline_clips = list(self.getTimelineClips())
        self.assertEqual(1, len(timeline_clips), timeline_clips)
        self.assertEqual(5 * Gst.SECOND, timeline_clips[0].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[0].get_duration())

        self.action_log.begin("ungroup")
        ungrouped = GES.Container.ungroup(clip1, False)
        self.assertEqual(2, len(ungrouped), ungrouped)
        self.action_log.commit()
        timeline_clips = list(self.getTimelineClips())
        self.assertEqual(2, len(timeline_clips), timeline_clips)
        self.assertEqual(5 * Gst.SECOND, timeline_clips[0].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[0].get_duration())
        self.assertEqual(5 * Gst.SECOND, timeline_clips[1].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[1].get_duration())

        self.action_log.undo()
        timeline_clips = list(self.getTimelineClips())
        self.assertEqual(1, len(timeline_clips))
        self.assertEqual(5 * Gst.SECOND, timeline_clips[0].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[0].get_duration())