mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-25 11:49:52 +01:00
7c0bc50b4a
Inc. Signed-off-by: Gabor Apati-Nagy<gabor.apati-nagy@citrix.com>
380 lines
13 KiB
C#
380 lines
13 KiB
C#
/* Copyright (c) Citrix Systems, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms,
|
|
* with or without modification, are permitted provided
|
|
* that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Drawing;
|
|
using System.Windows.Forms;
|
|
using XenAdmin.Core;
|
|
using System.Collections.Generic;
|
|
using XenAdmin.Network;
|
|
using XenAdmin.XenSearch;
|
|
|
|
using XenAPI;
|
|
using Message = System.Windows.Forms.Message;
|
|
|
|
namespace XenAdmin.Controls
|
|
{
|
|
/**
|
|
* Flicker-free TreeView, from
|
|
* http://www.codeguru.com/forum/archive/index.php/t-182326.html
|
|
*
|
|
* This has been specialised for the mainwindow treeview
|
|
* (persistent selection, scroll etc) so it should be used
|
|
* with caution for other things
|
|
*/
|
|
[FormFontFixer.PreserveFonts(true)]
|
|
public class FlickerFreeTreeView : VirtualTreeView
|
|
{
|
|
private List<VirtualTreeNode.PersistenceInfo> _persistedSelectionInfo;
|
|
private IList<object> _persistedTopNode;
|
|
|
|
protected override void OnBeforeExpand(VirtualTreeViewCancelEventArgs e)
|
|
{
|
|
if (TopNode == null)
|
|
{
|
|
_persistedTopNode = new List<object>();
|
|
}
|
|
else
|
|
{
|
|
_persistedTopNode = TopNode.GetPersistenceInfo().Path;
|
|
}
|
|
|
|
base.OnBeforeExpand(e);
|
|
}
|
|
|
|
protected override void OnAfterExpand(VirtualTreeViewEventArgs e)
|
|
{
|
|
base.OnAfterExpand(e);
|
|
if (_persistedTopNode != null)
|
|
{
|
|
int hPos = HScrollPos;
|
|
TopNode = ClosestMatch(_persistedTopNode);
|
|
HScrollPos = hPos;
|
|
}
|
|
}
|
|
|
|
protected override void WndProc(ref Message m)
|
|
{
|
|
if (m.Msg == Core.Win32.WM_ERASEBKGND)
|
|
{
|
|
m.Result = IntPtr.Zero;
|
|
return;
|
|
}
|
|
base.WndProc(ref m);
|
|
}
|
|
|
|
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
|
|
{
|
|
if (keyData == Keys.Apps)
|
|
OnKeyPress(new KeyPressEventArgs((char)keyData));
|
|
return base.ProcessCmdKey(ref msg, keyData);
|
|
}
|
|
|
|
#region Persist selection across BeginUpdate() / EndUpdate()
|
|
|
|
/// <summary>
|
|
/// We have to be a bit more clever now, as things can appear
|
|
/// more than once in the treeview (ie Group by tag)
|
|
///
|
|
/// Therefore we try and keep selection on the correct
|
|
/// object, even during migration or moving between folders.
|
|
/// In the case we can't, we default to its parent and its
|
|
/// parent's parent and so on.
|
|
/// </summary>
|
|
public new void BeginUpdate()
|
|
{
|
|
// Save the selection...
|
|
_persistedSelectionInfo = new List<VirtualTreeNode>(SelectedNodes).ConvertAll(n => n.GetPersistenceInfo());
|
|
|
|
// Save the scroll position..
|
|
if (TopNode == null)
|
|
{
|
|
_persistedTopNode = new List<object>();
|
|
}
|
|
else
|
|
{
|
|
_persistedTopNode = TopNode.GetPersistenceInfo().Path;
|
|
}
|
|
}
|
|
|
|
public new void EndUpdate()
|
|
{
|
|
base.EndUpdate();
|
|
|
|
int hPos = HScrollPos;
|
|
VirtualTreeNode restoredTopNode = ClosestMatch(_persistedTopNode);
|
|
|
|
if (restoredTopNode != TopNode)
|
|
{
|
|
TopNode = restoredTopNode;
|
|
|
|
// Restore the scroll position...
|
|
// Setting TopNode alters _both_ scrollbars. This sets the vertical one to the old position
|
|
// and the horizontal one to a different one depending on the width on the TopNode.
|
|
HScrollPos = hPos;
|
|
}
|
|
|
|
// Restore the selection...
|
|
if (_persistedSelectionInfo == null || _persistedSelectionInfo.Count == 0)
|
|
{
|
|
SelectedNode = Nodes[0];
|
|
}
|
|
else
|
|
{
|
|
RestoreSelection();
|
|
}
|
|
|
|
// if the selected nodes haven't change, but the selected tags have
|
|
// then a selections changed event still needs to be fired.
|
|
foreach (VirtualTreeNode node in SelectedNodes)
|
|
{
|
|
VirtualTreeNode.PersistenceInfo info = new VirtualTreeNode.PersistenceInfo(node);
|
|
|
|
if (!_persistedSelectionInfo.Contains(info))
|
|
{
|
|
// selection is different to old one. So fire an event.
|
|
|
|
ForceSelectionsChanged();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Try and restore the selection.
|
|
/// First, look for the object in the same position
|
|
/// Second, find the object in the maximal sub tree where it appeared only once
|
|
/// Finally, just select one of the parents
|
|
/// </summary>
|
|
private void RestoreSelection()
|
|
{
|
|
List<VirtualTreeNode> newSelectedNodes = new List<VirtualTreeNode>();
|
|
|
|
foreach (VirtualTreeNode.PersistenceInfo info in _persistedSelectionInfo)
|
|
{
|
|
VirtualTreeNode match;
|
|
|
|
// First, look for the object in the same position
|
|
if (TryExactMatch(info.Path, out match) >= info.Path.Count)
|
|
{
|
|
TryToSelectNode(newSelectedNodes, match);
|
|
continue;
|
|
}
|
|
|
|
// Second, find the object in the maximal sub tree where it appeared only once
|
|
if (TryExactMatch(info.PathToMaximalSubTree, out match) >= info.PathToMaximalSubTree.Count)
|
|
{
|
|
match = FindNodeIn(match, info.Tag);
|
|
if (match != null)
|
|
{
|
|
// since node has moved, make sure it's visible.
|
|
match.EnsureVisible();
|
|
TryToSelectNode(newSelectedNodes, match);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newSelectedNodes.Count == 0)
|
|
{
|
|
foreach (VirtualTreeNode.PersistenceInfo info in _persistedSelectionInfo)
|
|
{
|
|
// Finally, just select one of the parents
|
|
TryToSelectNode(newSelectedNodes, ClosestMatch(info.Path));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// restore selection
|
|
SelectedNodes.SetContents(newSelectedNodes);
|
|
}
|
|
|
|
private void TryToSelectNode(List<VirtualTreeNode> nodes, VirtualTreeNode node)
|
|
{
|
|
if (!CanSelectNode(node))
|
|
{
|
|
TryToSelectNode(nodes, node.Parent);
|
|
}
|
|
else if (!nodes.Contains(node))
|
|
{
|
|
nodes.Add(node);
|
|
}
|
|
}
|
|
|
|
public bool CanSelectNode(VirtualTreeNode node)
|
|
{
|
|
return node.Tag == null || node.Tag is IXenObject || node.Tag is GroupingTag || node.Tag is Search;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Try and find a node by following the path in selection
|
|
/// </summary>
|
|
/// <param name="selection"></param>
|
|
/// <param name="match"></param>
|
|
/// <returns>How far it got along the path before it bailed.
|
|
/// If it returns >= selection.Count, then it succeded</returns>
|
|
public int TryExactMatch(IList<Object> selection, out VirtualTreeNode match)
|
|
{
|
|
int i = 0;
|
|
match = Nodes[0];
|
|
|
|
while (i < selection.Count)
|
|
{
|
|
bool found = false;
|
|
|
|
foreach (VirtualTreeNode child in match.Nodes)
|
|
{
|
|
if (child.Tag.Equals(selection[i]))
|
|
{
|
|
match = child;
|
|
found = true;
|
|
i++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
break;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
public VirtualTreeNode FindNodeIn(VirtualTreeNode match, Object o)
|
|
{
|
|
if (match.Tag == o)
|
|
return match;
|
|
|
|
foreach (VirtualTreeNode child in match.Nodes)
|
|
{
|
|
VirtualTreeNode result = FindNodeIn(child, o);
|
|
if (result != null)
|
|
return result;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private VirtualTreeNode ClosestMatch(IList<Object> selection)
|
|
{
|
|
VirtualTreeNode currentNode;
|
|
int i = TryExactMatch(selection, out currentNode);
|
|
|
|
// We never got down the first step;
|
|
// this usually means selection changing over
|
|
// connection / disconnect. Skank it up
|
|
if (i == 0 && selection.Count > 0)
|
|
{
|
|
IXenObject o = selection[0] as IXenObject;
|
|
if (o != null)
|
|
{
|
|
IXenConnection connection = o.Connection;
|
|
|
|
foreach (VirtualTreeNode child in currentNode.Nodes)
|
|
{
|
|
IXenObject o2 = child.Tag as IXenObject;
|
|
if (o2 == null)
|
|
continue;
|
|
|
|
if (o2.Connection == connection)
|
|
{
|
|
currentNode = child;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return currentNode;
|
|
}
|
|
|
|
#endregion
|
|
|
|
public bool TryToSelectNewNode(Predicate<object> tagMatch, bool selectNode, bool expandNode, bool ensureNodeVisible)
|
|
{
|
|
foreach (VirtualTreeNode node in AllNodes)
|
|
{
|
|
if (tagMatch(node.Tag))
|
|
{
|
|
if (selectNode)
|
|
SelectedNode = node;
|
|
|
|
if (expandNode)
|
|
node.Expand();
|
|
|
|
if (ensureNodeVisible)
|
|
node.EnsureVisible();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects the specified object in the tree.
|
|
/// </summary>
|
|
/// <param name="o">The object to be selected.</param>
|
|
/// <param name="node">The node at which to start.</param>
|
|
/// <param name="expand">Expand the node when it's found.</param>
|
|
/// <param name="cancelled">if set to <c>true</c> then the node for the
|
|
/// specified object was not allowed to be selected.</param>
|
|
/// <returns>A value indicating whether selection was successful.</returns>
|
|
public bool SelectObject(IXenObject o, VirtualTreeNode node, bool expand, ref bool cancelled)
|
|
{
|
|
IXenObject candidate = node.Tag as IXenObject;
|
|
|
|
if (o == null || (candidate != null && candidate.opaque_ref == o.opaque_ref))
|
|
{
|
|
if (!CanSelectNode(node))
|
|
{
|
|
cancelled = true;
|
|
return false;
|
|
}
|
|
|
|
SelectedNode = node;
|
|
|
|
if (expand)
|
|
node.Expand();
|
|
|
|
return true;
|
|
}
|
|
|
|
foreach (VirtualTreeNode child in node.Nodes)
|
|
{
|
|
if (SelectObject(o, child, expand, ref cancelled))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|