mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-11-25 06:16:37 +01:00
a4fe507adf
In some cases calling Control.Invoke() from a background thread causes that thread to go in a "sleep, wait, or join" mode, while waiting for Invoke to happen, although the UI thread is running normally. If the Control is the MainWindow, it works as expected, but we've seen it happening while connecting or disconnecting from a large pool, on calling Invoke on controls like NavigationView, AlertSummaryPage, HistoryPage, etc. To fix this, we call the Invoke on the MainWindow in all the places where we've seen the issue. With this changes, the previous fix for CA-148245 (call RequestRefreshTreeView on CacheClearing event) is not needed anymore, so I removed that call. Signed-off-by: Mihaela Stoica <mihaela.stoica@citrix.com>
886 lines
31 KiB
C#
886 lines
31 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.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Data;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
|
|
using XenAdmin.Commands;
|
|
using XenAdmin.Core;
|
|
using XenAdmin.Model;
|
|
using XenAdmin.Network;
|
|
using XenAdmin.XenSearch;
|
|
|
|
using XenAPI;
|
|
|
|
namespace XenAdmin.Controls.MainWindowControls
|
|
{
|
|
public partial class NavigationView : UserControl
|
|
{
|
|
#region Private fields
|
|
|
|
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
private readonly SelectionManager selectionManager = new SelectionManager();
|
|
private readonly MainWindowTreeBuilder treeBuilder;
|
|
private readonly UpdateManager treeViewUpdateManager = new UpdateManager(30 * 1000);
|
|
|
|
private VirtualTreeNode _highlightedDragTarget;
|
|
private int ignoreRefreshTreeView;
|
|
private bool calledRefreshTreeView;
|
|
private bool inMajorChange;
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
[Browsable(true)]
|
|
public event Action TreeViewSelectionChanged;
|
|
|
|
[Browsable(true)]
|
|
public event Action TreeNodeBeforeSelected;
|
|
|
|
[Browsable(true)]
|
|
public event Action TreeNodeClicked;
|
|
|
|
[Browsable(true)]
|
|
public event Action TreeNodeRightClicked;
|
|
|
|
[Browsable(true)]
|
|
public event Action TreeViewRefreshed;
|
|
|
|
[Browsable(true)]
|
|
public event Action TreeViewRefreshSuspended;
|
|
|
|
[Browsable(true)]
|
|
public event Action TreeViewRefreshResumed;
|
|
|
|
internal event Action<string> DragDropCommandActivated;
|
|
|
|
#endregion
|
|
|
|
public NavigationView()
|
|
{
|
|
InitializeComponent();
|
|
|
|
panel1.BackColor = Program.TitleBarBorderColor;
|
|
|
|
treeView.ImageList = Images.ImageList16;
|
|
if (treeView.ItemHeight < 18)
|
|
treeView.ItemHeight = 18;
|
|
//otherwise it's too close together on XP and the icons crash into each other
|
|
|
|
VirtualTreeNode n = new VirtualTreeNode(Messages.XENCENTER);
|
|
n.NodeFont = Program.DefaultFont;
|
|
treeView.Nodes.Add(n);
|
|
treeView.SelectedNode = treeView.Nodes[0];
|
|
|
|
treeBuilder = new MainWindowTreeBuilder(treeView);
|
|
treeViewUpdateManager.Update += treeViewUpdateManager_Update;
|
|
}
|
|
|
|
#region Accessors
|
|
|
|
public NavigationPane.NavigationMode NavigationMode { get; set; }
|
|
|
|
public Search CurrentSearch { get; set; }
|
|
|
|
public bool InSearchMode { get; set; }
|
|
|
|
internal SelectionBroadcaster SelectionManager
|
|
{
|
|
get { return selectionManager; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Connection
|
|
|
|
public void XenConnectionCollectionChanged(CollectionChangeEventArgs e)
|
|
{
|
|
IXenConnection connection = (IXenConnection)e.Element;
|
|
if (connection == null)
|
|
return;
|
|
|
|
if (e.Action == CollectionChangeAction.Add)
|
|
{
|
|
connection.BeforeMajorChange += Connection_BeforeMajorChange;
|
|
connection.AfterMajorChange += Connection_AfterMajorChange;
|
|
}
|
|
else if (e.Action == CollectionChangeAction.Remove)
|
|
{
|
|
connection.BeforeMajorChange -= Connection_BeforeMajorChange;
|
|
connection.AfterMajorChange -= Connection_AfterMajorChange;
|
|
}
|
|
}
|
|
|
|
private void Connection_BeforeMajorChange(object sender, ConnectionMajorChangeEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
Program.Invoke(this, () =>
|
|
{
|
|
if (!e.Background)
|
|
{
|
|
if (inMajorChange)
|
|
return;
|
|
inMajorChange = true;
|
|
}
|
|
SuspendRefreshTreeView();
|
|
});
|
|
}
|
|
catch (Exception exn)
|
|
{
|
|
log.Error(exn, exn);
|
|
// Can do nothing more about this.
|
|
}
|
|
}
|
|
|
|
private void Connection_AfterMajorChange(object sender, ConnectionMajorChangeEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
Program.Invoke(this, () =>
|
|
{
|
|
ResumeRefreshTreeView();
|
|
|
|
if (!e.Background)
|
|
inMajorChange = false;
|
|
});
|
|
}
|
|
catch (Exception exn)
|
|
{
|
|
log.Error(exn, exn);
|
|
// Can do nothing more about this.
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region SearchBox
|
|
|
|
public void ResetSeachBox()
|
|
{
|
|
searchTextBox.Reset();
|
|
}
|
|
|
|
private void searchTextBox_TextChanged(object sender, EventArgs e)
|
|
{
|
|
RequestRefreshTreeView();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region TreeView
|
|
|
|
private void treeViewUpdateManager_Update(object sender, EventArgs e)
|
|
{
|
|
Program.AssertOffEventThread();
|
|
RefreshTreeView();
|
|
}
|
|
|
|
|
|
public void MajorChange(MethodInvoker f)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
if (inMajorChange)
|
|
{
|
|
f();
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
inMajorChange = true;
|
|
SuspendRefreshTreeView();
|
|
f();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
log.Debug("Exception thrown by target of MajorChange.", e);
|
|
log.Debug(e, e);
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
ResumeRefreshTreeView();
|
|
inMajorChange = false;
|
|
}
|
|
}
|
|
|
|
public void SaveAndRestoreTreeViewFocus(MethodInvoker f)
|
|
{
|
|
bool treeViewHasFocus = treeView.ContainsFocus;
|
|
searchTextBox.SaveState();
|
|
|
|
f();
|
|
|
|
if (!InSearchMode && treeViewHasFocus)
|
|
FocusTreeView();
|
|
|
|
searchTextBox.RestoreState();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects the specified object in the treeview.
|
|
/// </summary>
|
|
/// <param name="o">The object to be selected.</param>
|
|
/// <param name="expand">A value specifying whether the node should be expanded when it's found.
|
|
/// If false, the node is left in the state it's found in.</param>
|
|
/// <returns>A value indicating whether selection was successful.</returns>
|
|
public bool SelectObject(IXenObject o, bool expand)
|
|
{
|
|
bool cancelled = false;
|
|
if (treeView.Nodes.Count == 0)
|
|
return false;
|
|
|
|
bool success = treeView.SelectObject(o, treeView.Nodes[0], expand, ref cancelled);
|
|
|
|
if (!success && !cancelled && searchTextBox.Text.Length > 0)
|
|
{
|
|
// if the node could not be found and the node *is* selectable then it means that
|
|
// node isn't visible with the current search. So clear the search and try and
|
|
// select the object again.
|
|
|
|
// clear search.
|
|
ResetSeachBox();
|
|
|
|
// update the treeview
|
|
RefreshTreeView();
|
|
|
|
// and try again.
|
|
return treeView.SelectObject(o, treeView.Nodes[0], expand, ref cancelled);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
public bool TryToSelectNewNode(Predicate<object> tagMatch, bool selectNode, bool expandNode, bool ensureNodeVisible)
|
|
{
|
|
return treeView.TryToSelectNewNode(tagMatch, selectNode, expandNode, ensureNodeVisible);
|
|
}
|
|
|
|
public void EditSelectedNode()
|
|
{
|
|
SuspendRefreshTreeView();
|
|
treeView.LabelEdit = true;
|
|
treeView.SelectedNode.BeginEdit();
|
|
}
|
|
|
|
public void FocusTreeView()
|
|
{
|
|
treeView.Focus();
|
|
}
|
|
|
|
public void ForceTreeViewSelectionsChanged()
|
|
{
|
|
treeView.ForceSelectionsChanged();
|
|
}
|
|
|
|
public void RequestRefreshTreeView()
|
|
{
|
|
Program.BeginInvoke(Program.MainWindow, treeViewUpdateManager.RequestUpdate);
|
|
}
|
|
|
|
private void SuspendRefreshTreeView()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
if (ignoreRefreshTreeView == 0)
|
|
calledRefreshTreeView = false;
|
|
ignoreRefreshTreeView++;
|
|
|
|
if (TreeViewRefreshSuspended != null)
|
|
TreeViewRefreshSuspended();
|
|
}
|
|
|
|
private void ResumeRefreshTreeView()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
ignoreRefreshTreeView--;
|
|
if (ignoreRefreshTreeView == 0 && calledRefreshTreeView)
|
|
RequestRefreshTreeView();
|
|
|
|
if (TreeViewRefreshResumed != null)
|
|
TreeViewRefreshResumed();
|
|
}
|
|
|
|
private void RefreshTreeView()
|
|
{
|
|
if (Disposing || IsDisposed || Program.Exiting)
|
|
return;
|
|
|
|
Program.Invoke(Program.MainWindow, () =>
|
|
{
|
|
if (ignoreRefreshTreeView > 0)
|
|
{
|
|
calledRefreshTreeView = true;
|
|
return;
|
|
}
|
|
|
|
ignoreRefreshTreeView++; // Some events can be ignored while rebuilding the tree
|
|
|
|
try
|
|
{
|
|
var newRootNode = treeBuilder.CreateNewRootNode(CurrentSearch.AddFullTextFilter(searchTextBox.Text), NavigationMode);
|
|
treeBuilder.RefreshTreeView(newRootNode, searchTextBox.Text, NavigationMode);
|
|
}
|
|
catch (Exception exn)
|
|
{
|
|
log.Error(exn, exn);
|
|
#if DEBUG
|
|
if (Debugger.IsAttached)
|
|
throw;
|
|
#endif
|
|
}
|
|
finally
|
|
{
|
|
ignoreRefreshTreeView--;
|
|
}
|
|
|
|
if (TreeViewRefreshed != null)
|
|
TreeViewRefreshed();
|
|
});
|
|
}
|
|
|
|
private static Pool PoolAncestorOfNode(VirtualTreeNode node)
|
|
{
|
|
while (node != null)
|
|
{
|
|
var pool = node.Tag as Pool;
|
|
if (pool != null)
|
|
return pool;
|
|
|
|
node = node.Parent;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static Host HostAncestorOfNode(VirtualTreeNode node)
|
|
{
|
|
while (node != null)
|
|
{
|
|
var host = node.Tag as Host;
|
|
if (host != null)
|
|
return host;
|
|
|
|
node = node.Parent;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static GroupingTag GroupAncestorOfNode(VirtualTreeNode node)
|
|
{
|
|
while (node != null)
|
|
{
|
|
var group = node.Tag as GroupingTag;
|
|
if (group != null)
|
|
return group;
|
|
|
|
node = node.Parent;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private bool CanDrag()
|
|
{
|
|
if (NavigationMode == NavigationPane.NavigationMode.Infrastructure
|
|
|| NavigationMode == NavigationPane.NavigationMode.SavedSearch)
|
|
{
|
|
return Program.MainWindow.SelectionManager.Selection.AllItemsAre<Host>()
|
|
|| Program.MainWindow.SelectionManager.Selection.AllItemsAre<VM>(vm => !vm.is_a_template);
|
|
}
|
|
|
|
foreach (SelectedItem item in Program.MainWindow.SelectionManager.Selection)
|
|
{
|
|
if (item.XenObject == null || item.Connection == null || !item.Connection.IsConnected)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private List<DragDropCommand> GetDragDropCommands(VirtualTreeNode targetNode, IDataObject dragData)
|
|
{
|
|
List<DragDropCommand> commands = new List<DragDropCommand>();
|
|
commands.Add(new DragDropAddHostToPoolCommand(Program.MainWindow, targetNode, dragData));
|
|
commands.Add(new DragDropMigrateVMCommand(Program.MainWindow, targetNode, dragData));
|
|
commands.Add(new DragDropRemoveHostFromPoolCommand(Program.MainWindow, targetNode, dragData));
|
|
|
|
if (NavigationMode == NavigationPane.NavigationMode.Tags
|
|
|| NavigationMode == NavigationPane.NavigationMode.Folders
|
|
|| NavigationMode == NavigationPane.NavigationMode.CustomFields
|
|
|| NavigationMode == NavigationPane.NavigationMode.vApps)
|
|
{
|
|
commands.Add(new DragDropTagCommand(Program.MainWindow, targetNode, dragData));
|
|
commands.Add(new DragDropIntoFolderCommand(Program.MainWindow, targetNode, dragData));
|
|
}
|
|
|
|
return commands;
|
|
}
|
|
|
|
private void ClearHighlightedDragTarget()
|
|
{
|
|
if (_highlightedDragTarget != null)
|
|
{
|
|
_highlightedDragTarget.BackColor = treeView.BackColor;
|
|
_highlightedDragTarget.ForeColor = treeView.ForeColor;
|
|
_highlightedDragTarget = null;
|
|
treeBuilder.HighlightedDragTarget = null;
|
|
|
|
if (DragDropCommandActivated != null)
|
|
DragDropCommandActivated(null);
|
|
}
|
|
}
|
|
|
|
private void HandleNodeRightClick(VirtualTreeNodeMouseClickEventArgs e)
|
|
{
|
|
if (e.Button != MouseButtons.Right)
|
|
return;
|
|
|
|
if (treeView.SelectedNodes.Count == 0)
|
|
{
|
|
treeView.SelectedNode = e.Node;
|
|
|
|
if (treeView.SelectedNode != e.Node) // if node is unselectable in TreeView_BeforeSelect: CA-26615
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (TreeNodeRightClicked != null)
|
|
TreeNodeRightClicked();
|
|
|
|
TreeContextMenu.Items.Clear();
|
|
|
|
if (e.Node == treeView.Nodes[0] && treeView.SelectedNodes.Count == 1)
|
|
{
|
|
if (e.Node.Tag == null)// XenCenter (top most)
|
|
{
|
|
TreeContextMenu.Items.Add(new CommandToolStripMenuItem(new AddHostCommand(Program.MainWindow), true));
|
|
TreeContextMenu.Items.Add(new CommandToolStripMenuItem(new NewPoolCommand(Program.MainWindow, new SelectedItem[0]), true));
|
|
TreeContextMenu.Items.Add(new CommandToolStripMenuItem(new ConnectAllHostsCommand(Program.MainWindow), true));
|
|
TreeContextMenu.Items.Add(new CommandToolStripMenuItem(new DisconnectAllHostsCommand(Program.MainWindow), true));
|
|
}
|
|
else
|
|
{
|
|
var groupingTag = e.Node.Tag as GroupingTag;
|
|
if (groupingTag != null && groupingTag.Grouping as OrganizationViewFolders != null)
|
|
TreeContextMenu.Items.Add(new CommandToolStripMenuItem(
|
|
new NewFolderCommand(Program.MainWindow, new[] { new SelectedItem(groupingTag, e.Node) }),
|
|
true));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TreeContextMenu.Items.AddRange(Program.MainWindow.ContextMenuBuilder.Build(Program.MainWindow.SelectionManager.Selection));
|
|
}
|
|
|
|
int insertIndex = TreeContextMenu.Items.Count;
|
|
|
|
if (TreeContextMenu.Items.Count > 0)
|
|
{
|
|
CommandToolStripMenuItem lastItem = TreeContextMenu.Items[TreeContextMenu.Items.Count - 1] as CommandToolStripMenuItem;
|
|
|
|
if (lastItem != null && lastItem.Command is PropertiesCommand)
|
|
insertIndex--;
|
|
}
|
|
|
|
AddExpandCollapseItems(insertIndex, treeView.SelectedNodes);
|
|
AddOrgViewItems(insertIndex, treeView.SelectedNodes);
|
|
|
|
if (TreeContextMenu.Items.Count > 0)
|
|
{
|
|
TreeContextMenu.Show(treeView.PointToScreen(Point.Empty) + (Size)treeView.LastMouseDownEventPosition);
|
|
}
|
|
}
|
|
|
|
private void AddExpandCollapseItems(int insertIndex, IList<VirtualTreeNode> nodes)
|
|
{
|
|
if (nodes.Count == 1 && nodes[0].Nodes.Count == 0)
|
|
return;
|
|
|
|
Command cmd = new CollapseChildTreeNodesCommand(Program.MainWindow, nodes);
|
|
if (cmd.CanExecute())
|
|
TreeContextMenu.Items.Insert(insertIndex, new CommandToolStripMenuItem(cmd, true));
|
|
|
|
cmd = new ExpandTreeNodesCommand(Program.MainWindow, nodes);
|
|
if (cmd.CanExecute())
|
|
TreeContextMenu.Items.Insert(insertIndex, new CommandToolStripMenuItem(cmd, true));
|
|
}
|
|
|
|
private void AddOrgViewItems(int insertIndex, IList<VirtualTreeNode> nodes)
|
|
{
|
|
if (NavigationMode == NavigationPane.NavigationMode.Infrastructure
|
|
|| NavigationMode == NavigationPane.NavigationMode.SavedSearch
|
|
|| NavigationMode == NavigationPane.NavigationMode.Notifications
|
|
|| nodes.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Command cmd = new RemoveFromFolderCommand(Program.MainWindow, nodes);
|
|
|
|
if (cmd.CanExecute())
|
|
TreeContextMenu.Items.Insert(insertIndex, new CommandToolStripMenuItem(cmd, true));
|
|
|
|
cmd = new UntagCommand(Program.MainWindow, nodes);
|
|
|
|
if (cmd.CanExecute())
|
|
TreeContextMenu.Items.Insert(insertIndex, new CommandToolStripMenuItem(cmd, true));
|
|
}
|
|
|
|
|
|
private void treeView_AfterLabelEdit(object sender, VirtualNodeLabelEditEventArgs e)
|
|
{
|
|
VirtualTreeNode node = e.Node;
|
|
treeView.LabelEdit = false;
|
|
Folder folder = e.Node.Tag as Folder;
|
|
GroupingTag groupingTag = e.Node.Tag as GroupingTag;
|
|
Command command = null;
|
|
object newTag = null;
|
|
|
|
EventHandler<RenameCompletedEventArgs> completed = delegate(object s, RenameCompletedEventArgs ee)
|
|
{
|
|
Program.Invoke(this, delegate
|
|
{
|
|
ResumeRefreshTreeView();
|
|
|
|
if (ee.Success)
|
|
{
|
|
// the new tag is updated on the node here. This ensures that the node stays selected
|
|
// when the treeview is refreshed. If you don't set the tag like this, the treeview refresh code notices
|
|
// that the tags are different and selects the parent node instead of this node.
|
|
|
|
// if the command fails for some reason, the refresh code will correctly revert the tag back to the original.
|
|
node.Tag = newTag;
|
|
RefreshTreeView();
|
|
|
|
// since the selected node doesn't actually change, then a selectionsChanged message doesn't get fired
|
|
// and the selection doesn't get updated to be the new tag/folder. Do it manually here instead.
|
|
treeView_SelectionsChanged(treeView, EventArgs.Empty);
|
|
}
|
|
});
|
|
};
|
|
|
|
if (!string.IsNullOrEmpty(e.Label))
|
|
{
|
|
if (folder != null)
|
|
{
|
|
RenameFolderCommand cmd = new RenameFolderCommand(Program.MainWindow, folder, e.Label);
|
|
command = cmd;
|
|
cmd.Completed += completed;
|
|
newTag = new Folder(null, e.Label);
|
|
}
|
|
else if (groupingTag != null)
|
|
{
|
|
RenameTagCommand cmd = new RenameTagCommand(Program.MainWindow, groupingTag.Group.ToString(), e.Label);
|
|
command = cmd;
|
|
cmd.Completed += completed;
|
|
newTag = new GroupingTag(groupingTag.Grouping, groupingTag.Parent, e.Label);
|
|
}
|
|
}
|
|
|
|
if (command != null && command.CanExecute())
|
|
{
|
|
command.Execute();
|
|
}
|
|
else
|
|
{
|
|
ResumeRefreshTreeView();
|
|
e.CancelEdit = true;
|
|
}
|
|
}
|
|
|
|
private void treeView_BeforeSelect(object sender, VirtualTreeViewCancelEventArgs e)
|
|
{
|
|
if (e.Node == null)
|
|
return;
|
|
|
|
if (!treeView.CanSelectNode(e.Node))
|
|
{
|
|
e.Cancel = true;
|
|
return;
|
|
}
|
|
|
|
if (TreeNodeBeforeSelected != null)
|
|
TreeNodeBeforeSelected();
|
|
}
|
|
|
|
private void treeView_ItemDrag(object sender, VirtualTreeViewItemDragEventArgs e)
|
|
{
|
|
foreach (VirtualTreeNode node in e.Nodes)
|
|
{
|
|
if (node == null || node.TreeView == null)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// select the node if it isn't already selected
|
|
if (e.Nodes.Count == 1 && treeView.SelectedNode != e.Nodes[0])
|
|
{
|
|
treeView.SelectedNode = e.Nodes[0];
|
|
}
|
|
|
|
if (CanDrag())
|
|
{
|
|
DoDragDrop(new List<VirtualTreeNode>(e.Nodes).ToArray(), DragDropEffects.Move);
|
|
}
|
|
}
|
|
|
|
private void treeView_DragDrop(object sender, DragEventArgs e)
|
|
{
|
|
ClearHighlightedDragTarget();
|
|
|
|
VirtualTreeNode targetNode = treeView.GetNodeAt(treeView.PointToClient(new Point(e.X, e.Y)));
|
|
|
|
foreach (DragDropCommand cmd in GetDragDropCommands(targetNode, e.Data))
|
|
{
|
|
if (cmd.CanExecute())
|
|
{
|
|
cmd.Execute();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void treeView_DragEnter(object sender, DragEventArgs e)
|
|
{
|
|
e.Effect = DragDropEffects.None;
|
|
VirtualTreeNode targetNode = treeView.GetNodeAt(treeView.PointToClient(new Point(e.X, e.Y)));
|
|
foreach (DragDropCommand cmd in GetDragDropCommands(targetNode, e.Data))
|
|
{
|
|
if (cmd.CanExecute())
|
|
{
|
|
e.Effect = DragDropEffects.Move;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void treeView_DragLeave(object sender, EventArgs e)
|
|
{
|
|
ClearHighlightedDragTarget();
|
|
}
|
|
|
|
private void treeView_DragOver(object sender, DragEventArgs e)
|
|
{
|
|
// CA-11457: When dragging in resource tree, view doesn't scroll
|
|
// http://www.fmsinc.com/freE/NewTips/NET/NETtip21.asp
|
|
|
|
const int SCROLL_REGION = 20;
|
|
|
|
Point pt = treeView.PointToClient(Cursor.Position);
|
|
VirtualTreeNode targetNode = treeView.GetNodeAt(treeView.PointToClient(new Point(e.X, e.Y)));
|
|
|
|
if ((pt.Y + SCROLL_REGION) > treeView.Height)
|
|
{
|
|
Win32.SendMessage(treeView.Handle, Win32.WM_VSCROLL, new IntPtr(1), IntPtr.Zero);
|
|
}
|
|
else if (pt.Y < SCROLL_REGION)
|
|
{
|
|
Win32.SendMessage(treeView.Handle, Win32.WM_VSCROLL, IntPtr.Zero, IntPtr.Zero);
|
|
}
|
|
|
|
VirtualTreeNode targetToHighlight = null;
|
|
|
|
string statusBarText = null;
|
|
foreach (DragDropCommand cmd in GetDragDropCommands(targetNode, e.Data))
|
|
{
|
|
if (cmd.CanExecute())
|
|
targetToHighlight = cmd.HighlightNode;
|
|
|
|
if (cmd.StatusBarText != null)
|
|
statusBarText = cmd.StatusBarText;
|
|
}
|
|
|
|
if (DragDropCommandActivated != null)
|
|
DragDropCommandActivated(statusBarText);
|
|
|
|
if (targetToHighlight != null)
|
|
{
|
|
if (_highlightedDragTarget != targetToHighlight)
|
|
{
|
|
ClearHighlightedDragTarget();
|
|
treeBuilder.HighlightedDragTarget = targetToHighlight.Tag;
|
|
_highlightedDragTarget = targetToHighlight;
|
|
_highlightedDragTarget.BackColor = SystemColors.Highlight;
|
|
_highlightedDragTarget.ForeColor = SystemColors.HighlightText;
|
|
}
|
|
e.Effect = DragDropEffects.Move;
|
|
}
|
|
else
|
|
{
|
|
ClearHighlightedDragTarget();
|
|
e.Effect = DragDropEffects.None;
|
|
}
|
|
}
|
|
|
|
private void treeView_KeyUp(object sender, KeyEventArgs e)
|
|
{
|
|
switch (e.KeyCode)
|
|
{
|
|
case Keys.Apps:
|
|
if (treeView.SelectedNode != null)
|
|
{
|
|
treeView.SelectedNode.EnsureVisible();
|
|
HandleNodeRightClick(new VirtualTreeNodeMouseClickEventArgs(treeView.SelectedNode,
|
|
MouseButtons.Right, 1,
|
|
treeView.SelectedNode.Bounds.X,
|
|
treeView.SelectedNode.Bounds.Y + treeView.SelectedNode.Bounds.Height));
|
|
}
|
|
break;
|
|
|
|
case Keys.F2:
|
|
var cmd = new PropertiesCommand(Program.MainWindow, Program.MainWindow.SelectionManager.Selection);
|
|
if (cmd.CanExecute())
|
|
cmd.Execute();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void treeView_NodeMouseClick(object sender, VirtualTreeNodeMouseClickEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (treeView.Nodes.Count > 0)
|
|
HandleNodeRightClick(e);
|
|
|
|
if (TreeNodeClicked != null)
|
|
TreeNodeClicked();
|
|
}
|
|
catch (Exception exn)
|
|
{
|
|
log.Error(exn, exn);
|
|
// Swallow this exception -- there's no point throwing it on.
|
|
#if DEBUG
|
|
throw;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
private void treeView_NodeMouseDoubleClick(object sender, VirtualTreeNodeMouseClickEventArgs e)
|
|
{
|
|
if (e.Button != MouseButtons.Left)
|
|
return;
|
|
if (e.Node == null)
|
|
return;
|
|
|
|
IXenConnection conn = e.Node.Tag as IXenConnection;
|
|
if (conn == null)
|
|
{
|
|
var obj = e.Node.Tag as IXenObject;
|
|
if (obj != null)
|
|
conn = obj.Connection;
|
|
}
|
|
if (conn != null && !conn.IsConnected)
|
|
{
|
|
new ReconnectHostCommand(Program.MainWindow, conn).Execute();
|
|
return;
|
|
}
|
|
|
|
VM vm = e.Node.Tag as VM;
|
|
if (vm != null)
|
|
{
|
|
Command cmd = null;
|
|
|
|
if (vm.is_a_template)
|
|
{
|
|
cmd = new NewVMCommand(Program.MainWindow, Program.MainWindow.SelectionManager.Selection);
|
|
}
|
|
else if (vm.power_state == vm_power_state.Halted && vm.allowed_operations.Contains(vm_operations.start))
|
|
{
|
|
cmd = new StartVMCommand(Program.MainWindow, Program.MainWindow.SelectionManager.Selection);
|
|
}
|
|
else if (vm.power_state == vm_power_state.Suspended && vm.allowed_operations.Contains(vm_operations.resume))
|
|
{
|
|
cmd = new ResumeVMCommand(Program.MainWindow, Program.MainWindow.SelectionManager.Selection);
|
|
}
|
|
|
|
if (cmd != null && cmd.CanExecute())
|
|
{
|
|
treeView.SelectedNode = e.Node;
|
|
cmd.Execute();
|
|
}
|
|
return;
|
|
}
|
|
|
|
Host host = e.Node.Tag as Host;
|
|
if (host != null)
|
|
{
|
|
Command cmd = new PowerOnHostCommand(Program.MainWindow, host);
|
|
if (cmd.CanExecute())
|
|
{
|
|
treeView.SelectedNode = e.Node;
|
|
cmd.Execute();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void treeView_SelectionsChanged(object sender, EventArgs e)
|
|
{
|
|
// this is fired when the selection of the main treeview changes.
|
|
VirtualTreeNode rootNode = treeView.Nodes.Count > 0 ? treeView.Nodes[0] : null;
|
|
List<SelectedItem> items = new List<SelectedItem>();
|
|
foreach (VirtualTreeNode node in treeView.SelectedNodes)
|
|
{
|
|
GroupingTag groupingTag = node.Tag as GroupingTag;
|
|
IXenObject xenObject = node.Tag as IXenObject;
|
|
|
|
if (xenObject != null)
|
|
{
|
|
items.Add(new SelectedItem(xenObject, xenObject.Connection,
|
|
HostAncestorOfNode(node), PoolAncestorOfNode(node),
|
|
GroupAncestorOfNode(node), rootNode));
|
|
}
|
|
else
|
|
{
|
|
items.Add(new SelectedItem(groupingTag, rootNode));
|
|
}
|
|
}
|
|
|
|
selectionManager.SetSelection(items);
|
|
|
|
if (TreeViewSelectionChanged != null)
|
|
TreeViewSelectionChanged();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|