diff --git a/Src/Common/Controls/DetailControls/Slice.cs b/Src/Common/Controls/DetailControls/Slice.cs index fc0d396b5f..c933245bfb 100644 --- a/Src/Common/Controls/DetailControls/Slice.cs +++ b/Src/Common/Controls/DetailControls/Slice.cs @@ -11,16 +11,16 @@ using System.Text; using System.Windows.Forms; using System.Xml; -using SIL.LCModel.Core.Cellar; using SIL.FieldWorks.Common.Controls; using SIL.FieldWorks.Common.Framework.DetailControls.Resources; -using SIL.LCModel.Core.KernelInterfaces; using SIL.FieldWorks.Common.FwUtils; using SIL.FieldWorks.Common.RootSites; -using SIL.LCModel; -using SIL.LCModel.Infrastructure; using SIL.FieldWorks.FdoUi; using SIL.FieldWorks.LexText.Controls; +using SIL.LCModel; +using SIL.LCModel.Core.Cellar; +using SIL.LCModel.Core.KernelInterfaces; +using SIL.LCModel.Infrastructure; using SIL.LCModel.Utils; using SIL.PlatformUtilities; using SIL.Utils; @@ -2800,42 +2800,90 @@ internal protected virtual bool UpdateDisplayIfNeeded(int hvo, int tag) private void MoveField(Direction dir) { CheckDisposed(); - if (ContainingDataTree.ShowingAllFields) + XmlNode swapWith; + XmlNode fieldRef = MoveableFieldReferenceForSlice(); + + if (fieldRef == null) { - XmlNode swapWith; - XmlNode fieldRef = FieldReferenceForSlice(); + Debug.Fail("Could not identify field to move on slice."); + return; + } - if (fieldRef == null) - { - Debug.Fail("Could not identify field to move on slice."); - return; - } + if (dir == Direction.Up) + { + swapWith = PrevSliceSiblingPart(fieldRef); + } + else + { + swapWith = NextSliceSiblingPart(fieldRef); + } + var parent = fieldRef.ParentNode; + // Reorder in the parent node in the xml + if (parent != null) + { + parent.RemoveChild(fieldRef); if (dir == Direction.Up) - { - swapWith = PrevPartSibling(fieldRef); - } + parent.InsertBefore(fieldRef, swapWith); else + parent.InsertAfter(fieldRef, swapWith); + } + + // Persist in the parent part (might not be the immediate parent node) + Inventory.GetInventory("layouts", m_cache.ProjectId.Name) + .PersistOverrideElement(PartParent(fieldRef)); + ContainingDataTree.RefreshList(true); + } + + /// + /// Get the sibling for the current slice in the given dir. + /// + internal Slice GetSibling(Direction dir) + { + int islice = IndexInContainer; + int cslice = ContainingDataTree.Slices.Count; + int increment = (dir == Direction.Down ? 1 : -1); + int depth = GetMoveableDepth(); + XmlNode fieldRef = MoveableFieldReferenceForSlice(); + while (true) + { + islice += increment; + if (dir == Direction.Down) { - swapWith = NextPartSibling(fieldRef); + if (islice >= cslice) break; } - - var parent = fieldRef.ParentNode; - // Reorder in the parent node in the xml - if (parent != null) + else { - parent.RemoveChild(fieldRef); - if (dir == Direction.Up) - parent.InsertBefore(fieldRef, swapWith); - else - parent.InsertAfter(fieldRef, swapWith); + if (islice < 0) break; } + var slice = ContainingDataTree.Slices[islice]; + int sliceDepth = slice.GetMoveableDepth(); + // Skip over our children. + if (sliceDepth > depth) + continue; + // Stop if we get past the children of our parent. + if (sliceDepth < depth) + break; + XmlNode sliceFieldRef = slice.MoveableFieldReferenceForSlice(); + if (sliceFieldRef == fieldRef) + // Skip slices with the same fieldRef as self. + // This happens with nested headers. + continue; + return slice; + } + return null; + } - // Persist in the parent part (might not be the immediate parent node) - Inventory.GetInventory("layouts", m_cache.ProjectId.Name) - .PersistOverrideElement(PartParent(fieldRef)); - ContainingDataTree.RefreshList(true); + private int GetMoveableDepth() + { + int count = 0; + foreach (object obj in Key) + { + var node = obj as XmlNode; + if (IsMoveableNode(node)) + count++; } + return count; } /// @@ -2854,13 +2902,79 @@ private XmlNode FieldReferenceForSlice() { continue; } - fieldRef = node; } return fieldRef; } + /// + /// Get the last moveable field reference for slice. + /// + private XmlNode MoveableFieldReferenceForSlice() + { + XmlNode fieldRef = null; + foreach (object obj in Key) + { + var node = obj as XmlNode; + if (IsMoveableNode(node)) + { + fieldRef = node; + } + } + + return fieldRef; + } + + /// + /// Can this node be moved? + /// + private bool IsMoveableNode(XmlNode node) + { + if (!IsRefPartNode(node)) + return false; + if (node.PreviousSibling != null) + // node has siblings, so it can be moved. + return true; + // This is the first node in a (possibly singleton) sequence. + // Sometimes the first node represents the sequence as a whole (e.g. "Variant Type"). + // In this case, it is not moveable at this level, but where it is invoked (e.g. ref="EntryRefs"). + // Other times, the sequence as a whole is represented by a header (e.g. "Category Info." is represented by "Grammatical Info. Details"). + // In this case, the first node is moveable. + // We look at the reference part nodes above node to determine whether the node is moveable. + bool found = false; + for (int i = Key.Length - 1; i >= 0; i--) + { + XmlNode keyNode = Key[i] as XmlNode; + if (!IsRefPartNode(keyNode)) continue; + if (keyNode == node) + { + found = true; + continue; + } + if (!found) continue; + // keyNode is a ref part node above node. + string keyNodeLabel = XmlUtils.GetOptionalAttributeValue(keyNode, "label", null); + if (keyNodeLabel != null) + // keyNode represents the sequence as a whole. + // So node represents itself and can be moved at this level. + // Example: node label="Category Info.", keyNodeLabel="Grammatical Info. Details". + return true; + if (keyNode.PreviousSibling != null || keyNode.NextSibling != null) + // node represents the sequence as a whole. + // So it does not represent itself and cannot be moved at this level. + // Example: node label="Variant Type", keyNode ref="EntryRefs". + return false; + } + return true; + } + + private bool IsRefPartNode(XmlNode node) + { + return node != null && node.Name == "part" + && XmlUtils.GetOptionalAttributeValue(node, "ref", null) != null; + } + protected void SetFieldVisibility(string visibility) { CheckDisposed(); @@ -2976,13 +3090,47 @@ protected bool IsVisibilityItemChecked(string visibility) private bool CheckValidMove(UIItemDisplayProperties display, Direction dir) { - XmlNode lastPartRef = FieldReferenceForSlice(); + XmlNode lastPartRef = MoveableFieldReferenceForSlice(); if (lastPartRef == null) return false; return dir == Direction.Up - ? PrevPartSibling(lastPartRef) != null - : NextPartSibling(lastPartRef) != null; + ? PrevSliceSiblingPart(lastPartRef) != null + : NextSliceSiblingPart(lastPartRef) != null; + } + + private XmlNode PrevSliceSiblingPart(XmlNode partRef) + { + Slice targetSlice = GetSibling(Direction.Up); + XmlNode targetNode = targetSlice?.MoveableFieldReferenceForSlice(); + if (targetNode == null) + return null; + + XmlNode prev = PrevPartSibling(partRef); + while (prev != null) + { + if (prev == targetNode) + return prev; + prev = PrevPartSibling(prev); + } + return null; + } + + private XmlNode NextSliceSiblingPart(XmlNode partRef) + { + Slice targetSlice = GetSibling(Direction.Down); + XmlNode targetNode = targetSlice?.MoveableFieldReferenceForSlice(); + if (targetNode == null) + return null; + + XmlNode next = NextPartSibling(partRef); + while (next != null) + { + if (next == targetNode) + return next; + next = NextPartSibling(next); + } + return null; } private XmlNode PrevPartSibling(XmlNode partRef) @@ -3024,7 +3172,7 @@ private XmlNode PartParent(XmlNode partRef) public bool OnDisplayMoveFieldUp(object args, ref UIItemDisplayProperties display) { CheckDisposed(); - display.Enabled = ContainingDataTree.ShowingAllFields && CheckValidMove(display, Direction.Up); + display.Enabled = CheckValidMove(display, Direction.Up); return true; } @@ -3033,7 +3181,7 @@ public bool OnDisplayMoveFieldUp(object args, ref UIItemDisplayProperties displa public bool OnDisplayMoveFieldDown(object args, ref UIItemDisplayProperties display) { CheckDisposed(); - display.Enabled = ContainingDataTree.ShowingAllFields && CheckValidMove(display, Direction.Down); + display.Enabled = CheckValidMove(display, Direction.Down); return true; }