[gnome-subtitles: 3/6] #126 Undo/Redo in commands with text+translations



commit 60f95698953d7f02781ba8becea7189ce8f0822f
Author: Pedro Castro <pedro gnomesubtitles org>
Date:   Sat May 11 11:27:06 2019 +0100

    #126 Undo/Redo in commands with text+translations
    
    Undo/Redo in commands with text and translations must consider if the
    translation was reloaded

 src/GnomeSubtitles/Core/Command/BaseCommand.cs     |  28 +++-
 src/GnomeSubtitles/Core/Command/CommandManager.cs  |  27 +++-
 .../Core/Command/DeleteSubtitlesCommand.cs         |  31 +++--
 .../Core/Command/ReplaceAllCommand.cs              |  20 ++-
 .../Core/Command/SplitSubtitlesCommand.cs          | 151 +++++++++++++++------
 src/SubLib/Core/Timing/SplitOperator.cs            |   2 +-
 6 files changed, 197 insertions(+), 62 deletions(-)
---
diff --git a/src/GnomeSubtitles/Core/Command/BaseCommand.cs b/src/GnomeSubtitles/Core/Command/BaseCommand.cs
index 64e7497..cc26ec2 100644
--- a/src/GnomeSubtitles/Core/Command/BaseCommand.cs
+++ b/src/GnomeSubtitles/Core/Command/BaseCommand.cs
@@ -1,6 +1,6 @@
 /*
  * This file is part of Gnome Subtitles.
- * Copyright (C) 2006-2008 Pedro Castro
+ * Copyright (C) 2006-2019 Pedro Castro
  *
  * Gnome Subtitles is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,8 +17,22 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
+using System;
+
 namespace GnomeSubtitles.Core.Command {
 
+/*
+ * Note: commands affecting text *and* translation need to take into account that:
+ * - A translation may not be present when the command is executed, but may have been loaded (Translation > 
Open) afterwards.
+ *   When undoing, that translation needs to be taken into account. Example: Subtitles (with text only) were 
split. Then translation
+ *   was opened (which does not generate undo/redo because it's a file open). Undoing the split command will 
revert the original
+ *   text (which means the subtitles that were split will now be merged). In this case, the translation 
needs to be merged for those
+ *   subtitles, and not just set to their previous values (because there were none). Additionally, Redo 
should always apply the Split
+ *   command again in order to do a new split that takes into account the existing translation text.
+ * - A translation may be present when the command is executed, but may have been changed / loaded with a 
new file afterwards. This
+ *   means a ClearTarget event will be triggered in order to clear the translation data inside this command. 
However, this needs to be
+ *   taken into account later when Undoing / Redoing.
+ */
 public abstract class Command {
        private string description;
        private bool canGroup = false; //Whether this command can possibly be grouped with the previous 
command, if similar
@@ -65,6 +79,18 @@ public abstract class Command {
        public virtual Command MergeWith (Command command) {
                return command;
        }
+       
+       /// <summary>Clears a target within an existing command. When a command only has one target (normal 
or translation), this is
+       /// not necessary as the whole command will be deleted if that target (whole document or translation, 
respectively) becomes
+       /// unavailable. This is used when clearing a translation target in a command that includes 
NormalAndTranslation, which
+       /// means that only its translation part needs to be cleared (and that needs to be done by the 
command itself).</summary>
+       public virtual void ClearTarget (CommandTarget target) {
+               //If this command has NormalAndTranslation target, make sure it overrides this method
+               if (this.target == CommandTarget.NormalAndTranslation) {
+                       throw new NotImplementedException("ClearTarget is required for commands with 
NormalAndTranslation target");
+               }
+       }
+       
 
        /* Protected members */
 
diff --git a/src/GnomeSubtitles/Core/Command/CommandManager.cs 
b/src/GnomeSubtitles/Core/Command/CommandManager.cs
index 84d373e..15b930d 100644
--- a/src/GnomeSubtitles/Core/Command/CommandManager.cs
+++ b/src/GnomeSubtitles/Core/Command/CommandManager.cs
@@ -1,6 +1,6 @@
 /*
  * This file is part of Gnome Subtitles.
- * Copyright (C) 2006-2009 Pedro Castro
+ * Copyright (C) 2006-2019 Pedro Castro
  *
  * Gnome Subtitles is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -248,13 +248,21 @@ public class CommandManager {
                /* Go through the undo commands */
                if (undoCount > 0) {
                        int lastUndoIter = iterator - undoCount;
-                       if (lastUndoIter < 0)
+                       if (lastUndoIter < 0) {
                                lastUndoIter = limit + lastUndoIter;
+                       }
 
                        int undoIter = lastUndoIter;
                        while (undoIter != iterator) {
                                Command undoCommand = commands[undoIter];
+                               
+                               //We only keep the command if its target is not the one we're clearing
                                if (undoCommand.Target != target) {
+                                       //If the command target is NormalAndTranslation, it means at least 
part of it may need to be cleared
+                                       if (undoCommand.Target == CommandTarget.NormalAndTranslation) {
+                                               undoCommand.ClearTarget(target);
+                                       }
+                               
                                        newCommands[newIterator] = undoCommand;
                                        newIterator++;
                                        newUndoCount++;
@@ -269,7 +277,14 @@ public class CommandManager {
                        int newRedoIterator = newIterator; //Because newIterator cannot be changed now
                        for (int redoNum = 0 ; redoNum < redoCount ; redoNum++) {
                                Command redoCommand = commands[redoIter];
+                               
+                               //We only keep the command if its target is not the one we're clearing
                                if (redoCommand.Target != target) {
+                                       //If the command target is NormalAndTranslation, it means at least 
part of it may need to be cleared
+                                       if (redoCommand.Target == CommandTarget.NormalAndTranslation) {
+                                               redoCommand.ClearTarget(target);
+                                       }
+                               
                                        newCommands[newRedoIterator] = redoCommand;
                                        newRedoIterator++;
                                        newRedoCount++;
@@ -289,12 +304,16 @@ public class CommandManager {
                iterator = newIterator;
 
                /* Issue possible events */
-               if (toToggleUndo)
+               if (toToggleUndo) {
                        EmitUndoToggled();
-               if (toToggleRedo)
+               }
+               
+               if (toToggleRedo) {
                        EmitRedoToggled();
+               }
        }
 
+
        /* Event members */
 
        private void OnBaseInitFinished () {
diff --git a/src/GnomeSubtitles/Core/Command/DeleteSubtitlesCommand.cs 
b/src/GnomeSubtitles/Core/Command/DeleteSubtitlesCommand.cs
index 054e70a..79894ca 100644
--- a/src/GnomeSubtitles/Core/Command/DeleteSubtitlesCommand.cs
+++ b/src/GnomeSubtitles/Core/Command/DeleteSubtitlesCommand.cs
@@ -1,6 +1,6 @@
 /*
  * This file is part of Gnome Subtitles.
- * Copyright (C) 2006-2009,2011 Pedro Castro
+ * Copyright (C) 2006-2019 Pedro Castro
  *
  * Gnome Subtitles is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -29,11 +29,21 @@ public class DeleteSubtitlesCommand : MultipleSelectionCommand {
        private Subtitle[] subtitles = null;
 
        public DeleteSubtitlesCommand () : base(description, false, SelectionIntended.Simple, null) {
-               StoreSubtitles(); //TODO move to Execute
        }
 
 
        public override bool Execute () {
+               //Store subtitles to be deleted
+               int count = Paths.Length;
+               subtitles = new Subtitle[count];
+               for (int index = 0 ; index < count ; index++) {
+                       TreePath path = Paths[index];
+                       subtitles[index] = Base.Document.Subtitles[path];
+               }
+               
+               //If translations are loaded, our command affects both the normal document and translations
+               SetCommandTarget(Base.Document.IsTranslationLoaded ? CommandTarget.NormalAndTranslation : 
CommandTarget.Normal);
+       
                Base.Ui.View.Remove(Paths);
                return true;
        }
@@ -45,15 +55,14 @@ public class DeleteSubtitlesCommand : MultipleSelectionCommand {
        public override void Redo () {
                Execute();
        }
-
-       /* Private members */
-
-       private void StoreSubtitles () {
-               int count = Paths.Length;
-               subtitles = new Subtitle[count];
-               for (int index = 0 ; index < count ; index++) {
-                       TreePath path = Paths[index];
-                       subtitles[index] = Base.Document.Subtitles[path];
+       
+       public override void ClearTarget (CommandTarget target) {
+               if (target == CommandTarget.Translation) {
+                       foreach (Subtitle subtitle in subtitles) {
+                               if (subtitle.HasTranslation) {
+                                       subtitle.Translation.Clear();
+                               }
+                       }
                }
        }
 
diff --git a/src/GnomeSubtitles/Core/Command/ReplaceAllCommand.cs 
b/src/GnomeSubtitles/Core/Command/ReplaceAllCommand.cs
index 09f0fec..167036f 100644
--- a/src/GnomeSubtitles/Core/Command/ReplaceAllCommand.cs
+++ b/src/GnomeSubtitles/Core/Command/ReplaceAllCommand.cs
@@ -1,6 +1,6 @@
 /*
  * This file is part of Gnome Subtitles.
- * Copyright (C) 2006-2009 Pedro Castro
+ * Copyright (C) 2006-2019 Pedro Castro
  *
  * Gnome Subtitles is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -90,9 +90,23 @@ public class ReplaceAllCommand : MultipleSelectionCommand {
        public override void Redo () {
                Undo();
        }
+       
+       public override void ClearTarget (CommandTarget target) {
+               if (target == CommandTarget.Translation) {
+                       ArrayList newReplacedSubtitles = new ArrayList();
+                       
+                       foreach (SubtitleReplaceResult replacedSubtitle in replacedSubtitles) {
+                               if (replacedSubtitle.Text != null) {
+                                       newReplacedSubtitles.Add(new 
SubtitleReplaceResult(replacedSubtitle.Number, replacedSubtitle.Text, null));
+                               }
+                       }
+                       
+                       replacedSubtitles = newReplacedSubtitles;
+               }
+       }
 
-       /* Private members */
 
+       /* Private members */
 
        private void GetCommandData (out TreePath[] paths, out CommandTarget target) {
                ArrayList foundPaths = new ArrayList();
@@ -120,4 +134,4 @@ public class ReplaceAllCommand : MultipleSelectionCommand {
 
 }
 
-}
+}
\ No newline at end of file
diff --git a/src/GnomeSubtitles/Core/Command/SplitSubtitlesCommand.cs 
b/src/GnomeSubtitles/Core/Command/SplitSubtitlesCommand.cs
index 0ac685f..0106815 100644
--- a/src/GnomeSubtitles/Core/Command/SplitSubtitlesCommand.cs
+++ b/src/GnomeSubtitles/Core/Command/SplitSubtitlesCommand.cs
@@ -22,30 +22,125 @@ using Gtk;
 using Mono.Unix;
 using SubLib.Core.Domain;
 using SubLib.Core.Timing;
+using System;
 using System.Collections;
+using System.Collections.Generic;
 
 namespace GnomeSubtitles.Core.Command {
 
 public class SplitSubtitlesCommand : MultipleSelectionCommand {
        private static string description = Catalog.GetString("Splitting subtitles");
+
        private Subtitle[] subtitlesBefore = null;
+       private Dictionary<int, int[]> mapping = new Dictionary<int, int[]>(); //Maps the indices of the 
subtitles that were split <originalIndex, resultingIndices[]>
        private TreePath[] pathsAfter = null;
-       private Subtitle[] subtitlesAfter = null;
+       bool translationCleared = false; //Translation was cleared, so if we are undoing, we need to use the 
new translation text if present
 
        public SplitSubtitlesCommand () : base(description, false, SelectionIntended.Simple, null) {
        }
-
+       
        public override bool Execute () {
+               SetCommandTarget(Base.Document.IsTranslationLoaded ? CommandTarget.NormalAndTranslation : 
CommandTarget.Normal);
+
+               if (!Split()) {
+                       return false;
+               }
+
+               PostProcess();
+               return true;
+       }
+
+       public override void Undo () {
+               /*
+                * If the command originally applied to translations and they have changed, we need to use 
the new translations because we didn't have them when this command was first executed.
+                * The same goes if the command didn't originally have translations but it now has.
+                */
+               bool hadTranslation = (this.Target == CommandTarget.NormalAndTranslation);
+               bool updateTranslation = Base.Document.IsTranslationLoaded
+                       && ((hadTranslation && translationCleared) || !hadTranslation);
+               
+               if (updateTranslation) {
+                       for (int i = 0; i < subtitlesBefore.Length; i++) {
+                               Subtitle subtitle = subtitlesBefore[i];
+                               int subtitleIndex = Util.PathToInt(this.Paths[i]);
+
+                               ArrayList translations = new ArrayList();
+                               foreach (int subtitleIndexAfter in this.mapping[subtitleIndex]) {
+                                       string translation = 
Base.Document.Subtitles[subtitleIndexAfter].Translation.Get();
+                                       if (!String.IsNullOrEmpty(translation)) {
+                                               translations.Add(translation);
+                                       }
+                               }
+                               
+                               string mergedTranslation = String.Join("\n", 
(string[])translations.ToArray(typeof(string)));
+                               subtitle.Translation.Set(mergedTranslation);
+                       }
+                       translationCleared = false;
+               }
+
+               SetCommandTarget(Base.Document.IsTranslationLoaded ? CommandTarget.NormalAndTranslation : 
CommandTarget.Normal);
+               
+
+               Base.Document.Subtitles.Remove(this.pathsAfter);
+               Base.Ui.View.Insert(this.subtitlesBefore, this.Paths, this.FirstPath);
+               PostProcess();
+       }
+
+
+       /*
+        * In Redo, we just run the Split command again, so we don't reuse existing subtitles. Because of 
that, if a new translation
+        * has been loaded there's no problem, its current text will be used.
+        */
+       public override void Redo () {
+               SetCommandTarget(Base.Document.IsTranslationLoaded ? CommandTarget.NormalAndTranslation : 
CommandTarget.Normal);
+       
+               if (!Split()) {
+                       return;
+               }
+
+               PostProcess();
+               
+               Split();
+       }
+       
+       public override void ClearTarget (CommandTarget target) {
+               if (target == CommandTarget.Translation) {
+                       if (subtitlesBefore != null) {
+                               foreach (Subtitle subtitle in subtitlesBefore) {
+                                       if (subtitle.HasTranslation) {
+                                               subtitle.Translation.Clear();
+                                       }
+                               }
+                       }
+
+                       translationCleared = true;                      
+               }
+       }
+
+
+       /* Protected members */
+
+       protected void PostProcess () {
+               Base.Ui.Video.SeekToSelection(true);
+       }
+
+
+       /* Private members */
+
+       private bool Split () {
                Ui.View.Subtitles subtitles = Base.Document.Subtitles;
                ArrayList pathsBefore = new ArrayList();
                ArrayList subtitlesBefore = new ArrayList();
                ArrayList pathsAfter = new ArrayList();
+               Dictionary<int, int[]> mapping = new Dictionary<int, int[]>();
 
                SplitOperator splitOperator = new SplitOperator(subtitles, 
Base.Config.TimingsTimeBetweenSubtitles);
 
                foreach (TreePath path in Paths) {
+                       int originalSubtitleIndex = Util.PathToInt(path);
+                       
                        int subtitlesThatHaveBeenAdded = pathsAfter.Count - pathsBefore.Count; //number of 
subtitles that have been added ever since, in this loop
-                       int subtitleIndex = Util.PathToInt(path) + subtitlesThatHaveBeenAdded;
+                       int subtitleIndex = originalSubtitleIndex + subtitlesThatHaveBeenAdded;
                        Subtitle subtitle = subtitles[subtitleIndex];
                        
                        Subtitle[] newSubtitles = splitOperator.Split(subtitle);
@@ -54,10 +149,15 @@ public class SplitSubtitlesCommand : MultipleSelectionCommand {
                                subtitlesBefore.Add(subtitle);
                                
                                subtitles.Remove(subtitleIndex);
+                               int[] newSubtitleIndices = new int[newSubtitles.Length];
                                for (int i = 0 ; i < newSubtitles.Length ; i++) {
-                                       pathsAfter.Add(Util.IntToPath(subtitleIndex + i));
-                                       subtitles.Add(newSubtitles[i], subtitleIndex + i);
+                                       int newSubtitleIndex = subtitleIndex + i;
+                                       pathsAfter.Add(Util.IntToPath(newSubtitleIndex));
+                                       subtitles.Add(newSubtitles[i], newSubtitleIndex);
+                                       newSubtitleIndices[i] = newSubtitleIndex;
                                }
+                               
+                               mapping.Add(originalSubtitleIndex, newSubtitleIndices);
                        }
                }
 
@@ -68,46 +168,13 @@ public class SplitSubtitlesCommand : MultipleSelectionCommand {
                this.subtitlesBefore = (Subtitle [])subtitlesBefore.ToArray(typeof(Subtitle));
                this.Paths = (TreePath [])pathsBefore.ToArray(typeof(TreePath));
                this.pathsAfter = (TreePath [])pathsAfter.ToArray(typeof(TreePath));
+               this.mapping = mapping;
+               
                Base.Ui.View.RedrawPaths(this.pathsAfter);
-               Base.Ui.View.Selection.Select(this.pathsAfter, this.pathsAfter [0], true);
+               Base.Ui.View.Selection.Select(this.pathsAfter, this.pathsAfter[0], true);
                PostProcess();
                return true;
        }
-
-       public override void Undo () {
-               if (this.subtitlesAfter == null) {
-                       this.subtitlesAfter = GetSubtitlesAfter(Base.Document.Subtitles, this.pathsAfter);
-               }
-               Base.Document.Subtitles.Remove(this.pathsAfter);
-               Base.Ui.View.Insert(this.subtitlesBefore, this.Paths, this.FirstPath);
-               PostProcess();
-       }
-
-       public override void Redo () {
-               Base.Document.Subtitles.Remove(this.Paths);
-               Base.Ui.View.Insert(this.subtitlesAfter, this.pathsAfter, this.pathsAfter[0]);
-               PostProcess();
-       }
-
-       /* Protected members */
-
-       protected void PostProcess () {
-               Base.Ui.Video.SeekToSelection(true);
-       }
-
-
-       /* Private members */
-
-       private Subtitle[] GetSubtitlesAfter (GnomeSubtitles.Ui.View.Subtitles subtitles, TreePath[] 
pathsAfter) {
-               Subtitle[] subtitlesAfter = new Subtitle[pathsAfter.Length];
-               for (int index = 0 ; index < pathsAfter.Length ; index++) {
-                       TreePath path = pathsAfter[index];
-                       int subtitleIndex = Util.PathToInt(path);
-                       subtitlesAfter[index] = subtitles[subtitleIndex];
-               }
-               return subtitlesAfter;
-       }
-
 }
 
-}
+}
\ No newline at end of file
diff --git a/src/SubLib/Core/Timing/SplitOperator.cs b/src/SubLib/Core/Timing/SplitOperator.cs
index d588924..f41eaa3 100644
--- a/src/SubLib/Core/Timing/SplitOperator.cs
+++ b/src/SubLib/Core/Timing/SplitOperator.cs
@@ -131,7 +131,7 @@ public class SplitOperator {
                ArrayList translationLines = GetNonEmptyLines(originalLines);
                if (translationLines.Count > textLines.Count) {
                        int lastTextIndex = textLines.Count - 1;
-                       String lastLine = String.Join("\n", 
(string[])translationLines.ToArray(typeof(string)), lastTextIndex, translationLines.Count - textLines.Count + 
1);
+                       string lastLine = String.Join("\n", 
(string[])translationLines.ToArray(typeof(string)), lastTextIndex, translationLines.Count - textLines.Count + 
1);
                        translationLines[lastTextIndex] = lastLine;
                        translationLines.RemoveRange(lastTextIndex + 1, translationLines.Count - 
textLines.Count);
                }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]