class TestTimelineUndo(TestCase): def setUp(self): self.stream = new_stream() self.factory = new_source_factory() self.effect_factory = TestEffectFactory(self.stream) self.track1 = Track(self.stream) self.track2 = Track(self.stream) self.timeline = Timeline() self.timeline.addTrack(self.track1) self.timeline.addTrack(self.track2) self.track_object1 = SourceTrackObject(self.factory, self.stream) self.track_object2 = SourceTrackObject(self.factory, self.stream) self.track_effect1 = TrackEffect(self.effect_factory, self.stream) self.track_effect2 = TrackEffect(self.effect_factory, self.stream) self.track1.addTrackObject(self.track_object1) self.track2.addTrackObject(self.track_object2) self.timeline_object1 = TimelineObject(self.factory) self.timeline_object1.addTrackObject(self.track_object1) self.timeline_object1.addTrackObject(self.track_object2) self.action_log = UndoableActionLog() self.observer = TestTimelineLogObserver(self.action_log) self.observer.startObserving(self.timeline) def testAddTimelineObject(self): stacks = [] def commitCb(action_log, stack, nested): stacks.append(stack) self.action_log.connect("commit", commitCb) self.action_log.begin("add clip") self.timeline.addTimelineObject(self.timeline_object1) self.action_log.commit() self.failUnlessEqual(len(stacks), 1) stack = stacks[0] self.failUnlessEqual(len(stack.done_actions), 1) action = stack.done_actions[0] self.failUnless(isinstance(action, TimelineObjectAdded)) self.failUnless(self.timeline_object1 \ in self.timeline.timeline_objects) self.action_log.undo() self.failIf(self.timeline_object1 \ in self.timeline.timeline_objects) self.action_log.redo() self.failUnless(self.timeline_object1 \ in self.timeline.timeline_objects) def testRemoveTimelineObject(self): stacks = [] def commitCb(action_log, stack, nested): stacks.append(stack) self.action_log.connect("commit", commitCb) self.timeline.addTimelineObject(self.timeline_object1) self.action_log.begin("remove clip") self.timeline.removeTimelineObject(self.timeline_object1, deep=True) self.action_log.commit() self.failUnlessEqual(len(stacks), 1) stack = stacks[0] self.failUnlessEqual(len(stack.done_actions), 1) action = stack.done_actions[0] self.failUnless(isinstance(action, TimelineObjectRemoved)) self.failIf(self.timeline_object1 \ in self.timeline.timeline_objects) self.action_log.undo() self.failUnless(self.timeline_object1 \ in self.timeline.timeline_objects) self.action_log.redo() self.failIf(self.timeline_object1 \ in self.timeline.timeline_objects) def testAddEffectToTimelineObject(self): stacks = [] pipeline = Pipeline() def commitCb(action_log, stack, nested): stacks.append(stack) self.action_log.connect("commit", commitCb) self.observer.pipeline = pipeline #FIXME Should I commit it and check there are 2 elements #in the stacks self.timeline.addTimelineObject(self.timeline_object1) self.track1.addTrackObject(self.track_effect1) self.action_log.begin("add effect") self.timeline_object1.addTrackObject(self.track_effect1) self.action_log.commit() self.failUnlessEqual(len(stacks), 1) stack = stacks[0] self.failUnlessEqual(len(stack.done_actions), 1) action = stack.done_actions[0] self.failUnless(isinstance(action, TrackEffectAdded)) self.failUnless(self.track_effect1 \ in self.timeline_object1.track_objects) self.failUnless(self.track_effect1 \ in self.track1.track_objects) self.failUnless(len([effect for effect in \ self.timeline_object1.track_objects if isinstance(effect, TrackEffect)]) == 1) self.failUnless(len([effect for effect in self.track1.track_objects if isinstance(effect, TrackEffect)]) == 1) self.action_log.undo() self.failIf(self.track_effect1 \ in self.timeline_object1.track_objects) self.failIf(self.track_effect1 \ in self.track1.track_objects) self.action_log.redo() self.failUnless(len([effect for effect in self.timeline_object1.track_objects if isinstance(effect, TrackEffect)]) == 1) self.failUnless(len([effect for effect in self.track1.track_objects if isinstance(effect, TrackEffect)]) == 1) self.timeline.removeTimelineObject(self.timeline_object1, deep=True) def testTimelineObjectPropertyChange(self): stacks = [] def commitCb(action_log, stack, nested): stacks.append(stack) self.action_log.connect("commit", commitCb) self.timeline_object1.start = 5 * gst.SECOND self.timeline_object1.duration = 20 * gst.SECOND self.timeline.addTimelineObject(self.timeline_object1) self.action_log.begin("modify clip") self.timeline_object1.start = 10 * gst.SECOND self.action_log.commit() self.failUnlessEqual(len(stacks), 1) stack = stacks[0] self.failUnlessEqual(len(stack.done_actions), 1) action = stack.done_actions[0] self.failUnless(isinstance(action, TimelineObjectPropertyChanged)) self.failUnlessEqual(self.timeline_object1.start, 10 * gst.SECOND) self.action_log.undo() self.failUnlessEqual(self.timeline_object1.start, 5 * gst.SECOND) self.action_log.redo() self.failUnlessEqual(self.timeline_object1.start, 10 * gst.SECOND) self.timeline_object1.priority = 10 self.action_log.begin("priority change") self.timeline_object1.priority = 20 self.action_log.commit() self.failUnlessEqual(self.timeline_object1.priority, 20) self.action_log.undo() self.failUnlessEqual(self.timeline_object1.priority, 10) self.action_log.redo() self.failUnlessEqual(self.timeline_object1.priority, 20) def testUngroup(self): self.timeline_object1.start = 5 * gst.SECOND self.timeline_object1.duration = 20 * gst.SECOND self.timeline.addTimelineObject(self.timeline_object1) self.timeline.setSelectionToObj(self.track_object1, SELECT_ADD) self.failUnlessEqual(len(self.timeline.timeline_objects), 1) self.failUnlessEqual(self.timeline.timeline_objects[0].start, 5 * gst.SECOND) self.failUnlessEqual(self.timeline.timeline_objects[0].duration, 20 * gst.SECOND) self.action_log.begin("ungroup") self.timeline.ungroupSelection() self.action_log.commit() self.failUnlessEqual(len(self.timeline.timeline_objects), 2) self.failUnlessEqual(self.timeline.timeline_objects[0].start, 5 * gst.SECOND) self.failUnlessEqual(self.timeline.timeline_objects[0].duration, 20 * gst.SECOND) self.failUnlessEqual(self.timeline.timeline_objects[1].start, 5 * gst.SECOND) self.failUnlessEqual(self.timeline.timeline_objects[1].duration, 20 * gst.SECOND) self.action_log.undo() self.failUnlessEqual(len(self.timeline.timeline_objects), 1) self.failUnlessEqual(self.timeline.timeline_objects[0].start, 5 * gst.SECOND) self.failUnlessEqual(self.timeline.timeline_objects[0].duration, 20 * gst.SECOND)
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.failUnlessRaises(UndoWrongStateError, self.log.rollback) def testCommitWrongState(self): self.failUnlessRaises(UndoWrongStateError, self.log.commit) def testPushWrongState(self): # no error in this case self.log.push(None) def testUndoWrongState(self): self.failUnlessRaises(UndoWrongStateError, self.log.undo) def testRedoWrongState(self): self.failUnlessRaises(UndoWrongStateError, self.log.redo) def testCheckpoint(self): self.log.begin("meh") self.log.push(DummyUndoableAction()) self.failUnlessRaises(UndoWrongStateError, self.log.checkpoint) self.log.rollback() self.log.checkpoint() self.failIfEqual(self.log._checkpoint, None) def testDirty(self): self.failIf(self.log.dirty()) self.log.begin("meh") self.log.push(DummyUndoableAction()) self.log.commit() self.failUnless(self.log.dirty()) self.log.checkpoint() self.failIf(self.log.dirty()) self.log.undo() self.failUnless(self.log.dirty()) self.log.redo() self.failIf(self.log.dirty()) def testCommit(self): """ Commit a stack. """ self.failUnlessEqual(len(self.log.undo_stacks), 0) self.failUnlessEqual(len(self.log.redo_stacks), 0) self.log.begin("meh") self.failUnlessEqual(len(self.signals), 1) name, (stack, nested) = self.signals[0] self.failUnlessEqual(name, "begin") self.failIf(nested) self.failUnlessEqual(self.log.undo_stacks, []) self.log.commit() self.failUnlessEqual(len(self.signals), 2) name, (stack, nested) = self.signals[1] self.failUnlessEqual(name, "commit") self.failIf(nested) self.failUnlessEqual(len(self.log.undo_stacks), 1) self.failUnlessEqual(len(self.log.redo_stacks), 0) def testNestedCommit(self): """ Do two nested commits. """ self.failUnlessEqual(len(self.log.undo_stacks), 0) self.failUnlessEqual(len(self.log.redo_stacks), 0) self.log.begin("meh") self.failUnlessEqual(len(self.signals), 1) name, (stack, nested) = self.signals[0] self.failUnlessEqual(name, "begin") self.failIf(nested) self.failUnlessEqual(len(self.log.undo_stacks), 0) self.failUnlessEqual(len(self.log.redo_stacks), 0) self.log.begin("nested") self.failUnlessEqual(len(self.signals), 2) name, (stack, nested) = self.signals[1] self.failUnlessEqual(name, "begin") self.failUnless(nested) self.failUnlessEqual(self.log.undo_stacks, []) self.log.commit() self.failUnlessEqual(len(self.signals), 3) name, (stack, nested) = self.signals[2] self.failUnlessEqual(name, "commit") self.failUnless(nested) self.failUnlessEqual(len(self.log.undo_stacks), 0) self.failUnlessEqual(len(self.log.redo_stacks), 0) self.failUnlessEqual(self.log.undo_stacks, []) self.log.commit() self.failUnlessEqual(len(self.signals), 4) name, (stack, nested) = self.signals[3] self.failUnlessEqual(name, "commit") self.failIf(nested) self.failUnlessEqual(len(self.log.undo_stacks), 1) self.failUnlessEqual(len(self.log.redo_stacks), 0) def testRollback(self): """ Test a rollback. """ self.failUnlessEqual(len(self.log.undo_stacks), 0) self.failUnlessEqual(len(self.log.redo_stacks), 0) self.log.begin("meh") self.failUnlessEqual(len(self.signals), 1) name, (stack, nested) = self.signals[0] self.failUnlessEqual(name, "begin") self.failIf(nested) self.log.rollback() self.failUnlessEqual(len(self.signals), 2) name, (stack, nested) = self.signals[1] self.failUnlessEqual(name, "rollback") self.failIf(nested) self.failUnlessEqual(len(self.log.undo_stacks), 0) self.failUnlessEqual(len(self.log.redo_stacks), 0) def testNestedRollback(self): """ Test two nested rollbacks. """ self.failUnlessEqual(len(self.log.undo_stacks), 0) self.failUnlessEqual(len(self.log.redo_stacks), 0) self.log.begin("meh") self.failUnlessEqual(len(self.signals), 1) name, (stack, nested) = self.signals[0] self.failUnlessEqual(name, "begin") self.failIf(nested) self.failUnlessEqual(len(self.log.undo_stacks), 0) self.failUnlessEqual(len(self.log.redo_stacks), 0) self.log.begin("nested") self.failUnlessEqual(len(self.signals), 2) name, (stack, nested) = self.signals[1] self.failUnlessEqual(name, "begin") self.failUnless(nested) self.log.rollback() self.failUnlessEqual(len(self.signals), 3) name, (stack, nested) = self.signals[2] self.failUnlessEqual(name, "rollback") self.failUnless(nested) self.failUnlessEqual(len(self.log.undo_stacks), 0) self.failUnlessEqual(len(self.log.redo_stacks), 0) self.log.rollback() self.failUnlessEqual(len(self.signals), 4) name, (stack, nested) = self.signals[3] self.failUnlessEqual(name, "rollback") self.failIf(nested) self.failUnlessEqual(len(self.log.undo_stacks), 0) self.failUnlessEqual(len(self.log.redo_stacks), 0) def testUndoRedo(self): """ Try an undo() redo() sequence. """ # begin self.log.begin("meh") self.failUnlessEqual(len(self.signals), 1) name, (stack, nested) = self.signals[0] self.failUnlessEqual(name, "begin") self.failIf(nested) # push two actions action1 = DummyUndoableAction() self.log.push(action1) self.failUnlessEqual(len(self.signals), 2) name, (stack, signalAction) = self.signals[1] self.failUnlessEqual(name, "push") self.failUnless(action1 is signalAction) action2 = DummyUndoableAction() self.log.push(action2) self.failUnlessEqual(len(self.signals), 3) name, (stack, signalAction) = self.signals[2] self.failUnlessEqual(name, "push") self.failUnless(action2 is signalAction) # commit self.failUnlessEqual(len(self.log.undo_stacks), 0) self.failUnlessEqual(len(self.log.redo_stacks), 0) self.log.commit() self.failUnlessEqual(len(self.signals), 4) name, (stack, nested) = self.signals[3] self.failUnlessEqual(name, "commit") self.failIf(nested) self.failUnlessEqual(len(self.log.undo_stacks), 1) self.failUnlessEqual(len(self.log.redo_stacks), 0) self.failUnless(action1.done_) self.failUnless(action2.done_) # undo what we just committed self.log.undo() self.failUnlessEqual(len(self.signals), 5) name, stack = self.signals[4] self.failUnlessEqual(name, "undo") self.failUnlessEqual(len(self.log.undo_stacks), 0) self.failUnlessEqual(len(self.log.redo_stacks), 1) self.failIf(action1.done_) self.failIf(action2.done_) # redo self.log.redo() self.failUnlessEqual(len(self.signals), 6) name, stack = self.signals[5] self.failUnlessEqual(name, "redo") self.failUnlessEqual(len(self.log.undo_stacks), 1) self.failUnlessEqual(len(self.log.redo_stacks), 0) self.failUnless(action1.done_) self.failUnless(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.failUnlessEqual(call_sequence, ["undo3", "undo2", "undo1"]) call_sequence[:] = [] self.log.redo() self.failUnlessEqual(call_sequence, ["do1", "do2", "do3"]) call_sequence[:] = [] self.log.undo() self.failUnlessEqual(call_sequence, ["undo3", "undo2", "undo1"])