// 
// Hiero
// Copyright (c) 2015  Barry Block 
// 
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation, either version 3 of the License, or (at your option) any later
// version. 
// 
// This program is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE.  See the GNU General Public License for more details. 
// 
// You should have received a copy of the GNU General Public License along with
// this program.  If not, see <http://www.gnu.org/licenses/>. 
//

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Gtk;

// Following are the command classes representing Hiero commands (actions) that can be executed, un-done or re-done. 
// These are used by the command manager (a simplistic undo/redo framework) that is incorporated into the Document
// class. The general framework is adapted from Steve Lautenschlager's undo/redo code which can be found here:
// http://www.cambiaresearch.com/articles/82/generic-undoredo-stack-in-csharp

namespace Hiero
{
   // All of the application's outline commands must implement this.
   public interface ICommand
   {
      void Do(bool logging);
      void Undo(bool logging);
      bool OutlineModified { get; }
   }

   /*============================================*/
   /*                  COMMAND                   */
   /*============================================*/

   public abstract class Command : ICommand
   {
      protected static string checkMark = " ✔";
         // This is the text used to display a check mark for a node.
      protected static string crossMark = " ✘";
         // This is the text used to display a cross mark for a node.
      public string Name { get; set; }
         // The command's ID.
      protected Document Outline { get; set; }
         // The Hiero document instance.
      protected TreePath NodePath { get; set; }
         // The path of the node being operated on.
      public bool OutlineModified { get; set; }
         // The state of the document's modified flag prior to the command's execution.
      protected string LoggedText { get; set; }
         // The text of the logged command.

      // The constructor.
      public Command(Document outline, TreeIter theNode)
      {
         Outline = outline; 
         NodePath = Model.GetPath(theNode);
         OutlineModified = Outline.Modified;
      }

      // Returns the iter of the node operated on by the command.
      public TreeIter NodeIter
      {
         get 
         {
            TreeIter theNode;
            Model.GetIter(out theNode, NodePath);
            return theNode;
         }
      }

      // Returns the path string of the node operated on by the command.
      protected string NodeStr
      {
         get 
         {
            TreeIter theNode;
            Model.GetIter(out theNode, NodePath);
            return Model.GetStringFromIter(theNode);
         }
      }

      // Returns the tree's model.
      public TreeStore Model
      {
         get { return (TreeStore) Outline.Tree.Model; }
      }

      // Returns true if the given node is the root node.
      internal static bool IsRoot(TreeStore model, TreeIter theNode)
      {
         TreeIter theRoot;
         model.GetIterFirst(out theRoot);
         return theNode.Equals(theRoot);
      }

      // Attaches the subtree provided in the form of the given text to the given node and returns 
      // the root of the new subtree. Code adapted from demo by Rod Stephens which is found here: 
      // http://csharphelper.com/blog/2014/09/load-a-treeview-from-a-tab-delimited-file-in-c/ 
      protected static void AttachSubtree(TreeStore model, TreeIter theNode, string text, out TreeIter root)
      {
         root = TreeIter.Zero;

         // Break the text into lines:

         string[] textLines = text.Split(new char[] {'\n'}, StringSplitOptions.RemoveEmptyEntries);

         // Create a "lookup" for keeping track of parent nodes per level:

         Dictionary<int, TreeIter> parents = new Dictionary<int, TreeIter>();

         // Load the text lines:

         foreach (string textLine in textLines)
         {
            // See how many tabs are at the start of the line:

            int level = textLine.Length - textLine.TrimStart('\t').Length;

            // Clean up the line of text and translate newlines:

            string cleanedLine = textLine.TrimStart('\t').Replace(MainClass.escapedNewline, "\n");
            bool nodeMatched = cleanedLine[cleanedLine.Length-1] == 'T';
            cleanedLine = cleanedLine.Remove(cleanedLine.Length-1, 1);

            // Add the new node:

            if (level == 0)
            {
               // It's the "root" node.

               root = model.AppendValues(theNode, cleanedLine, nodeMatched);
               parents[level] = root;
            }
            else
            {
               // It's not the "root" node.

               parents[level] = model.AppendValues(parents[level - 1], cleanedLine, nodeMatched);
            }
         }
      }

      // Returns the subtree rooted at the given node as an ITF string.
      private static void SerializeSubtree(TreeStore model, ref string result, TreeIter theNode, int level, int noOfNewlines)
      {
         TreeIter childIter;
         string Indent;

         // Output the node's text:

         Indent = new string('\t', level);
         string newlines = new string('\n', noOfNewlines);
         string nodeText = (string) model.GetValue(theNode, 0);
         nodeText = nodeText.Replace("\n", MainClass.escapedNewline);
         bool nodeMatched = (bool) model.GetValue(theNode, 1);
         string matchStr = nodeMatched ? "T" : "F";
         result += Indent + nodeText + matchStr + newlines;

         // Process the child nodes:

         int NoOfChildren = model.IterNChildren(theNode);
         for (int i = 0; i < NoOfChildren; i++)
         {
            model.IterNthChild(out childIter, theNode, i);
            SerializeSubtree(model, ref result, childIter, level + 1, noOfNewlines);
         }
      }

