mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-23 00:46:03 +01:00
7c0bc50b4a
Inc. Signed-off-by: Gabor Apati-Nagy<gabor.apati-nagy@citrix.com>
850 lines
29 KiB
C#
850 lines
29 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.CodeAnalysis;
|
|
using System.Drawing;
|
|
using System.Drawing.Drawing2D;
|
|
using System.Security.Permissions;
|
|
using System.Windows.Forms;
|
|
using XenAdmin.Controls;
|
|
using XenAdmin.Core;
|
|
|
|
using Message = System.Windows.Forms.Message;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace XenAdmin.Controls
|
|
{
|
|
public partial class SnapshotTreeView : ListView
|
|
{
|
|
private const int straightLineLength = 8;
|
|
|
|
private SnapshotIcon root;
|
|
|
|
|
|
//We need this fake thing to make the scrollbars work with the customdrawdate.
|
|
private ListViewItem whiteIcon = new ListViewItem();
|
|
|
|
|
|
private Color linkLineColor = SystemColors.ControlDark;
|
|
private float linkLineWidth = 2.0f;
|
|
|
|
private int hGap = 50;
|
|
private int vGap = 20;
|
|
private readonly CustomLineCap linkLineArrow = new AdjustableArrowCap(4f, 4f, true);
|
|
|
|
|
|
#region Properties
|
|
|
|
[Browsable(true), Category("Appearance"), Description("Color used to draw connecting lines")]
|
|
[DefaultValue(typeof(Color), "ControlDark")]
|
|
public Color LinkLineColor
|
|
{
|
|
get { return linkLineColor; }
|
|
set
|
|
{
|
|
linkLineColor = value;
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
[Browsable(true), Category("Appearance"), Description("Width of connecting lines")]
|
|
[DefaultValue(2.0f)]
|
|
public float LinkLineWidth
|
|
{
|
|
get { return linkLineWidth; }
|
|
set
|
|
{
|
|
linkLineWidth = value;
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
[Browsable(true), Category("Appearance"), Description("Horizontal gap between icons")]
|
|
[DefaultValue(50)]
|
|
public int HGap
|
|
{
|
|
get { return hGap; }
|
|
set
|
|
{
|
|
if (value < 4 * straightLineLength)
|
|
value = 4 * straightLineLength;
|
|
hGap = value;
|
|
PerformLayout(this, "HGap");
|
|
}
|
|
}
|
|
|
|
[Browsable(true), Category("Appearance"), Description("Vertical gap between icons")]
|
|
[DefaultValue(20)]
|
|
public int VGap
|
|
{
|
|
get { return vGap; }
|
|
set
|
|
{
|
|
if (value < 0)
|
|
value = 0;
|
|
vGap = value;
|
|
PerformLayout(this, "VGap");
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
private ImageList imageList = new ImageList();
|
|
|
|
public SnapshotTreeView(IContainer container)
|
|
{
|
|
if (container == null)
|
|
throw new ArgumentNullException("container");
|
|
container.Add(this);
|
|
|
|
InitializeComponent();
|
|
DoubleBuffered = true;
|
|
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
|
|
base.Items.Add(whiteIcon);
|
|
|
|
//Init image list
|
|
imageList.ColorDepth = ColorDepth.Depth32Bit;
|
|
imageList.ImageSize=new Size(32,32);
|
|
imageList.Images.Add(Properties.Resources._000_HighLightVM_h32bit_32);
|
|
imageList.Images.Add(Properties.Resources.VMTemplate_h32bit_32);
|
|
imageList.Images.Add(Properties.Resources.VMTemplate_h32bit_32);
|
|
imageList.Images.Add(Properties.Resources._000_VMSnapShotDiskOnly_h32bit_32);
|
|
imageList.Images.Add(Properties.Resources._000_VMSnapshotDiskMemory_h32bit_32);
|
|
imageList.Images.Add(Properties.Resources._000_ScheduledVMsnapshotDiskOnly_h32bit_32);
|
|
imageList.Images.Add(Properties.Resources._000_ScheduledVMSnapshotDiskMemory_h32bit_32);
|
|
imageList.Images.Add(Properties.Resources.SpinningFrame0);
|
|
imageList.Images.Add(Properties.Resources.SpinningFrame1);
|
|
imageList.Images.Add(Properties.Resources.SpinningFrame2);
|
|
imageList.Images.Add(Properties.Resources.SpinningFrame3);
|
|
imageList.Images.Add(Properties.Resources.SpinningFrame4);
|
|
imageList.Images.Add(Properties.Resources.SpinningFrame5);
|
|
imageList.Images.Add(Properties.Resources.SpinningFrame6);
|
|
imageList.Images.Add(Properties.Resources.SpinningFrame7);
|
|
|
|
this.LargeImageList = imageList;
|
|
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters",
|
|
MessageId = "System.InvalidOperationException.#ctor(System.String)",
|
|
Justification = "Indicates a programming error - not user facing")]
|
|
internal ListViewItem AddSnapshot(SnapshotIcon snapshot)
|
|
{
|
|
if (snapshot == null)
|
|
throw new ArgumentNullException("snapshot");
|
|
if (snapshot.Parent != null)
|
|
{
|
|
snapshot.Parent.AddChild(snapshot);
|
|
snapshot.Parent.Invalidate();
|
|
}
|
|
else if (root != null)
|
|
{
|
|
throw new InvalidOperationException("Adding a new root!");
|
|
}
|
|
else
|
|
{
|
|
root = snapshot;
|
|
}
|
|
if (snapshot.ImageIndex == SnapshotIcon.VMImageIndex)
|
|
{
|
|
//Sort all the parents of the VM to make the path to the VM the first one.
|
|
SnapshotIcon current = snapshot;
|
|
while (current.Parent != null)
|
|
{
|
|
if (current.Parent.Children.Count > 1)
|
|
{
|
|
int indexCurrent = current.Parent.Children.IndexOf(current);
|
|
SnapshotIcon temp = current.Parent.Children[0];
|
|
current.Parent.Children[0] = current;
|
|
current.Parent.Children[indexCurrent] = temp;
|
|
|
|
}
|
|
current.IsInVMBranch = true;
|
|
current = current.Parent;
|
|
}
|
|
|
|
}
|
|
ListViewItem item = Items.Add(snapshot);
|
|
if (Items.Count == 1)
|
|
Items.Add(whiteIcon);
|
|
return item;
|
|
}
|
|
|
|
|
|
|
|
public new void Clear()
|
|
{
|
|
base.Clear();
|
|
RemoveSnapshot(root);
|
|
}
|
|
|
|
|
|
|
|
internal void RemoveSnapshot(SnapshotIcon snapshot)
|
|
{
|
|
if (snapshot != null && snapshot.Parent != null)
|
|
{
|
|
IList<SnapshotIcon> siblings = snapshot.Parent.Children;
|
|
int pos = siblings.IndexOf(snapshot);
|
|
siblings.Remove(snapshot);
|
|
// add our children in our place
|
|
foreach (SnapshotIcon child in snapshot.Children)
|
|
{
|
|
siblings.Insert(pos++, child);
|
|
child.Parent = snapshot.Parent;
|
|
}
|
|
snapshot.Parent.Invalidate();
|
|
snapshot.Parent = null;
|
|
}
|
|
else
|
|
{
|
|
root = null;
|
|
}
|
|
|
|
}
|
|
|
|
protected override void OnSelectedIndexChanged(EventArgs e)
|
|
{
|
|
if (SelectedItems.Count == 1)
|
|
{
|
|
SnapshotIcon item = SelectedItems[0] as SnapshotIcon;
|
|
if (item != null && !item.Selectable)
|
|
{
|
|
item.Selected = false;
|
|
item.Focused = false;
|
|
}
|
|
|
|
|
|
}
|
|
else if (this.SelectedItems.Count > 1)
|
|
{
|
|
foreach (ListViewItem item in SelectedItems)
|
|
{
|
|
SnapshotIcon snapItem = item as SnapshotIcon;
|
|
if (snapItem != null && !snapItem.Selectable)
|
|
{
|
|
item.Selected = false;
|
|
item.Focused = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
base.OnSelectedIndexChanged(e);
|
|
}
|
|
|
|
protected override void OnMouseUp(MouseEventArgs e)
|
|
{
|
|
ListViewItem item = this.HitTest(e.X, e.Y).Item;
|
|
if (item == null)
|
|
{
|
|
ListViewItem item2 = this.HitTest(e.X, e.Y - 23).Item;
|
|
if (item2 != null)
|
|
{
|
|
item2.Selected = true;
|
|
item2.Focused = true;
|
|
}
|
|
else
|
|
{
|
|
base.OnMouseUp(new MouseEventArgs(e.Button, e.Clicks, e.X, e.Y - 23, e.Delta));
|
|
return;
|
|
}
|
|
}
|
|
|
|
base.OnMouseUp(e);
|
|
|
|
}
|
|
|
|
#region Layout
|
|
|
|
protected override void OnLayout(LayoutEventArgs levent)
|
|
{
|
|
|
|
if (root != null && this.Parent != null)
|
|
{
|
|
//This is needed to maximize and minimize properly, there is some issue in the ListView Control
|
|
Win32.POINT pt = new Win32.POINT();
|
|
IntPtr hResult = SendMessage(Handle, LVM_GETORIGIN, IntPtr.Zero, ref pt);
|
|
|
|
origin = pt;
|
|
root.InvalidateAll();
|
|
int x = Math.Max(this.HGap, this.Size.Width / 2 - root.SubtreeWidth / 2);
|
|
int y = Math.Max(this.VGap, this.Size.Height / 2 - root.SubtreeHeight / 2);
|
|
PositionSnapshots(root, x, y);
|
|
Invalidate();
|
|
|
|
whiteIcon.Position = new Point(x + origin.X, y + root.SubtreeHeight - 20 + origin.Y);
|
|
}
|
|
}
|
|
|
|
protected override void OnParentChanged(EventArgs e)
|
|
{
|
|
//We need to cancel the parent changes when we change to the other view
|
|
}
|
|
|
|
private void PositionSnapshots(SnapshotIcon icon, int x, int y)
|
|
{
|
|
try
|
|
{
|
|
Size iconSize = icon.DefaultSize;
|
|
|
|
|
|
Point newPoint = new Point(x, y + icon.CentreHeight - iconSize.Height / 2);
|
|
icon.Position = new Point(newPoint.X + origin.X, newPoint.Y + origin.Y);
|
|
x += iconSize.Width + HGap;
|
|
for (int i = 0; i < icon.Children.Count; i++)
|
|
{
|
|
SnapshotIcon child = icon.Children[i];
|
|
PositionSnapshots(child, x, y);
|
|
y += child.SubtreeHeight;
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Debugger.Break();
|
|
}
|
|
}
|
|
public const int LVM_GETORIGIN = 0x1000 + 41;
|
|
|
|
private Win32.POINT origin = new Win32.POINT();
|
|
|
|
[DllImport("user32.dll")]
|
|
internal static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, ref Win32.POINT pt);
|
|
|
|
|
|
|
|
public override Size GetPreferredSize(Size proposedSize)
|
|
{
|
|
if (root == null && Parent != null)
|
|
{
|
|
return DefaultSize;
|
|
}
|
|
return new Size(root.SubtreeWidth, root.SubtreeHeight);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Drawing
|
|
|
|
private bool m_empty = false;
|
|
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
|
|
protected override void WndProc(ref Message m)
|
|
{
|
|
const int WM_HSCROLL = 0x0114;
|
|
const int WM_VSCROLL = 0x0115;
|
|
const int WM_MOUSEWHEEL = 0x020A;
|
|
const int WM_PAINT = 0x000F;
|
|
|
|
base.WndProc(ref m);
|
|
|
|
if (m.Msg == WM_HSCROLL || m.Msg == WM_VSCROLL || (m.Msg == WM_MOUSEWHEEL && (IsVerticalScrollBarVisible(this) || IsHorizontalScrollBarVisible(this))))
|
|
{
|
|
Invalidate();
|
|
}
|
|
if (m.Msg == WM_PAINT)
|
|
{
|
|
Graphics gg = CreateGraphics();
|
|
|
|
if (this.Items.Count == 0)
|
|
{
|
|
m_empty = true;
|
|
Graphics g = CreateGraphics();
|
|
string text = Messages.SNAPSHOTS_EMPTY;
|
|
SizeF proposedSize = g.MeasureString(text, Font, 275);
|
|
float x = this.Width / 2 - proposedSize.Width / 2;
|
|
float y = this.Height / 2 - proposedSize.Height / 2;
|
|
RectangleF rect = new RectangleF(x, y, proposedSize.Width, proposedSize.Height);
|
|
using (var brush = new SolidBrush(BackColor))
|
|
g.FillRectangle(brush, rect);
|
|
g.DrawString(text, Font, Brushes.Black, rect);
|
|
}
|
|
}
|
|
else if (m_empty && this.Items.Count > 0)
|
|
{
|
|
m_empty = false;
|
|
this.Invalidate();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
private const int WS_HSCROLL = 0x100000;
|
|
private const int WS_VSCROLL = 0x200000;
|
|
private const int GWL_STYLE = (-16);
|
|
|
|
[System.Runtime.InteropServices.DllImport("user32",
|
|
CharSet = System.Runtime.InteropServices.CharSet.Auto)]
|
|
private static extern int GetWindowLong(IntPtr hwnd, int nIndex);
|
|
|
|
internal static bool IsVerticalScrollBarVisible(Control ctrl)
|
|
{
|
|
if (!ctrl.IsHandleCreated)
|
|
return false;
|
|
|
|
return (GetWindowLong(ctrl.Handle, GWL_STYLE) & WS_VSCROLL) != 0;
|
|
}
|
|
|
|
internal static bool IsHorizontalScrollBarVisible(Control ctrl)
|
|
{
|
|
if (!ctrl.IsHandleCreated)
|
|
return false;
|
|
|
|
return (GetWindowLong(ctrl.Handle, GWL_STYLE) & WS_HSCROLL) != 0;
|
|
}
|
|
|
|
|
|
private void SnapshotTreeView_DrawItem(object sender, DrawListViewItemEventArgs e)
|
|
{
|
|
|
|
if (this.Parent != null)
|
|
{
|
|
e.DrawDefault = true;
|
|
SnapshotIcon icon = e.Item as SnapshotIcon;
|
|
if (icon == null)
|
|
return;
|
|
|
|
DrawDate(e, icon, false);
|
|
|
|
if (icon.Parent != null)
|
|
PaintLine(e.Graphics, icon.Parent, icon, icon.IsInVMBranch);
|
|
for (int i = 0; i < icon.Children.Count; i++)
|
|
{
|
|
SnapshotIcon child = icon.Children[i];
|
|
PaintLine(e.Graphics, icon, child, child.IsInVMBranch);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
public void DrawRoundRect(Graphics g, Brush b, float x, float y, float width, float height, float radius)
|
|
{
|
|
|
|
GraphicsPath gp = new GraphicsPath();
|
|
|
|
gp.AddLine(x + radius, y, x + width - (radius * 2), y); // Line
|
|
|
|
gp.AddArc(x + width - (radius * 2), y, radius * 2, radius * 2, 270, 90); // Corner
|
|
|
|
gp.AddLine(x + width, y + radius, x + width, y + height - (radius * 2)); // Line
|
|
|
|
gp.AddArc(x + width - (radius * 2), y + height - (radius * 2), radius * 2, radius * 2, 0, 90); // Corner
|
|
gp.AddLine(x + width - (radius * 2), y + height, x + radius, y + height); // Line
|
|
|
|
gp.AddArc(x, y + height - (radius * 2), radius * 2, radius * 2, 90, 90); // Corner
|
|
|
|
gp.AddLine(x, y + height - (radius * 2), x, y + radius); // Line
|
|
|
|
gp.AddArc(x, y, radius * 2, radius * 2, 180, 90); // Corner
|
|
|
|
gp.CloseFigure();
|
|
|
|
|
|
|
|
g.FillPath(b, gp);
|
|
|
|
}
|
|
|
|
private void DrawDate(DrawListViewItemEventArgs e, SnapshotIcon icon, bool background)
|
|
{
|
|
|
|
StringFormat stringFormat = new StringFormat();
|
|
stringFormat.Alignment = StringAlignment.Center;
|
|
|
|
//Time
|
|
int timeX = e.Bounds.X;
|
|
int timeY = e.Bounds.Y + e.Bounds.Height;
|
|
Size proposedSizeName = new Size(e.Bounds.Width,
|
|
Int32.MaxValue);
|
|
Size timeSize = TextRenderer.MeasureText(e.Graphics, icon.LabelCreationTime,
|
|
new Font(this.Font.FontFamily, this.Font.Size - 1), proposedSizeName, TextFormatFlags.WordBreak);
|
|
timeSize = new Size(e.Bounds.Width, timeSize.Height);
|
|
Rectangle timeRect = new Rectangle(new Point(timeX, timeY), timeSize);
|
|
if (background)
|
|
{
|
|
e.Graphics.FillRectangle(Brushes.GreenYellow, timeRect);
|
|
}
|
|
e.Graphics.DrawString(icon.LabelCreationTime, new Font(this.Font.FontFamily, this.Font.Size - 1), Brushes.Black, timeRect, stringFormat);
|
|
|
|
}
|
|
|
|
private void PaintLine(Graphics g, SnapshotIcon icon, SnapshotIcon child, bool highlight)
|
|
{
|
|
if (child.Index == -1)
|
|
return;
|
|
|
|
try
|
|
{
|
|
Rectangle leftItemBounds = icon.GetBounds(ItemBoundsPortion.Entire);
|
|
Rectangle rightItemBounds = child.GetBounds(ItemBoundsPortion.Entire);
|
|
leftItemBounds.Size = icon.DefaultSize;
|
|
rightItemBounds.Size = child.DefaultSize;
|
|
|
|
|
|
int left = leftItemBounds.Right + 6;
|
|
int right = rightItemBounds.Left;
|
|
int mid = (left + right) / 2;
|
|
Point start = new Point(left, (leftItemBounds.Bottom + leftItemBounds.Top) / 2);
|
|
Point end = new Point(right, (rightItemBounds.Top + rightItemBounds.Bottom) / 2);
|
|
Point curveStart = start;
|
|
curveStart.Offset(straightLineLength, 0);
|
|
Point curveEnd = end;
|
|
curveEnd.Offset(-straightLineLength, 0);
|
|
Point control1 = new Point(mid + straightLineLength, start.Y);
|
|
Point control2 = new Point(mid - straightLineLength, end.Y);
|
|
|
|
Color lineColor = LinkLineColor;
|
|
float lineWidth = LinkLineWidth;
|
|
if (highlight)
|
|
{
|
|
lineColor = Color.ForestGreen;
|
|
lineWidth = 2.5f;
|
|
}
|
|
using (Pen p = new Pen(lineColor, lineWidth))
|
|
{
|
|
p.SetLineCap(LineCap.Round, LineCap.Custom, DashCap.Flat);
|
|
p.CustomEndCap = linkLineArrow;
|
|
|
|
g.SmoothingMode = SmoothingMode.AntiAlias;
|
|
GraphicsPath path = new GraphicsPath();
|
|
path.AddLine(start, curveStart);
|
|
path.AddBezier(curveStart, control1, control2, curveEnd);
|
|
path.AddLine(curveEnd, end);
|
|
g.DrawPath(p, path);
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
//Debugger.Break();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
private string _spinningMessage = "";
|
|
public string SpinningMessage { get { return _spinningMessage; } }
|
|
|
|
internal void ChangeVMToSpinning(bool p, string message)
|
|
{
|
|
_spinningMessage = message;
|
|
foreach (var item in Items)
|
|
{
|
|
SnapshotIcon snapshotIcon = item as SnapshotIcon;
|
|
|
|
if (snapshotIcon != null && (snapshotIcon.ImageIndex == SnapshotIcon.VMImageIndex || snapshotIcon.ImageIndex > SnapshotIcon.UnknownImage))
|
|
{
|
|
if (string.IsNullOrEmpty(message))
|
|
{
|
|
|
|
snapshotIcon.ChangeSpinningIcon(p, _spinningMessage);
|
|
}
|
|
else
|
|
{
|
|
snapshotIcon.ChangeSpinningIcon(p, _spinningMessage);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
internal class SnapshotIcon : ListViewItem
|
|
{
|
|
public const int VMImageIndex = 0;
|
|
public const int Template = 1;
|
|
public const int CustomTemplate = 2;
|
|
public const int DiskSnapshot = 3;
|
|
public const int DiskAndMemorySnapshot = 4;
|
|
public const int ScheduledDiskSnapshot = 5;
|
|
public const int ScheduledDiskMemorySnapshot = 6;
|
|
public const int UnknownImage = 6;
|
|
|
|
private SnapshotIcon parent;
|
|
private readonly SnapshotTreeView treeView;
|
|
private readonly List<SnapshotIcon> children = new List<SnapshotIcon>();
|
|
private readonly string _name;
|
|
private readonly string _creationTime;
|
|
|
|
public Size DefaultSize = new Size(70, 64);
|
|
|
|
private Timer spinningTimer = new Timer();
|
|
|
|
|
|
|
|
#region Cached dimensions
|
|
|
|
private int subtreeWidth;
|
|
private int subtreeHeight;
|
|
private int subtreeWeight;
|
|
private int centreHeight;
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
public string LabelCreationTime
|
|
{
|
|
get { return _creationTime; }
|
|
}
|
|
|
|
public string LabelName
|
|
{
|
|
get { return _name; }
|
|
}
|
|
|
|
|
|
|
|
internal SnapshotIcon Parent
|
|
{
|
|
get { return parent; }
|
|
set { parent = value; }
|
|
}
|
|
|
|
internal IList<SnapshotIcon> Children
|
|
{
|
|
get { return children; }
|
|
}
|
|
|
|
internal bool Selectable
|
|
{
|
|
get
|
|
{
|
|
// It's selectable if it's a snapshot; otherwise not
|
|
return ImageIndex == DiskSnapshot || ImageIndex == DiskAndMemorySnapshot || ImageIndex == ScheduledDiskSnapshot || ImageIndex == ScheduledDiskMemorySnapshot;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
public SnapshotIcon(string name, string createTime, SnapshotIcon parent, SnapshotTreeView treeView, int imageIndex)
|
|
: base(name.Ellipsise(35))
|
|
{
|
|
this._name = name.Ellipsise(35);
|
|
this._creationTime = createTime;
|
|
this.parent = parent;
|
|
this.treeView = treeView;
|
|
this.UseItemStyleForSubItems = false;
|
|
this.ToolTipText = String.Format("{0} {1}", name, createTime);
|
|
this.ImageIndex = imageIndex;
|
|
if (imageIndex == SnapshotIcon.VMImageIndex)
|
|
{
|
|
spinningTimer.Tick += new EventHandler(timer_Tick);
|
|
spinningTimer.Interval = 150;
|
|
}
|
|
|
|
}
|
|
|
|
private int currentSpinningFrame = 7;
|
|
private void timer_Tick(object sender, EventArgs e)
|
|
{
|
|
this.ImageIndex = currentSpinningFrame <= 14 ? currentSpinningFrame++ : currentSpinningFrame = 7;
|
|
}
|
|
|
|
internal void ChangeSpinningIcon(bool enabled, string message)
|
|
{
|
|
if (this.ImageIndex > UnknownImage || this.ImageIndex == VMImageIndex)
|
|
{
|
|
this.ImageIndex = enabled ? 7 : VMImageIndex;
|
|
this.Text = enabled ? message : Messages.NOW;
|
|
|
|
if (enabled)
|
|
spinningTimer.Start();
|
|
else
|
|
spinningTimer.Stop();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private bool _isInVMBranch = false;
|
|
public bool IsInVMBranch
|
|
{
|
|
get { return _isInVMBranch; }
|
|
set { _isInVMBranch = value; }
|
|
}
|
|
|
|
internal void AddChild(SnapshotIcon icon)
|
|
{
|
|
children.Add(icon);
|
|
Invalidate();
|
|
}
|
|
|
|
public override void Remove()
|
|
{
|
|
SnapshotTreeView view = (SnapshotTreeView)ListView;
|
|
view.RemoveSnapshot(this);
|
|
base.Remove();
|
|
view.PerformLayout();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Causes the item and its ancestors to forget their cached dimensions.
|
|
/// </summary>
|
|
public void Invalidate()
|
|
{
|
|
subtreeWeight = 0;
|
|
subtreeHeight = 0;
|
|
centreHeight = 0;
|
|
subtreeWidth = 0;
|
|
if (parent != null)
|
|
parent.Invalidate();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Causes the item and all its descendents to forget their cached dimensions.
|
|
/// </summary>
|
|
public void InvalidateAll()
|
|
{
|
|
subtreeWeight = 0;
|
|
subtreeHeight = 0;
|
|
centreHeight = 0;
|
|
subtreeWidth = 0;
|
|
foreach (SnapshotIcon icon in children)
|
|
{
|
|
icon.InvalidateAll();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region Layout/Dimensions
|
|
|
|
public int SubtreeWidth
|
|
{
|
|
get
|
|
{
|
|
if (subtreeWidth == 0)
|
|
{
|
|
int currentWidth = this.DefaultSize.Width + treeView.HGap;
|
|
if (children.Count > 0)
|
|
{
|
|
int maxWidth = 0;
|
|
foreach (SnapshotIcon icon in children)
|
|
{
|
|
int childSubtree = icon.SubtreeWidth;
|
|
if (currentWidth + childSubtree > maxWidth)
|
|
maxWidth = currentWidth + childSubtree;
|
|
}
|
|
if (maxWidth > currentWidth)
|
|
subtreeWidth = maxWidth;
|
|
else
|
|
subtreeWidth = currentWidth;
|
|
}
|
|
}
|
|
return subtreeWidth;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Height of the subtree rooted at this node, including the margin above and below.
|
|
/// </summary>
|
|
public int SubtreeHeight
|
|
{
|
|
get
|
|
{
|
|
if (subtreeHeight == 0)
|
|
{
|
|
subtreeHeight = this.DefaultSize.Height + treeView.VGap;
|
|
if (children.Count > 0)
|
|
{
|
|
int height = 0;
|
|
foreach (SnapshotIcon icon in children)
|
|
{
|
|
height += icon.SubtreeHeight; // recurse
|
|
}
|
|
if (height > subtreeHeight)
|
|
subtreeHeight = height;
|
|
}
|
|
}
|
|
return subtreeHeight;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The number of items rooted at this node (including the node itself)
|
|
/// </summary>
|
|
public int SubtreeWeight
|
|
{
|
|
get
|
|
{
|
|
if (subtreeWeight == 0)
|
|
{
|
|
int weight = 1; // this
|
|
foreach (SnapshotIcon icon in children)
|
|
{
|
|
weight += icon.SubtreeWeight;
|
|
}
|
|
subtreeWeight = weight;
|
|
}
|
|
return subtreeWeight;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The weighted mean centre height for this node, within the range 0 - SubtreeHeight
|
|
/// </summary>
|
|
public int CentreHeight
|
|
{
|
|
get
|
|
{
|
|
if (centreHeight == 0)
|
|
{
|
|
int top = 0;
|
|
int totalWeight = 0;
|
|
int weightedCentre = 0;
|
|
foreach (SnapshotIcon icon in children)
|
|
{
|
|
int iconWeight = icon.SubtreeWeight;
|
|
totalWeight += iconWeight;
|
|
weightedCentre += iconWeight * (top + icon.CentreHeight); // recurse
|
|
top += icon.SubtreeHeight;
|
|
}
|
|
if (totalWeight > 0)
|
|
centreHeight = weightedCentre / totalWeight;
|
|
else
|
|
centreHeight = (top + SubtreeHeight) / 2;
|
|
}
|
|
return centreHeight;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |