From b927f06b955b8fcd66e402c1aadccd351b0d6ad0 Mon Sep 17 00:00:00 2001 From: Justin Aquadro Date: Wed, 14 Nov 2012 20:48:25 -0500 Subject: [PATCH 1/2] Fixed node cutting/deleting not triggering isModified state --- Model/TagDataNode.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Model/TagDataNode.cs b/Model/TagDataNode.cs index 465cad6..d73e954 100644 --- a/Model/TagDataNode.cs +++ b/Model/TagDataNode.cs @@ -212,6 +212,7 @@ namespace NBTExplorer.Model { if (CanDeleteNode) { TagParent.DeleteTag(Tag); + IsParentModified = true; return Parent.Nodes.Remove(this); } @@ -251,6 +252,7 @@ namespace NBTExplorer.Model NbtClipboardController.CopyToClipboard(new NbtClipboardData(NodeName, Tag)); TagParent.DeleteTag(Tag); + IsParentModified = true; Parent.Nodes.Remove(this); return true; } From 1596d3365d74b3cba3a21261cfa93b5d283223fe Mon Sep 17 00:00:00 2001 From: Justin Aquadro Date: Thu, 15 Nov 2012 00:27:35 -0500 Subject: [PATCH 2/2] Multi-select support (for delete) --- Model/DataNode.cs | 51 +- Model/NodeCapabilities.cs | 12 + Model/TagDataNode.cs | 20 +- NBTExplorer.csproj | 3 + Properties/AssemblyInfo.cs | 4 +- .../MultiSelectTreeview.cs | 585 ++++++++++++++++++ Windows/MainForm.Designer.cs | 4 +- Windows/MainForm.cs | 375 ++++++++++- 8 files changed, 1036 insertions(+), 18 deletions(-) create mode 100644 Vendor/MultiSelectTreeView/MultiSelectTreeview.cs diff --git a/Model/DataNode.cs b/Model/DataNode.cs index 1107ebd..2bcb38f 100644 --- a/Model/DataNode.cs +++ b/Model/DataNode.cs @@ -171,7 +171,7 @@ namespace NBTExplorer.Model } } - #region Capabilities + #region Node Capabilities protected virtual NodeCapabilities Capabilities { @@ -240,6 +240,55 @@ namespace NBTExplorer.Model #endregion + #region Group Capabilities + + public virtual GroupCapabilities RenameNodeCapabilities + { + get { return GroupCapabilities.Single; } + } + + public virtual GroupCapabilities EditNodeCapabilities + { + get { return GroupCapabilities.Single; } + } + + public virtual GroupCapabilities DeleteNodeCapabilities + { + get { return GroupCapabilities.MultiMixedType | GroupCapabilities.ElideChildren; } + } + + public virtual GroupCapabilities CutNodeCapabilities + { + get { return GroupCapabilities.Single; } //SiblingMixedType + } + + public virtual GroupCapabilities CopyNodeCapabilities + { + get { return GroupCapabilities.Single; } //SiblingMixedType + } + + public virtual GroupCapabilities PasteIntoNodeCapabilities + { + get { return GroupCapabilities.Single; } + } + + public virtual GroupCapabilities SearchNodeCapabilites + { + get { return GroupCapabilities.Single; } + } + + public virtual GroupCapabilities ReorderNodeCapabilities + { + get { return GroupCapabilities.Single; } //SiblingMixedType + } + + public virtual GroupCapabilities RefreshNodeCapabilites + { + get { return GroupCapabilities.Single; } // MultiMixedType | ElideChildren + } + + #endregion + #region Operations public virtual bool CreateNode (TagType type) diff --git a/Model/NodeCapabilities.cs b/Model/NodeCapabilities.cs index 45f35c3..dcfffca 100644 --- a/Model/NodeCapabilities.cs +++ b/Model/NodeCapabilities.cs @@ -17,4 +17,16 @@ namespace NBTExplorer.Model Reorder = 0x100, Refresh = 0x200, } + + [Flags] + public enum GroupCapabilities + { + Single = 0x0, + SiblingSameType = 0x1, + SiblingMixedType = 0x2 | SiblingSameType, + MultiSameType = 0x4 | SiblingSameType, + MultiMixedType = 0x8 | MultiSameType | SiblingMixedType, + ElideChildren = 0x10, + All = MultiMixedType | ElideChildren, + } } diff --git a/Model/TagDataNode.cs b/Model/TagDataNode.cs index d73e954..8c5a92f 100644 --- a/Model/TagDataNode.cs +++ b/Model/TagDataNode.cs @@ -55,8 +55,8 @@ namespace NBTExplorer.Model | NodeCapabilities.Cut | NodeCapabilities.Delete | NodeCapabilities.PasteInto - | (TagParent.IsNamedContainer ? NodeCapabilities.Rename : NodeCapabilities.None) - | (TagParent.IsOrderedContainer ? NodeCapabilities.Reorder : NodeCapabilities.None) + | ((TagParent != null && TagParent.IsNamedContainer) ? NodeCapabilities.Rename : NodeCapabilities.None) + | ((TagParent != null && TagParent.IsOrderedContainer) ? NodeCapabilities.Reorder : NodeCapabilities.None) | NodeCapabilities.Search; } } @@ -158,8 +158,8 @@ namespace NBTExplorer.Model | NodeCapabilities.Cut | NodeCapabilities.Delete | NodeCapabilities.Edit - | (TagParent.IsNamedContainer ? NodeCapabilities.Rename : NodeCapabilities.None) - | (TagParent.IsOrderedContainer ? NodeCapabilities.Reorder : NodeCapabilities.None); + | ((TagParent != null && TagParent.IsNamedContainer) ? NodeCapabilities.Rename : NodeCapabilities.None) + | ((TagParent != null && TagParent.IsOrderedContainer) ? NodeCapabilities.Reorder : NodeCapabilities.None); } } @@ -167,7 +167,7 @@ namespace NBTExplorer.Model { get { - if (TagParent.IsOrderedContainer) + if (TagParent != null && TagParent.IsOrderedContainer) return TagParent.OrderedTagContainer.GetTagIndex(Tag) > 0; return false; } @@ -177,7 +177,7 @@ namespace NBTExplorer.Model { get { - if (TagParent.IsOrderedContainer) + if (TagParent != null && TagParent.IsOrderedContainer) return TagParent.OrderedTagContainer.GetTagIndex(Tag) < (TagParent.TagCount - 1); return false; } @@ -210,7 +210,7 @@ namespace NBTExplorer.Model public override bool DeleteNode () { - if (CanDeleteNode) { + if (CanDeleteNode && TagParent != null) { TagParent.DeleteTag(Tag); IsParentModified = true; return Parent.Nodes.Remove(this); @@ -221,7 +221,7 @@ namespace NBTExplorer.Model public override bool RenameNode () { - if (CanRenameNode && TagParent.IsNamedContainer && FormRegistry.EditString != null) { + if (CanRenameNode && TagParent != null && TagParent.IsNamedContainer && FormRegistry.EditString != null) { RestrictedStringFormData data = new RestrictedStringFormData(TagParent.NamedTagContainer.GetTagName(Tag)); data.RestrictedValues.AddRange(TagParent.NamedTagContainer.TagNamesInUse); @@ -248,7 +248,7 @@ namespace NBTExplorer.Model public override bool CutNode () { - if (CanCutNode) { + if (CanCutNode && TagParent != null) { NbtClipboardController.CopyToClipboard(new NbtClipboardData(NodeName, Tag)); TagParent.DeleteTag(Tag); @@ -262,7 +262,7 @@ namespace NBTExplorer.Model public override bool ChangeRelativePosition (int offset) { - if (CanReoderNode) { + if (CanReoderNode && TagParent != null) { int curIndex = TagParent.OrderedTagContainer.GetTagIndex(Tag); int newIndex = curIndex + offset; diff --git a/NBTExplorer.csproj b/NBTExplorer.csproj index c3a14f4..5f6ca5b 100644 --- a/NBTExplorer.csproj +++ b/NBTExplorer.csproj @@ -82,6 +82,9 @@ + + Component + Form diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index c9d4599..b55c05a 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.1.0.0")] -[assembly: AssemblyFileVersion("2.1.0.0")] +[assembly: AssemblyVersion("2.2.0.0")] +[assembly: AssemblyFileVersion("2.2.0.0")] diff --git a/Vendor/MultiSelectTreeView/MultiSelectTreeview.cs b/Vendor/MultiSelectTreeView/MultiSelectTreeview.cs new file mode 100644 index 0000000..a40a373 --- /dev/null +++ b/Vendor/MultiSelectTreeView/MultiSelectTreeview.cs @@ -0,0 +1,585 @@ +// Copyright 2007 Andrew D. Weiss +// Licensed under CPOL +// http://www.codeproject.com/Articles/20581/Multiselect-Treeview-Implementation + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace NBTExplorer.Vendor.MultiSelectTreeView +{ + public class MultiSelectTreeView : TreeView + { + + #region Selected Node(s) Properties + + private List m_SelectedNodes = null; + public List SelectedNodes + { + get + { + return m_SelectedNodes; + } + set + { + ClearSelectedNodes(); + if( value != null ) + { + foreach( TreeNode node in value ) + { + ToggleNode( node, true ); + } + } + } + } + + // Note we use the new keyword to Hide the native treeview's SelectedNode property. + private TreeNode m_SelectedNode; + public new TreeNode SelectedNode + { + get { return m_SelectedNode; } + set + { + ClearSelectedNodes(); + if( value != null ) + { + SelectNode( value ); + } + } + } + + #endregion + + public MultiSelectTreeView() + { + m_SelectedNodes = new List(); + base.SelectedNode = null; + } + + #region Overridden Events + + protected override void OnGotFocus( EventArgs e ) + { + // Make sure at least one node has a selection + // this way we can tab to the ctrl and use the + // keyboard to select nodes + try + { + if( m_SelectedNode == null && this.TopNode != null ) + { + ToggleNode( this.TopNode, true ); + } + + base.OnGotFocus( e ); + } + catch( Exception ex ) + { + HandleException( ex ); + } + } + + protected override void OnMouseDown( MouseEventArgs e ) + { + // If the user clicks on a node that was not + // previously selected, select it now. + + try + { + base.SelectedNode = null; + + TreeNode node = this.GetNodeAt( e.Location ); + if( node != null ) + { + int leftBound = node.Bounds.X; // - 20; // Allow user to click on image + int rightBound = node.Bounds.Right + 10; // Give a little extra room + if( e.Location.X > leftBound && e.Location.X < rightBound ) + { + if( ModifierKeys == Keys.None && ( m_SelectedNodes.Contains( node ) ) ) + { + // Potential Drag Operation + // Let Mouse Up do select + } + else + { + SelectNode( node ); + } + } + } + + base.OnMouseDown( e ); + } + catch( Exception ex ) + { + HandleException( ex ); + } + } + + protected override void OnMouseUp( MouseEventArgs e ) + { + // If the clicked on a node that WAS previously + // selected then, reselect it now. This will clear + // any other selected nodes. e.g. A B C D are selected + // the user clicks on B, now A C & D are no longer selected. + try + { + // Check to see if a node was clicked on + TreeNode node = this.GetNodeAt( e.Location ); + if( node != null ) + { + if( ModifierKeys == Keys.None && m_SelectedNodes.Contains( node ) ) + { + int leftBound = node.Bounds.X; // -20; // Allow user to click on image + int rightBound = node.Bounds.Right + 10; // Give a little extra room + if( e.Location.X > leftBound && e.Location.X < rightBound ) + { + + SelectNode( node ); + } + } + } + + base.OnMouseUp( e ); + } + catch( Exception ex ) + { + HandleException( ex ); + } + } + + protected override void OnItemDrag( ItemDragEventArgs e ) + { + // If the user drags a node and the node being dragged is NOT + // selected, then clear the active selection, select the + // node being dragged and drag it. Otherwise if the node being + // dragged is selected, drag the entire selection. + try + { + TreeNode node = e.Item as TreeNode; + + if( node != null ) + { + if( !m_SelectedNodes.Contains( node ) ) + { + SelectSingleNode( node ); + ToggleNode( node, true ); + } + } + + base.OnItemDrag( e ); + } + catch( Exception ex ) + { + HandleException( ex ); + } + } + + protected override void OnBeforeSelect( TreeViewCancelEventArgs e ) + { + // Never allow base.SelectedNode to be set! + try + { + base.SelectedNode = null; + e.Cancel = true; + + base.OnBeforeSelect( e ); + } + catch( Exception ex ) + { + HandleException( ex ); + } + } + + protected override void OnAfterSelect( TreeViewEventArgs e ) + { + // Never allow base.SelectedNode to be set! + try + { + base.OnAfterSelect( e ); + base.SelectedNode = null; + } + catch( Exception ex ) + { + HandleException( ex ); + } + } + + protected override void OnKeyDown( KeyEventArgs e ) + { + // Handle all possible key strokes for the control. + // including navigation, selection, etc. + + base.OnKeyDown( e ); + + if( e.KeyCode == Keys.ShiftKey ) return; + + //this.BeginUpdate(); + bool bShift = ( ModifierKeys == Keys.Shift ); + + try + { + // Nothing is selected in the tree, this isn't a good state + // select the top node + if( m_SelectedNode == null && this.TopNode != null ) + { + ToggleNode( this.TopNode, true ); + } + + // Nothing is still selected in the tree, this isn't a good state, leave. + if( m_SelectedNode == null ) return; + + if( e.KeyCode == Keys.Left ) + { + if( m_SelectedNode.IsExpanded && m_SelectedNode.Nodes.Count > 0 ) + { + // Collapse an expanded node that has children + m_SelectedNode.Collapse(); + } + else if( m_SelectedNode.Parent != null ) + { + // Node is already collapsed, try to select its parent. + SelectSingleNode( m_SelectedNode.Parent ); + } + } + else if( e.KeyCode == Keys.Right ) + { + if( !m_SelectedNode.IsExpanded ) + { + // Expand a collpased node's children + m_SelectedNode.Expand(); + } + else + { + // Node was already expanded, select the first child + SelectSingleNode( m_SelectedNode.FirstNode ); + } + } + else if( e.KeyCode == Keys.Up ) + { + // Select the previous node + if( m_SelectedNode.PrevVisibleNode != null ) + { + SelectNode( m_SelectedNode.PrevVisibleNode ); + } + } + else if( e.KeyCode == Keys.Down ) + { + // Select the next node + if( m_SelectedNode.NextVisibleNode != null ) + { + SelectNode( m_SelectedNode.NextVisibleNode ); + } + } + else if( e.KeyCode == Keys.Home ) + { + if( bShift ) + { + if( m_SelectedNode.Parent == null ) + { + // Select all of the root nodes up to this point + if( this.Nodes.Count > 0 ) + { + SelectNode( this.Nodes[0] ); + } + } + else + { + // Select all of the nodes up to this point under this nodes parent + SelectNode( m_SelectedNode.Parent.FirstNode ); + } + } + else + { + // Select this first node in the tree + if( this.Nodes.Count > 0 ) + { + SelectSingleNode( this.Nodes[0] ); + } + } + } + else if( e.KeyCode == Keys.End ) + { + if( bShift ) + { + if( m_SelectedNode.Parent == null ) + { + // Select the last ROOT node in the tree + if( this.Nodes.Count > 0 ) + { + SelectNode( this.Nodes[this.Nodes.Count - 1] ); + } + } + else + { + // Select the last node in this branch + SelectNode( m_SelectedNode.Parent.LastNode ); + } + } + else + { + if( this.Nodes.Count > 0 ) + { + // Select the last node visible node in the tree. + // Don't expand branches incase the tree is virtual + TreeNode ndLast = this.Nodes[0].LastNode; + while( ndLast.IsExpanded && ( ndLast.LastNode != null ) ) + { + ndLast = ndLast.LastNode; + } + SelectSingleNode( ndLast ); + } + } + } + else if( e.KeyCode == Keys.PageUp ) + { + // Select the highest node in the display + int nCount = this.VisibleCount; + TreeNode ndCurrent = m_SelectedNode; + while( ( nCount ) > 0 && ( ndCurrent.PrevVisibleNode != null ) ) + { + ndCurrent = ndCurrent.PrevVisibleNode; + nCount--; + } + SelectSingleNode( ndCurrent ); + } + else if( e.KeyCode == Keys.PageDown ) + { + // Select the lowest node in the display + int nCount = this.VisibleCount; + TreeNode ndCurrent = m_SelectedNode; + while( ( nCount ) > 0 && ( ndCurrent.NextVisibleNode != null ) ) + { + ndCurrent = ndCurrent.NextVisibleNode; + nCount--; + } + SelectSingleNode( ndCurrent ); + } + else + { + // Assume this is a search character a-z, A-Z, 0-9, etc. + // Select the first node after the current node that + // starts with this character + string sSearch = ( (char) e.KeyValue ).ToString(); + + TreeNode ndCurrent = m_SelectedNode; + while( ( ndCurrent.NextVisibleNode != null ) ) + { + ndCurrent = ndCurrent.NextVisibleNode; + if( ndCurrent.Text.StartsWith( sSearch ) ) + { + SelectSingleNode( ndCurrent ); + break; + } + } + } + } + catch( Exception ex ) + { + HandleException( ex ); + } + finally + { + this.EndUpdate(); + } + } + + #endregion + + #region Helper Methods + + private void SelectNode( TreeNode node ) + { + try + { + this.BeginUpdate(); + + if( m_SelectedNode == null || ModifierKeys == Keys.Control ) + { + // Ctrl+Click selects an unselected node, or unselects a selected node. + bool bIsSelected = m_SelectedNodes.Contains( node ); + ToggleNode( node, !bIsSelected ); + } + else if( ModifierKeys == Keys.Shift ) + { + // Shift+Click selects nodes between the selected node and here. + TreeNode ndStart = m_SelectedNode; + TreeNode ndEnd = node; + + if( ndStart.Parent == ndEnd.Parent ) + { + // Selected node and clicked node have same parent, easy case. + if( ndStart.Index < ndEnd.Index ) + { + // If the selected node is beneath the clicked node walk down + // selecting each Visible node until we reach the end. + while( ndStart != ndEnd ) + { + ndStart = ndStart.NextVisibleNode; + if( ndStart == null ) break; + ToggleNode( ndStart, true ); + } + } + else if( ndStart.Index == ndEnd.Index ) + { + // Clicked same node, do nothing + } + else + { + // If the selected node is above the clicked node walk up + // selecting each Visible node until we reach the end. + while( ndStart != ndEnd ) + { + ndStart = ndStart.PrevVisibleNode; + if( ndStart == null ) break; + ToggleNode( ndStart, true ); + } + } + } + else + { + // Selected node and clicked node have same parent, hard case. + // We need to find a common parent to determine if we need + // to walk down selecting, or walk up selecting. + + TreeNode ndStartP = ndStart; + TreeNode ndEndP = ndEnd; + int startDepth = Math.Min( ndStartP.Level, ndEndP.Level ); + + // Bring lower node up to common depth + while( ndStartP.Level > startDepth ) + { + ndStartP = ndStartP.Parent; + } + + // Bring lower node up to common depth + while( ndEndP.Level > startDepth ) + { + ndEndP = ndEndP.Parent; + } + + // Walk up the tree until we find the common parent + while( ndStartP.Parent != ndEndP.Parent ) + { + ndStartP = ndStartP.Parent; + ndEndP = ndEndP.Parent; + } + + // Select the node + if( ndStartP.Index < ndEndP.Index ) + { + // If the selected node is beneath the clicked node walk down + // selecting each Visible node until we reach the end. + while( ndStart != ndEnd ) + { + ndStart = ndStart.NextVisibleNode; + if( ndStart == null ) break; + ToggleNode( ndStart, true ); + } + } + else if( ndStartP.Index == ndEndP.Index ) + { + if( ndStart.Level < ndEnd.Level ) + { + while( ndStart != ndEnd ) + { + ndStart = ndStart.NextVisibleNode; + if( ndStart == null ) break; + ToggleNode( ndStart, true ); + } + } + else + { + while( ndStart != ndEnd ) + { + ndStart = ndStart.PrevVisibleNode; + if( ndStart == null ) break; + ToggleNode( ndStart, true ); + } + } + } + else + { + // If the selected node is above the clicked node walk up + // selecting each Visible node until we reach the end. + while( ndStart != ndEnd ) + { + ndStart = ndStart.PrevVisibleNode; + if( ndStart == null ) break; + ToggleNode( ndStart, true ); + } + } + } + } + else + { + // Just clicked a node, select it + SelectSingleNode( node ); + } + + OnAfterSelect( new TreeViewEventArgs( m_SelectedNode ) ); + } + finally + { + this.EndUpdate(); + } + } + + private void ClearSelectedNodes() + { + try + { + foreach( TreeNode node in m_SelectedNodes ) + { + node.BackColor = this.BackColor; + node.ForeColor = this.ForeColor; + } + } + finally + { + m_SelectedNodes.Clear(); + m_SelectedNode = null; + } + } + + private void SelectSingleNode( TreeNode node ) + { + if( node == null ) + { + return; + } + + ClearSelectedNodes(); + ToggleNode( node, true ); + node.EnsureVisible(); + } + + private void ToggleNode( TreeNode node, bool bSelectNode ) + { + if( bSelectNode ) + { + m_SelectedNode = node; + if( !m_SelectedNodes.Contains( node ) ) + { + m_SelectedNodes.Add( node ); + } + node.BackColor = SystemColors.Highlight; + node.ForeColor = SystemColors.HighlightText; + } + else + { + m_SelectedNodes.Remove( node ); + node.BackColor = this.BackColor; + node.ForeColor = this.ForeColor; + } + } + + private void HandleException( Exception ex ) + { + // Perform some error handling here. + // We don't want to bubble errors to the CLR. + MessageBox.Show( ex.Message ); + } + + #endregion + } +} diff --git a/Windows/MainForm.Designer.cs b/Windows/MainForm.Designer.cs index 8a2baef..b7383d3 100644 --- a/Windows/MainForm.Designer.cs +++ b/Windows/MainForm.Designer.cs @@ -54,7 +54,7 @@ this._menuItemFindNext = new System.Windows.Forms.ToolStripMenuItem(); this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this._menuItemAbout = new System.Windows.Forms.ToolStripMenuItem(); - this._nodeTree = new System.Windows.Forms.TreeView(); + this._nodeTree = new NBTExplorer.Vendor.MultiSelectTreeView.MultiSelectTreeView(); this.imageList1 = new System.Windows.Forms.ImageList(this.components); this.toolStrip1 = new System.Windows.Forms.ToolStrip(); this._buttonOpen = new System.Windows.Forms.ToolStripButton(); @@ -680,7 +680,7 @@ private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem searchToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem; - private System.Windows.Forms.TreeView _nodeTree; + private NBTExplorer.Vendor.MultiSelectTreeView.MultiSelectTreeView _nodeTree; private System.Windows.Forms.ToolStrip toolStrip1; private System.Windows.Forms.ToolStripButton _buttonOpen; private System.Windows.Forms.ToolStripButton _buttonSave; diff --git a/Windows/MainForm.cs b/Windows/MainForm.cs index cf68595..4b8b938 100644 --- a/Windows/MainForm.cs +++ b/Windows/MainForm.cs @@ -413,6 +413,294 @@ namespace NBTExplorer.Windows } } + private void DeleteNode (IList nodes) + { + bool? elideChildren = null; + if (!CanOperateOnNodesEx(nodes, DeleteNodePred, out elideChildren)) + return; + + if (elideChildren == true) + nodes = ElideChildren(nodes); + + foreach (TreeNode node in nodes) { + DataNode dataNode = node.Tag as DataNode; + if (dataNode.DeleteNode()) { + UpdateNodeText(node.Parent); + node.Remove(); + } + } + + UpdateUI(); + } + + /*private bool CanDeleteNodes (IList nodes) + { + bool? elideChildren = null; + return CanDeleteNodesEx(nodes, out elideChildren); + } + + private bool CanDeleteNodesEx (IList nodes, out bool? elideChildren) + { + GroupCapabilities caps = GroupCapabilities.All; + elideChildren = null; + + foreach (TreeNode node in nodes) { + if (node == null || !(node.Tag is DataNode)) + return false; + + DataNode dataNode = node.Tag as DataNode; + if (!dataNode.CanDeleteNode) + return false; + + caps &= dataNode.DeleteNodeCapabilities; + + bool elideChildrenFlag = (dataNode.DeleteNodeCapabilities & GroupCapabilities.ElideChildren) == GroupCapabilities.ElideChildren; + if (elideChildren == null) + elideChildren = elideChildrenFlag; + if (elideChildren != elideChildrenFlag) + return false; + } + + if (nodes.Count > 1 && !SufficientCapabilities(nodes, caps)) + return false; + + return true; + }*/ + + delegate bool DataNodePredicate (DataNode dataNode, out GroupCapabilities caps); + + private bool CreateByteNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = GroupCapabilities.Single; + return (dataNode != null) && dataNode.CanCreateTag(TagType.TAG_BYTE); + } + + private bool CreateShortNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = GroupCapabilities.Single; + return (dataNode != null) && dataNode.CanCreateTag(TagType.TAG_SHORT); + } + + private bool CreateIntNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = GroupCapabilities.Single; + return (dataNode != null) && dataNode.CanCreateTag(TagType.TAG_INT); + } + + private bool CreateLongNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = GroupCapabilities.Single; + return (dataNode != null) && dataNode.CanCreateTag(TagType.TAG_LONG); + } + + private bool CreateFloatNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = GroupCapabilities.Single; + return (dataNode != null) && dataNode.CanCreateTag(TagType.TAG_FLOAT); + } + + private bool CreateDoubleNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = GroupCapabilities.Single; + return (dataNode != null) && dataNode.CanCreateTag(TagType.TAG_DOUBLE); + } + + private bool CreateByteArrayNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = GroupCapabilities.Single; + return (dataNode != null) && dataNode.CanCreateTag(TagType.TAG_BYTE_ARRAY); + } + + private bool CreateIntArrayNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = GroupCapabilities.Single; + return (dataNode != null) && dataNode.CanCreateTag(TagType.TAG_INT_ARRAY); + } + + private bool CreateStringNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = GroupCapabilities.Single; + return (dataNode != null) && dataNode.CanCreateTag(TagType.TAG_STRING); + } + + private bool CreateListNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = GroupCapabilities.Single; + return (dataNode != null) && dataNode.CanCreateTag(TagType.TAG_LIST); + } + + private bool CreateCompoundNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = GroupCapabilities.Single; + return (dataNode != null) && dataNode.CanCreateTag(TagType.TAG_COMPOUND); + } + + private bool RenameNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = dataNode.RenameNodeCapabilities; + return (dataNode != null) && dataNode.CanRenameNode; + } + + private bool EditNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = dataNode.EditNodeCapabilities; + return (dataNode != null) && dataNode.CanEditNode; + } + + private bool DeleteNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = dataNode.DeleteNodeCapabilities; + return (dataNode != null) && dataNode.CanDeleteNode; + } + + private bool CutNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = dataNode.CutNodeCapabilities; + return (dataNode != null) && dataNode.CanCutNode; + } + + private bool CopyNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = dataNode.CopyNodeCapabilities; + return (dataNode != null) && dataNode.CanCopyNode; + } + + private bool PasteIntoNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = dataNode.PasteIntoNodeCapabilities; + return (dataNode != null) && dataNode.CanPasteIntoNode; + } + + private bool SearchNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = dataNode.SearchNodeCapabilites; + return (dataNode != null) && dataNode.CanSearchNode; + } + + private bool ReorderNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = dataNode.ReorderNodeCapabilities; + return (dataNode != null) && dataNode.CanReoderNode; + } + + private bool RefreshNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = dataNode.RefreshNodeCapabilites; + return (dataNode != null) && dataNode.CanRefreshNode; + } + + /*private bool CreateTagNodePred (DataNode dataNode, out GroupCapabilities caps) + { + caps = GroupCapabilities.Single; + return (dataNode != null) && dataNode.CanCreateTag + }*/ + + private bool CanOperateOnNodes (IList nodes, DataNodePredicate pred) + { + bool? elideChildren = null; + return CanOperateOnNodesEx(nodes, pred, out elideChildren); + } + + private bool CanOperateOnNodesEx (IList nodes, DataNodePredicate pred, out bool? elideChildren) + { + GroupCapabilities caps = GroupCapabilities.All; + elideChildren = null; + + foreach (TreeNode node in nodes) { + if (node == null || !(node.Tag is DataNode)) + return false; + + DataNode dataNode = node.Tag as DataNode; + GroupCapabilities dataCaps; + if (!pred(dataNode, out dataCaps)) + return false; + + caps &= dataCaps; + + bool elideChildrenFlag = (dataNode.DeleteNodeCapabilities & GroupCapabilities.ElideChildren) == GroupCapabilities.ElideChildren; + if (elideChildren == null) + elideChildren = elideChildrenFlag; + if (elideChildren != elideChildrenFlag) + return false; + } + + if (nodes.Count > 1 && !SufficientCapabilities(nodes, caps)) + return false; + + return true; + } + + private IList ElideChildren (IList nodes) + { + List filtered = new List(); + + foreach (TreeNode node in nodes) { + TreeNode parent = node.Parent; + bool foundParent = false; + + while (parent != null) { + if (nodes.Contains(parent)) { + foundParent = true; + break; + } + parent = parent.Parent; + } + + if (!foundParent) + filtered.Add(node); + } + + return filtered; + } + + private bool CommonContainer (IEnumerable nodes) + { + object container = null; + foreach (TreeNode node in nodes) { + DataNode dataNode = node.Tag as DataNode; + if (container == null) + container = dataNode.Parent; + + if (container != dataNode.Parent) + return false; + } + + return true; + } + + private bool CommonType (IEnumerable nodes) + { + Type datatype = null; + foreach (TreeNode node in nodes) { + DataNode dataNode = node.Tag as DataNode; + if (datatype == null) + datatype = dataNode.GetType(); + + if (datatype != dataNode.GetType()) + return false; + } + + return true; + } + + private bool SufficientCapabilities (IEnumerable nodes, GroupCapabilities commonCaps) + { + bool commonContainer = CommonContainer(nodes); + bool commonType = CommonType(nodes); + + bool pass = true; + if (commonContainer && commonType) + pass &= ((commonCaps & GroupCapabilities.SiblingSameType) == GroupCapabilities.SiblingSameType); + else if (commonContainer && !commonType) + pass &= ((commonCaps & GroupCapabilities.SiblingMixedType) == GroupCapabilities.SiblingMixedType); + else if (!commonContainer && commonType) + pass &= ((commonCaps & GroupCapabilities.MultiSameType) == GroupCapabilities.MultiSameType); + else if (!commonContainer && !commonType) + pass &= ((commonCaps & GroupCapabilities.MultiMixedType) == GroupCapabilities.MultiMixedType); + + return pass; + } + private void CopyNode (TreeNode node) { if (node == null || !(node.Tag is DataNode)) @@ -685,19 +973,42 @@ namespace NBTExplorer.Windows private void UpdateUI () { TreeNode selected = _nodeTree.SelectedNode; - if (selected != null && selected.Tag is DataNode) { + if (_nodeTree.SelectedNodes.Count > 1) { + UpdateUI(_nodeTree.SelectedNodes); + } + else if (selected != null && selected.Tag is DataNode) { UpdateUI(selected.Tag as DataNode); } else { + DisableButtons(_buttonAddTagByte, _buttonAddTagByteArray, _buttonAddTagCompound, _buttonAddTagDouble, _buttonAddTagFloat, + _buttonAddTagInt, _buttonAddTagIntArray, _buttonAddTagList, _buttonAddTagLong, _buttonAddTagShort, + _buttonAddTagString, _buttonCopy, _buttonCut, _buttonDelete, _buttonEdit, _buttonPaste, _buttonRefresh, + _buttonRename); + _buttonSave.Enabled = CheckModifications(); _buttonFindNext.Enabled = false; + DisableMenuItems(_menuItemCopy, _menuItemCut, _menuItemDelete, _menuItemEditValue, _menuItemPaste, _menuItemRefresh, + _menuItemRename); + _menuItemSave.Enabled = _buttonSave.Enabled; _menuItemFind.Enabled = false; _menuItemFindNext.Enabled = _searchState != null; } } + private void DisableButtons (params ToolStripButton[] buttons) + { + foreach (ToolStripButton button in buttons) + button.Enabled = false; + } + + private void DisableMenuItems (params ToolStripMenuItem[] buttons) + { + foreach (ToolStripMenuItem button in buttons) + button.Enabled = false; + } + private void UpdateUI (DataNode node) { if (node == null) @@ -736,6 +1047,47 @@ namespace NBTExplorer.Windows _menuItemRefresh.Enabled = node.CanRefreshNode; _menuItemFind.Enabled = node.CanSearchNode; _menuItemFindNext.Enabled = _searchState != null; + + UpdateUI(_nodeTree.SelectedNodes); + } + + private void UpdateUI (IList nodes) + { + if (nodes == null) + return; + + _buttonAddTagByte.Enabled = CanOperateOnNodes(nodes, CreateByteNodePred); + _buttonAddTagShort.Enabled = CanOperateOnNodes(nodes, CreateShortNodePred); + _buttonAddTagInt.Enabled = CanOperateOnNodes(nodes, CreateIntNodePred); + _buttonAddTagLong.Enabled = CanOperateOnNodes(nodes, CreateLongNodePred); + _buttonAddTagFloat.Enabled = CanOperateOnNodes(nodes, CreateFloatNodePred); + _buttonAddTagDouble.Enabled = CanOperateOnNodes(nodes, CreateDoubleNodePred); + _buttonAddTagByteArray.Enabled = CanOperateOnNodes(nodes, CreateByteArrayNodePred); + _buttonAddTagIntArray.Enabled = CanOperateOnNodes(nodes, CreateIntArrayNodePred); + _buttonAddTagString.Enabled = CanOperateOnNodes(nodes, CreateStringNodePred); + _buttonAddTagList.Enabled = CanOperateOnNodes(nodes, CreateListNodePred); + _buttonAddTagCompound.Enabled = CanOperateOnNodes(nodes, CreateCompoundNodePred); + + _buttonSave.Enabled = CheckModifications(); + _buttonRename.Enabled = CanOperateOnNodes(nodes, RenameNodePred); + _buttonEdit.Enabled = CanOperateOnNodes(nodes, EditNodePred); + _buttonDelete.Enabled = CanOperateOnNodes(nodes, DeleteNodePred); + _buttonCut.Enabled = CanOperateOnNodes(nodes, CutNodePred) && NbtClipboardController.IsInitialized; ; + _buttonCopy.Enabled = CanOperateOnNodes(nodes, CopyNodePred) && NbtClipboardController.IsInitialized; ; + _buttonPaste.Enabled = CanOperateOnNodes(nodes, PasteIntoNodePred) && NbtClipboardController.IsInitialized; ; + _buttonFindNext.Enabled = CanOperateOnNodes(nodes, SearchNodePred) || _searchState != null; + _buttonRefresh.Enabled = CanOperateOnNodes(nodes, RefreshNodePred); + + _menuItemSave.Enabled = _buttonSave.Enabled; + _menuItemRename.Enabled = _buttonRename.Enabled; + _menuItemEditValue.Enabled = _buttonEdit.Enabled; + _menuItemDelete.Enabled = _buttonDelete.Enabled; + _menuItemCut.Enabled = _buttonCut.Enabled; + _menuItemCopy.Enabled = _buttonCopy.Enabled; + _menuItemPaste.Enabled = _buttonPaste.Enabled; + _menuItemFind.Enabled = CanOperateOnNodes(nodes, SearchNodePred); + _menuItemRefresh.Enabled = _buttonRefresh.Enabled; + _menuItemFindNext.Enabled = _searchState != null; } private void UpdateOpenMenu () @@ -786,6 +1138,23 @@ namespace NBTExplorer.Windows list.Insert(0, entry); } + private GroupCapabilities CommonCapabilities (IEnumerable capabilities) + { + GroupCapabilities caps = GroupCapabilities.All; + foreach (GroupCapabilities cap in capabilities) + caps &= cap; + return caps; + } + + public void ActionDeleteNode () + { + DeleteNode(_nodeTree.SelectedNodes); + + _nodeTree.SelectedNodes.Clear(); + _nodeTree.SelectedNode = null; + UpdateUI(); + } + #region Event Handlers private void MainForm_Closing (object sender, CancelEventArgs e) @@ -867,7 +1236,7 @@ namespace NBTExplorer.Windows private void _buttonDelete_Click (object sender, EventArgs e) { - DeleteNode(_nodeTree.SelectedNode); + ActionDeleteNode(); } private void _buttonCopy_Click (object sernder, EventArgs e) @@ -995,7 +1364,7 @@ namespace NBTExplorer.Windows private void _menuItemDelete_Click (object sender, EventArgs e) { - DeleteNode(_nodeTree.SelectedNode); + ActionDeleteNode(); } private void _menuItemCopy_Click (object sender, EventArgs e)