      // Returns the text contents of the subtree rooted at the given node.
      internal static string GetSubtreeText(TreeStore model, TreeIter theNode)
      {
         const int noOfNewlines = 1; 
         string result = string.Empty;

         // Get the text:

         SerializeSubtree(model, ref result, theNode, 0, noOfNewlines);

         // Remove that trailing newline:

         if (result.Length > (noOfNewlines-1)) result = result.Remove(result.Length - noOfNewlines, noOfNewlines);

         return result;
      }

      // Loads an OutlineView widget from indented text formatted text. Code adapted from demo by Rod Stephens which
      //  is found here: http://csharphelper.com/blog/2014/09/load-a-treeview-from-a-tab-delimited-file-in-c/ 
      public void LoadOutlineFromText(TreeStore model, string subtreeText)
      {
         // Break the text into lines:

         string[] textLines = subtreeText.Split(new char[] {'\n'},
         StringSplitOptions.RemoveEmptyEntries);

         // Create a "lookup" for keeping track of parent nodes per level:

         Dictionary<int, TreeIter> parents = new Dictionary<int, TreeIter>();

         // Load the text lines:

         model.Clear();
         foreach (string textLine in textLines)
         {
            // See how many tabs are at the start of the line:

            int level = textLine.Length - textLine.TrimStart('\t').Length;

            // Clean up the line of text and translate newlines:

            string cleanedLine = textLine.TrimStart('\t').Replace(MainClass.escapedNewline, "\n");
            bool nodeMatched = cleanedLine[cleanedLine.Length-1] == 'T';
            cleanedLine = cleanedLine.Remove(cleanedLine.Length-1, 1);

            // Add the new node:

            if (level == 0)
            {
               // It's the root node.

               parents[level] = model.AppendValues(cleanedLine, nodeMatched);
            }
            else
               parents[level] = model.AppendValues(parents[level - 1], cleanedLine, nodeMatched);
         }
      }

      // Returns the sibling order of the given node.
      protected static int GetSiblingOrder(TreeStore model, TreeIter theNode)
      {
         string pathStr = model.GetStringFromIter(theNode);
         string[] parts = pathStr.Split(new char[] {':'}, StringSplitOptions.None);
         string lastPart = parts[parts.Length-1];
         return Convert.ToInt32(lastPart);
      }

      // Returns the given node's prior sibling.
      internal static TreeIter IterPrevious(TreeStore model, TreeIter theNode)
      {
         TreeIter parentNode;
         model.IterParent(out parentNode, theNode);
         int sibOrder = GetSiblingOrder(model, theNode);
         TreeIter priorNode = TreeIter.Zero;
         if (sibOrder > 0) model.IterNthChild(out priorNode, parentNode, sibOrder-1);
         return priorNode;
      }

      // Sets the given node's sibling order. Assumes that the
      // node is currently the LAST node in the sibling order.
      protected static void SetSiblingOrder(TreeStore model, TreeIter theNode, int n)
      {
         while (GetSiblingOrder(model, theNode) != n)
         {
            // Get the node's prior sibling:

            TreeIter priorNode = IterPrevious(model, theNode);
   
            // Swap the two nodes:
   
            model.Swap(priorNode, theNode);
         }
      }

      // Sets focus to a nearby node of the node.
      protected static TreeIter GetNearbyNode(TreeStore model, TreeIter parentNode, TreeIter theNode)
      {
         int totalSibs = model.IterNChildren(parentNode);
         int sibOrder = GetSiblingOrder(model, theNode);
         TreeIter nearbyNode = theNode;

         if (sibOrder < totalSibs-1)
         {
            // Use the node's next sibling:

            model.IterNext(ref nearbyNode);
         }
         else
         {
            if (sibOrder > 0)
            {
               // Use the node's prior sibling:

               nearbyNode = IterPrevious(model, theNode);
            }
            else
            {
               // If all else fails, use the node's parent node:

               nearbyNode = parentNode;
            }
         }

         return nearbyNode;
      }

      // Returns true if the given node is the sibling following the reference node.
      protected static bool IsNextSibling(TreeStore model, TreeIter theNode, TreeIter refNode)
      {
         TreeIter nextNode = refNode;
         bool Ok = model.IterNext(ref nextNode); 
         return (Ok && theNode.Equals(nextNode));
      }

      // Moves the given node upward until it is the sibling following the reference node.
      protected static void SlideNode(TreeStore model, TreeIter theNode, TreeIter refNode)
      {
         while (!IsNextSibling(model, theNode, refNode))
         {
            // Get the node's prior node:
   
            TreeIter priorNode = IterPrevious(model, theNode);
   
            // Swap the two nodes:
   
            model.Swap(priorNode, theNode);
         }
      }

      // Moves the node up in the sibling order.
      protected static TreePath MoveNodeUp(TreeStore model, TreeIter theNode)
      {
         // Copy reference to the node (else below logic won't work):

         TreeIter refNode = theNode;

         // Get the prior node:

         TreeIter priorNode = IterPrevious(model, refNode);

         // Swap the two nodes:

         model.Swap(priorNode, refNode);

         // Save the node:

         TreePath thePath = model.GetPath(refNode);
         return thePath;
      }

      // Moves the node down in the sibling order.
      protected static TreePath MoveNodeDown(TreeStore model, TreeIter theNode)
      {
         // Copy reference to the node (else below logic won't work):

         TreeIter refNode = theNode;

         // Get the next node:

         TreeIter nextNode = refNode;
         model.IterNext(ref nextNode); 

         // Swap the two nodes:

         model.Swap(nextNode, refNode);

         // Save the node:

         TreePath thePath = model.GetPath(refNode);
         return thePath;
      }

      // Promotes the node.
      protected static TreePath PromoteNode(Document outline, TreeIter theNode)
      {
         TreeStore model = (TreeStore) outline.Tree.Model;

         // Get the node's grandparent:

         TreeIter parentNode;
         TreeIter grandparentNode;
         model.IterParent(out parentNode, theNode);
         model.IterParent(out grandparentNode, parentNode);

         // Copy the node (and its children):

         TreeIter dstNode;
         OutlineView.CopyNode(model, theNode, grandparentNode, out dstNode);

         // Slide the new node into position following the parent node:

         SlideNode(model, dstNode, parentNode);

         // Delete the original node:

         TreeIter refNode = theNode;
         model.Remove(ref refNode);

         // Focus the newly added node:

         TreePath thePath = model.GetPath(dstNode);
         outline.Tree.SetCursor(thePath, outline.Tree.Columns[0], false);
         outline.Tree.GrabFocus();
         return thePath;
      }

      // Demotes the node.
      protected static TreePath DemoteNode(Document outline, TreeIter theNode)
      {
         TreeStore model = (TreeStore) outline.Tree.Model;

         // Get the prior node:

         TreeIter priorNode = IterPrevious(model, theNode);

         // Copy the subtree beneath the sibling:

         TreeIter dstNode;
         OutlineView.CopyNode(model, theNode, priorNode, out dstNode);

         // Delete the original node:

         TreeIter refNode = theNode;
         model.Remove(ref refNode);

         // Get the node's parent:

         TreeIter parentNode;
         if (model.IterParent(out parentNode, dstNode))
         {
            // Expand the node's parent:

            TreePath parentPath = model.GetPath(parentNode);
            outline.Tree.ExpandToPath(parentPath);
         }

         // Set focus to the demoted node:

         TreePath thePath = model.GetPath(dstNode);
         outline.Tree.SetCursor(thePath, outline.Tree.Columns[0], false);
         outline.Tree.GrabFocus();
         return thePath;
      }

      // Toggles checkmark at end of the node's text.
      protected static void ToggleCheckmark(TreeStore model, TreeIter theNode)
      {
         int len = checkMark.Length;
         string text = (string) model.GetValue(theNode, 0);
         bool hasCheck = (text.Length >= len) && (text.Substring(text.Length-len) == checkMark);
         bool hasCross = (text.Length >= len) && (text.Substring(text.Length-len) == crossMark);

         if (!hasCross)
         {
            if (hasCheck)
               model.SetValue(theNode, 0, text.Substring(0, text.Length-len));
            else
               model.SetValue(theNode, 0, text + checkMark);
         }
      }

      // Toggles crossmark at end of the node's text.
      protected static void ToggleCrossmark(TreeStore model, TreeIter theNode)
      {
         int len = checkMark.Length;
         string text = (string) model.GetValue(theNode, 0);
         bool hasCheck = (text.Length >= len) && (text.Substring(text.Length-len) == checkMark);
         bool hasCross = (text.Length >= len) && (text.Substring(text.Length-len) == crossMark);

         if (!hasCheck)
         {
            if (hasCross)
               model.SetValue(theNode, 0, text.Substring(0, text.Length-len));
            else
               model.SetValue(theNode, 0, text + crossMark);
         }
      }

      // Replaces any occurence of multiple newlines with a single newline.
      protected static string ReduceNewlines(string text)
      {
         return Regex.Replace(text, @"\n{2,}", "\n");
      }

      // Replaces all newlines with two newlines.
      protected static string DoubleNewlines(string text)
      {
         return text.Replace("\n", "\n\n");
      }

      // Replaces all newlines with spaces.
      protected static string NewlinesToSpaces(string text)
      {
         return text.Replace("\n", " ");
      }

      // Executes the command.
      public abstract void Do(bool logging);

      // Undoes execution of the command.
      public abstract void Undo(bool logging);
   }

   /*============================================*/
   /*                 DELETENODE                 */
   /*============================================*/

   public class DeleteNodeCommand : Command
   {
      private string Subtree { get; set; }
         // The deleted subtree (whose root is the deleted node).
      private TreeIter ParentNode { get; set; }
         // The deleted node's parent.

      public DeleteNodeCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "DeleteNode";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         // Save the node's current state: 

         TreeIter parentNode;
         Model.IterParent(out parentNode, NodeIter);
         ParentNode = parentNode;
         Subtree = GetSubtreeText(Model, NodeIter);

         // Get a node nearby to focus after the node is deleted:

         TreeIter nearbyNode = GetNearbyNode(Model, parentNode, NodeIter);

         // Delete the node:

         TreeIter theNode = NodeIter;
         Model.Remove(ref theNode);

         // Set focus to nearby node:

         TreePath thePath = Model.GetPath(nearbyNode);
         Outline.Tree.SetCursor(thePath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         // Re-attach the previously deleted node: 

         TreeIter rootNode;
         AttachSubtree(Model, ParentNode, Subtree, out rootNode);

         // Set the restored node's sibling order:

         SetSiblingOrder(Model, rootNode, GetSiblingOrder(Model, NodeIter));

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine);
      }
   }

   /*============================================*/
   /*                  EDITTEXT                  */
   /*============================================*/

   public class EditTextCommand : Command
   {
      private string OldText { get; set; }
         // The node's original text prior to editing.
      private string NewText { get; set; }
         // The node's edited text.

      public EditTextCommand(Document outline, TreeIter theNode, string text) : base(outline, theNode)
      {
         Name = "EditText";
         NewText = text;
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         string text = NewText.Replace("\n", MainClass.escapedNewline);
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + "|" + text + Environment.NewLine;
         
         // Save the node's current state: 

         OldText = (string) Model.GetValue(NodeIter, 0);

         // Edit the node's text:

         Model.SetValue(NodeIter, 0, NewText);
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         // Revert the editing of the node: 

         Model.SetValue(NodeIter, 0, OldText); 

         // Focus the node:

         TreePath thePath = Model.GetPath(NodeIter);
         Outline.Tree.SetCursor(thePath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*                  ADDCHILD                  */
   /*============================================*/

   public class AddChildNodeCommand : Command
   {
      private TreePath ChildPath { get; set; }
         // The added chiild.

      public AddChildNodeCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "AddChildNode";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         // Add the node:

         TreeIter childIter = Model.AppendValues(NodeIter, MainClass.newNodePlaceholder, false);

         // Focus the newly added node:

         ChildPath = Model.GetPath(childIter);
         Outline.Tree.ExpandToPath(ChildPath);
         Outline.Tree.SetCursor(ChildPath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         // Delete the newly-added node:

         TreeIter childIter;
         Model.GetIter(out childIter, ChildPath);
         Model.Remove(ref childIter);

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*                 ADDSIBLING                 */
   /*============================================*/

   public class AddSiblingNodeCommand : Command
   {
      private TreePath SiblingPath { get; set; }
         // The added sibling.

      public AddSiblingNodeCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "AddSiblingNode";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         // Add the node:

         TreeIter siblingIter;
         siblingIter = Model.InsertNodeAfter(NodeIter);
         Model.SetValue(siblingIter, 0, MainClass.newNodePlaceholder);
         Model.SetValue(siblingIter, 1, false);

         // Focus the newly added node:

         SiblingPath = Model.GetPath(siblingIter);
         Outline.Tree.ExpandToPath(SiblingPath);
         Outline.Tree.SetCursor(SiblingPath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         // Delete the newly-added node:

         TreeIter siblingIter;
         Model.GetIter(out siblingIter, SiblingPath);
         Model.Remove(ref siblingIter);

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*                 MOVENODEUP                 */
   /*============================================*/

   public class MoveNodeUpCommand : Command
   {
      private TreePath ThePath { get; set; }
         // The node after the swap.

      public MoveNodeUpCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "MoveNodeUp";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         ThePath = MoveNodeUp(Model, NodeIter);
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         TreeIter theNode;
         Model.GetIter(out theNode, ThePath);
         MoveNodeDown(Model, theNode);

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*                MOVENODEDOWN                */
   /*============================================*/

   public class MoveNodeDownCommand : Command
   {
      private TreePath ThePath { get; set; }
         // The node after the swap.

      public MoveNodeDownCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "MoveNodeDown";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         ThePath = MoveNodeDown(Model, NodeIter);
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         TreeIter theNode;
         Model.GetIter(out theNode, ThePath);
         MoveNodeUp(Model, theNode);

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*                PROMOTENODE                 */
   /*============================================*/

   public class PromoteNodeCommand : Command
   {
      private TreePath ThePath { get; set; }
         // The promoted node.
      private int SiblingOrder { get; set; }
         // The node's sibling order prior to being promoted.

      public PromoteNodeCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "PromoteNode";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         SiblingOrder = GetSiblingOrder(Model, NodeIter);
         ThePath = PromoteNode(Outline, NodeIter);
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         TreeIter theNode;
         Model.GetIter(out theNode, ThePath);

         // Demote the prior promoted node:

         TreePath thePath;
         thePath = DemoteNode(Outline, theNode);

         // Restore its sibling order: 

         Model.GetIter(out theNode, thePath);
         SetSiblingOrder(Model, theNode, SiblingOrder);

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*                 DEMOTENODE                 */
   /*============================================*/

   public class DemoteNodeCommand : Command
   {
      private TreePath ThePath { get; set; }
         // The demoted node.

      public DemoteNodeCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "DemoteNode";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         ThePath = DemoteNode(Outline, NodeIter);
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         TreeIter theNode;
         Model.GetIter(out theNode, ThePath);
         PromoteNode(Outline, theNode);

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*               CHECKMARKNODE                */
   /*============================================*/

   public class CheckmarkNodeCommand : Command
   {
      public CheckmarkNodeCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "CheckmarkNode";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         ToggleCheckmark(Model, NodeIter);
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         ToggleCheckmark(Model, NodeIter);

         // Set focus to the demoted node:

         TreePath thePath = Model.GetPath(NodeIter);
         Outline.Tree.SetCursor(thePath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*               CROSSMARKNODE                */
   /*============================================*/

   public class CrossmarkNodeCommand : Command
   {
      public CrossmarkNodeCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "CrossmarkNode";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         ToggleCrossmark(Model, NodeIter);
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         ToggleCrossmark(Model, NodeIter);

         // Set focus to the demoted node:

         TreePath thePath = Model.GetPath(NodeIter);
         Outline.Tree.SetCursor(thePath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*                REPARENTNODE                */
   /*============================================*/

   public class ReparentNodeCommand : Command
   {
      private TreePath ThePath { get; set; }
         // The re-parented node.

      public ReparentNodeCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "ReparentNode";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         // Create a new sibling node under which the current node will be copied:

         TreeIter newNode = Model.InsertNodeAfter(NodeIter); 
         Model.SetValue(newNode, 0, MainClass.newNodePlaceholder);

         // Copy the current node beneath the newly created sibling:

         TreeIter aNode;
         OutlineView.CopyNode(Model, NodeIter, newNode, out aNode);

         // Delete the original node:

         TreeIter refNode = NodeIter;
         Model.Remove(ref refNode);

         // Focus the new parent:

         ThePath = Model.GetPath(newNode);
         Outline.Tree.ExpandToPath(ThePath);
         Outline.Tree.SetCursor(ThePath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         // Get the newly-added node:

         TreeIter newNode;
         Model.GetIter(out newNode, ThePath);

         if (IsRoot(Model, newNode))
         {
            // The outline's original root is being restored. This is a special case 
            // in which the OutlineView widget is simply re-loaded from the subtree 
            // that's rooted at the root node's one and only child. Get the subtree 
            // as a string:

            TreeIter childNode;
            Model.IterNthChild(out childNode, newNode, 0); 
            string subtreeText = GetSubtreeText(Model, childNode);

            // Re-load the tree from the subtree text:

            LoadOutlineFromText(Model, subtreeText);

            // Focus the outline's new root node:
   
            TreeIter theRoot;
            Model.GetIterFirst(out theRoot);
            TreePath thePath = Model.GetPath(theRoot);
            Outline.Tree.SetCursor(thePath, Outline.Tree.Columns[0], false);
            Outline.Tree.GrabFocus();
         } 
         else
         {
            // Get the node's sibling order:
   
            int siblingOrder = GetSiblingOrder(Model, newNode);
   
            // Get its parent:
   
            TreeIter parentNode;
            Model.IterParent(out parentNode, newNode);
   
            // Get its only child (the node that was just re-parented):
   
            TreeIter childNode;
            Model.IterNthChild(out childNode, newNode, 0);
   
            // Copy the child node's subtree back to the parent:
   
            TreeIter theNode;
            OutlineView.CopyNode(Model, childNode, parentNode, out theNode);
   
            // Move the subtree copy up the list of siblings so that 
            // it is the next sibling following the newly-added node:
   
            SetSiblingOrder(Model, theNode, siblingOrder+1);
   
            // Delete the newly-added node:
   
            Model.Remove(ref newNode);
   
            // Focus the root of the new subtree:
   
            ThePath = Model.GetPath(theNode);
            Outline.Tree.SetCursor(ThePath, Outline.Tree.Columns[0], false);
            Outline.Tree.GrabFocus();
         }

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*              REDUCEDNEWLINES               */
   /*============================================*/

   public class ReduceNewlinesCommand : Command
   {
      private string OldText { get; set; }
         // The node's original text prior to newline reduction.

      public ReduceNewlinesCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "ReduceNewlines";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         // Save the node's current state: 

         OldText = (string) Model.GetValue(NodeIter, 0);

         // Edit the node's text:

         string text = ReduceNewlines((string) Model.GetValue(NodeIter, 0));
         Model.SetValue(NodeIter, 0, text);
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         // Revert the editing of the node: 

         Model.SetValue(NodeIter, 0, OldText); 

         // Focus the node:

         TreePath thePath = Model.GetPath(NodeIter);
         Outline.Tree.SetCursor(thePath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*             NORMALIZENEWLINES              */
   /*============================================*/

   public class NormalizeNewlinesCommand : Command
   {
      private string OldText { get; set; }
         // The node's original text prior to normalizing.

      public NormalizeNewlinesCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "NormalizeNewlines";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         // Save the node's current state: 

         OldText = (string) Model.GetValue(NodeIter, 0);

         // Edit the node's text:

         string text = DoubleNewlines(ReduceNewlines((string) Model.GetValue(NodeIter, 0)));
         Model.SetValue(NodeIter, 0, text);
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         // Revert the editing of the node: 

         Model.SetValue(NodeIter, 0, OldText); 

         // Focus the node:

         TreePath thePath = Model.GetPath(NodeIter);
         Outline.Tree.SetCursor(thePath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*               DOUBLENEWLINES               */
   /*============================================*/

   public class DoubleNewlinesCommand : Command
   {
      private string OldText { get; set; }
         // The node's original text prior to doubling newlines.

      public DoubleNewlinesCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "DoubleNewlines";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         // Save the node's current state: 

         OldText = (string) Model.GetValue(NodeIter, 0);

         // Edit the node's text:

         string text = DoubleNewlines((string) Model.GetValue(NodeIter, 0));
         Model.SetValue(NodeIter, 0, text);
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         // Revert the editing of the node: 

         Model.SetValue(NodeIter, 0, OldText); 

         // Focus the node:

         TreePath thePath = Model.GetPath(NodeIter);
         Outline.Tree.SetCursor(thePath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*              NEWLINESTOSPACES              */
   /*============================================*/

   public class NewlinesToSpacesCommand : Command
   {
      private string OldText { get; set; }
         // The node's original text prior to doubling newlines.

      public NewlinesToSpacesCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "NewlinesToSpaces";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         // Save the node's current state: 

         OldText = (string) Model.GetValue(NodeIter, 0);

         // Edit the node's text:

         string text = NewlinesToSpaces((string) Model.GetValue(NodeIter, 0));
         Model.SetValue(NodeIter, 0, text);
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         // Revert the editing of the node: 

         Model.SetValue(NodeIter, 0, OldText); 

         // Focus the node:

         TreePath thePath = Model.GetPath(NodeIter);
         Outline.Tree.SetCursor(thePath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*                EXTRACTNODE                 */
   /*============================================*/

   public class ExtractNodeCommand : Command
   {
      private bool IsTheRoot { get; set; }
         // Is true if the root is being extracted.
      private TreePath ParentPath { get; set; }
         // The extracted node's parent.
      private int NoOfChildren { get; set; }
         // The extracted node's number of children (if not root).
      private TreePath FirstChildPath { get; set; }
         // The first child after the extraction.
      private string NodeText { get; set; }
         // The extracted node's text.
      private string SubtreeText { get; set; }
         // The subtree beneath the outline's root node (if the root is being extracted).

      public ExtractNodeCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "ExtractNode";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         // Capture information about the node:

         NodeText = (string) Model.GetValue(NodeIter, 0);

         // Perform the extraction operation:

         IsTheRoot = IsRoot(Model, NodeIter);
         if (IsTheRoot)
         {
            // The outline's root is being extracted. This is a special case in which the 
            // OutlineView widget is simply re-loaded from the subtree that's rooted 
            // at the root node's one and only child. Get the subtree as a string:

            TreeIter childNode;
            Model.IterNthChild(out childNode, NodeIter, 0); 
            SubtreeText = GetSubtreeText(Model, childNode);

            // Re-load the tree from the subtree text:

            LoadOutlineFromText(Model, SubtreeText);

            // Focus the outline's new root node:
   
            TreeIter theRoot;
            Model.GetIterFirst(out theRoot);
            TreePath thePath = Model.GetPath(theRoot);
            Outline.Tree.SetCursor(thePath, Outline.Tree.Columns[0], false);
            Outline.Tree.GrabFocus();
         }
         else
         {
            // The outline's root is not being extracted. Get the node's parent:
   
            TreeIter parentNode;
            Model.IterParent(out parentNode, NodeIter);
            ParentPath = Model.GetPath(parentNode);
   
            // Promote each of the node's children:
   
            TreeIter newNode = TreeIter.Zero;
            NoOfChildren = Model.IterNChildren(NodeIter);
            for (int i = NoOfChildren-1; i >= 0 ; i--)
            {
               // Get the child's subtree:
   
               TreeIter childNode;
               Model.IterNthChild(out childNode, NodeIter, i);
               string theText = GetSubtreeText(Model, childNode);
   
               // Attach the subtree to the node's parent:
   
               AttachSubtree(Model, parentNode, theText, out newNode);

               // Slide the new node into position following the node being extracted:
      
               SlideNode(Model, newNode, NodeIter);
            }
   
            // Delete the extracted node:
   
            TreeIter refNode = NodeIter;
            Model.Remove(ref refNode);

            // Focus the extracted node's first child:
   
            FirstChildPath = Model.GetPath(newNode);
            Outline.Tree.SetCursor(FirstChildPath, Outline.Tree.Columns[0], false);
            Outline.Tree.GrabFocus();
         }
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         if (IsTheRoot)
         {
            LoadOutlineFromText(Model, NodeText + "\n\t" + Regex.Replace(SubtreeText, @"\t+", "$0\t"));

            // Focus the outline's new root node:
   
            TreeIter theRoot;
            Model.GetIterFirst(out theRoot);
            TreePath thePath = Model.GetPath(theRoot);
            Outline.Tree.SetCursor(thePath, Outline.Tree.Columns[0], false);
            Outline.Tree.GrabFocus();
         }
         else
         {
            // The outline's root was not extracted. Restore the extracted node:

            TreeIter parentNode;
            Model.GetIter(out parentNode, ParentPath);
            TreeIter firstChildNode;
            Model.GetIter(out firstChildNode, FirstChildPath);
            TreeIter restoredNode = Model.InsertNodeBefore(parentNode, firstChildNode);
            Model.SetValue(restoredNode, 0, NodeText);

            // Copying each of the original child nodes beneath 
            // the restored node. Index the first child:

            TreeIter indexNode = firstChildNode;

            for (int i=1; i <= NoOfChildren; i++)
            {
               // Save off a reference to the next child prior to processing this one:

               TreeIter nextNode = indexNode; 
               Model.IterNext(ref nextNode);

               // Process this child:

               TreeIter aNode;
               OutlineView.CopyNode(Model, indexNode, restoredNode, out aNode);
               Model.Remove(ref indexNode);

               // Prepare to process the next child:

               indexNode = nextNode;
            }

            // Focus the extracted node's first child:
   
            TreePath thePath = Model.GetPath(restoredNode);
            Outline.Tree.SetCursor(thePath, Outline.Tree.Columns[0], false);
            Outline.Tree.GrabFocus();
         }

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*                 SPLITNODE                  */
   /*============================================*/

   public class SplitNodeCommand : Command
   {
      private string NodeText { get; set; }
         // The extracted node's text.
      private int NoOfSplitNodes { get; set; }
         // The number of splits produced from the node.

      public SplitNodeCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "SplitNode";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         // Get the node's text:

         NodeText = (string) Model.GetValue(NodeIter, 0);
         
         // Create the split nodes:

         string[] splitText = NodeText.Split(new string[] {"\n"}, StringSplitOptions.RemoveEmptyEntries);
         NoOfSplitNodes = splitText.Length;

         if (NoOfSplitNodes > 1)
         {
            // There is at least one split (boundary).

            for (int i = NoOfSplitNodes-1; i >= 1 ; i--)
            {
               TreeIter splitNode = Model.InsertNodeAfter(NodeIter);
               Model.SetValue(splitNode, 0, splitText[i]);
            }

            // Replace the text for the original node with the first parcel of split text,
            // (handling the original node in this way preserves any child nodes that it 
            // might have):
   
            Model.SetValue(NodeIter, 0, splitText[0]);
         }

         // Focus the first split node:

         NodePath = Model.GetPath(NodeIter);
         Outline.Tree.SetCursor(NodePath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         // Restore the original node's text:

         TreeIter theNode;
         Model.GetIter(out theNode, NodePath);
         Model.SetValue(theNode, 0, NodeText);

         // Delete the split nodes:

         if (NoOfSplitNodes > 1)
         {
            // Deleting the "extra" split nodes. Index the first one:

            TreeIter indexNode = theNode;
            Model.IterNext(ref indexNode);

            for (int i=1; i <= NoOfSplitNodes-1; i++)
            {
               // Get the next node prior to deleting this one:
   
               TreeIter nextNode = indexNode; 
               Model.IterNext(ref nextNode);
   
               // Delete the node:

               Model.Remove(ref indexNode);

               // Prepare to process the next split node:

               indexNode = nextNode;
            }
         }

         // Focus the original node:

         Outline.Tree.SetCursor(NodePath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*                  JOINNODE                  */
   /*============================================*/

   public class JoinNodeCommand : Command
   {
      private int NoOfChildren { get; set; }
         // The number of children "contained" by the current node.
      private TreePath PriorPath { get; set; }
         // The "prior" node that was joined.
      private string PriorText { get; set; }
         // The prior node's text.
      private string CurrText { get; set; }
         // The current node's text.

      public JoinNodeCommand(Document outline, TreeIter theNode) : base(outline, theNode)
      {
         Name = "JoinNode";
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine;

         // Get the prior node:

         TreeIter priorNode = IterPrevious(Model, NodeIter);
         
         // Append the current node's text to that of the prior node:

         CurrText = (string) Model.GetValue(NodeIter, 0);
         bool nodeMatched = (bool) Model.GetValue(NodeIter, 1);
         PriorText = (string) Model.GetValue(priorNode, 0);
         bool priorMatched = (bool) Model.GetValue(priorNode, 1);
         Model.SetValue(priorNode, 0, PriorText + "\n" + CurrText);
         Model.SetValue(priorNode, 1, nodeMatched || priorMatched);

         // Copy the current node's children beneath the prior node:

         NoOfChildren = Model.IterNChildren(NodeIter);
         for (int i = 0; i < NoOfChildren; i++)
         {
            TreeIter childNode;
            Model.IterNthChild(out childNode, NodeIter, i);
            TreeIter theRoot;
            OutlineView.CopyNode(Model, childNode, priorNode, out theRoot);
         }

         // Delete the node:

         TreeIter refNode = NodeIter;
         Model.Remove(ref refNode);

         // Focus the prior node:

         PriorPath = Model.GetPath(priorNode);
         Outline.Tree.SetCursor(PriorPath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         // Get the prior node and reset its text:

         TreeIter priorNode;
         Model.GetIter(out priorNode, PriorPath);
         Model.SetValue(priorNode, 0, PriorText);

         // Get the prior node's parent:

         TreeIter parentNode;
         Model.IterParent(out parentNode, priorNode);

         // Re-create the current node:

         TreeIter currNode = Model.InsertNodeAfter(parentNode, priorNode);
         Model.SetValue(currNode, 0, CurrText);

         // Copy the children back beneath the current node:

         int index = Model.IterNChildren(priorNode) - NoOfChildren;
         for (int i = index; i < index + NoOfChildren; i++)
         {
            // Copy the child:

            TreeIter childNode;
            Model.IterNthChild(out childNode, priorNode, i); 
            TreeIter aNode;
            OutlineView.CopyNode(Model, childNode, currNode, out aNode);
         }

         // Delete the original children:

         index = Model.IterNChildren(priorNode) - 1;
         for (int i = index; i > index - NoOfChildren; i--)
         {
            TreeIter childNode;
            Model.IterNthChild(out childNode, priorNode, i); 

            // Delete the original:

            Model.Remove(ref childNode);
         }

         // Focus the current node:

         TreePath thePath = Model.GetPath(currNode);
         Outline.Tree.SetCursor(thePath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }

   /*============================================*/
   /*                  COPYNODE                  */
   /*============================================*/

   public class CopyNodeCommand : Command
   {
      private string SubtreeText { get; set; }
         // The copied subtree text.
      private Hiero.Clipboard Clipbd { get; set; }
         // The clipboard instance.
      private TreePath RootPath { get; set; }
         // This is the root of the added subtree.

      public CopyNodeCommand(Document outline, TreeIter theNode, string text) : base(outline, theNode)
      {
         Name = "CopyNode";
         SubtreeText = text;
      }

      // Executes the command.
      public override void Do(bool logging)
      {
         string text = SubtreeText.Replace("\n", Document.escapedNewlineForCmds); 
         if (logging) LoggedText = Name + "|" + Model.GetStringFromIter(NodeIter) + "|" + text + Environment.NewLine;

         TreeIter rootNode;
         AttachSubtree(Model, NodeIter, SubtreeText, out rootNode);
         RootPath = Model.GetPath(rootNode);

         // Focus the root of the subtree that was added:

         TreePath thePath = Model.GetPath(rootNode);
         Outline.Tree.SetCursor(thePath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();
         
         // Log the command:

         if (logging) Outline.CmdLog.Write(LoggedText);
      }

      // Undoes execution of the command.
      public override void Undo(bool logging)
      {
         TreeIter rootNode;
         Model.GetIter(out rootNode, RootPath); 
         Model.Remove(ref rootNode);

         // Focus the original current node:

         Outline.Tree.SetCursor(NodePath, Outline.Tree.Columns[0], false);
         Outline.Tree.GrabFocus();

         // Log the undo:

         if (logging) Outline.CmdLog.Write("Undo|" + Model.GetStringFromIter(NodeIter) + Environment.NewLine); 
      }
   }
}
