xenadmin/XenAdmin/Controls/Ballooning/ShinyBar.cs

351 lines
15 KiB
C#
Raw Normal View History

/* 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.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Text;
using System.Windows.Forms;
using XenAdmin.Core;
namespace XenAdmin.Controls.Ballooning
{
public partial class ShinyBar : UserControl
{
private static readonly Font font = new Font("Segoe UI", 9);
//private static readonly Font font = FormFontFixer.DefaultFont;
private const int radius = 5;
private const int pad = 2;
private const int TEXT_PAD = 3;
private const int TEXT_FADE = 8;
// Because one bar has several regions with different tooltips, we handle the tooltip timing etc. ourselves.
private ToolTip toolTip = new ToolTip();
private Dictionary<Rectangle, string> toolTipRegions = new Dictionary<Rectangle, string>();
private Timer toolTipTimer = new Timer();
private const int TOOLTIP_DELAY = 500;
private Point lastMouseLocation;
private Point toolTipLocation;
private string toolTipText;
private bool ignoreNextOnMouseMove;
public ShinyBar()
{
InitializeComponent();
toolTip.Popup += toolTip_Popup;
toolTipTimer.Interval = TOOLTIP_DELAY;
toolTipTimer.Tick += toolTipTimer_Tick;
}
protected void DrawToTarget(Graphics g, Rectangle barBounds, Rectangle segmentBounds, Color color)
{
DrawToTarget(g, barBounds, segmentBounds, color, "", Color.Empty, HorizontalAlignment.Left, "");
}
protected void DrawToTarget(Graphics g, Rectangle barBounds, Rectangle segmentBounds, Color color, string text, Color textColor, HorizontalAlignment alignment, string toolTipText)
{
// The width shouldn't normally be < 0, but free space can be transiently < 0 while the metrics catch up,
// especially across a reboot of a VM.
if (segmentBounds.Width <= 0)
return;
// We draw the whole bar for each segment, but clip to the region corresponding to that segment (as if we
// were viewing it through a succession of windows). This is the easiest way to make it the right shape.
g.Clip = new Region(segmentBounds);
g.SmoothingMode = SmoothingMode.AntiAlias;
using (GraphicsPath outerPath = GetRoundedPath(barBounds, radius))
{
// Outer rounded rectangle
{
Color topColor = color;
Color bottomColor = ControlPaint.LightLight(color);
using (LinearGradientBrush outerBrush = new LinearGradientBrush(barBounds, topColor, bottomColor, LinearGradientMode.Vertical))
{
g.FillPath(outerBrush, outerPath);
}
}
}
// Render text
// Text is rendered behind the 'reflection'. It is centered within the segment, unless the segment is too narrow to fit all the text,
// in which case the text is rendered TEXT_PAD pixels from the left edge. If the text would extend into the TEXT_PAD pixels from
// the right edge of the segment, then the right TEXT_FADE pixels of the text are faded out.
if (!String.IsNullOrEmpty(text))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
StringFormat sf = new StringFormat();
SizeF textSize = g.MeasureString(text, font);
float horizPos;
switch (alignment)
{
case HorizontalAlignment.Left:
horizPos = segmentBounds.Left + TEXT_PAD;
break;
case HorizontalAlignment.Right:
// NB If the caption is multi-line, it's still left-aligned within its box:
// just the box is right-aligned within the segment.
horizPos = segmentBounds.Right - textSize.Width - TEXT_PAD;
break;
default: // HorizontalAlignment.Center:
horizPos = segmentBounds.Left + (segmentBounds.Width - textSize.Width) / 2;
break;
}
RectangleF textRect = new RectangleF(horizPos,
segmentBounds.Top + (segmentBounds.Height - textSize.Height * 0.9f) / 2,
textSize.Width,
textSize.Height);
if (textRect.X < segmentBounds.X + TEXT_PAD)
{
textRect.Offset((segmentBounds.X + TEXT_PAD) - textRect.X, 0);
}
// Render text to a bitmap...
using (Bitmap bitmap = new Bitmap((int)(segmentBounds.Width + 1), (int)(segmentBounds.Height + 1), PixelFormat.Format32bppArgb))
{
using (Graphics gBitmap = Graphics.FromImage(bitmap))
{
gBitmap.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
gBitmap.Clip = new Region(new Rectangle(TEXT_PAD, 0, bitmap.Width - 2 * TEXT_PAD, bitmap.Height));
textRect.X -= segmentBounds.Left;
textRect.Y -= segmentBounds.Top;
using (SolidBrush textBrush = new SolidBrush(textColor))
gBitmap.DrawString(text, font, textBrush, textRect, sf);
// ...if we can't fit all the text in, overlay fade...
if (textSize.Width + (2 * TEXT_PAD) > bitmap.Width)
{
for (int x = bitmap.Width - TEXT_FADE - TEXT_PAD; x < bitmap.Width - TEXT_PAD; x++)
{
if (x < 0)
{
// For very narrow segments, don't try to paint outside segment
continue;
}
double i = (x - bitmap.Width + TEXT_FADE + TEXT_PAD) / (double)TEXT_FADE;
for (int y = 0; y < bitmap.Height; y++)
{
Color c = bitmap.GetPixel(x, y);
int alpha = (int)(c.A - (i * c.A));
c = Color.FromArgb(alpha, c.R, c.G, c.B);
bitmap.SetPixel(x, y, c);
}
}
}
}
// ...and render bitmap to final image
Point location = new Point((int)segmentBounds.X, (int)segmentBounds.Y);
g.DrawImage(bitmap, location);
}
}
// Inner rounded rectangle
RectangleF rect = new RectangleF(barBounds.X + pad, barBounds.Y + pad, barBounds.Width - (2f * pad), barBounds.Height * 0.49f);
using (GraphicsPath innerPath = GetRoundedPath(rect, radius - pad))
{
int alphaTop = 120, alphaBottom = 30;
Color topColor = Color.FromArgb(alphaTop, Color.White);
Color bottomColor = Color.FromArgb(alphaBottom, Color.White);
rect = new RectangleF(rect.X, rect.Y, rect.Width, rect.Height + pad + 1);
using (LinearGradientBrush lighterBrush = new LinearGradientBrush(rect, topColor, bottomColor, LinearGradientMode.Vertical))
{
g.FillPath(lighterBrush, innerPath);
}
}
// Remember the tooltip text
if (!string.IsNullOrEmpty(toolTipText))
toolTipRegions[segmentBounds] = toolTipText;
// Reset the clip region
g.Clip = new Region();
}
private static GraphicsPath GetRoundedPath(RectangleF rect, float radius)
{
float diameter = 2 * radius;
GraphicsPath path = new GraphicsPath();
path.StartFigure();
path.AddArc(rect.X, rect.Y, diameter, diameter, 180, 90);
path.AddArc(rect.Right - diameter, rect.Y, diameter, diameter, 270, 90);
path.AddArc(rect.Right - diameter, rect.Bottom - diameter - 1, diameter, diameter, 0, 90);
path.AddArc(rect.X, rect.Bottom - diameter - 1, diameter, diameter, 90, 90);
path.CloseFigure();
return path;
}
protected static void DrawGrid(Graphics g, Rectangle barArea, double bytesPerPixel, double max)
{
const int min_gap = 40; // min gap between consecutive labels (which are on alternate ticks)
const int line_height = 12;
int line_bottom = barArea.Top + barArea.Height / 2;
int line_top = barArea.Top - line_height;
int text_bottom = line_top - 2;
// Find the size of the longest label
string label = string.Format(Messages.VAL_MB, Util.ToMB(max, RoundingBehaviour.Nearest));
Size labelSize = Drawing.MeasureText(g, label, Program.DefaultFont, TextFormatFlags.NoPadding);
int longest = labelSize.Width;
int text_top = text_bottom - labelSize.Height;
// Calculate a suitable increment
long incr = Util.BINARY_MEGA / 2;
while((double)incr / bytesPerPixel * 2 < min_gap + longest)
incr *= 2;
// Draw the grid
using (Pen pen = new Pen(BallooningColors.Grid))
{
bool withLabel = true;
for (long x = 0; x <= max; x += incr)
{
// Tick
int pos = barArea.Left + (int)((double)x / bytesPerPixel);
g.DrawLine(pen, pos, line_top, pos, line_bottom);
// Label
if (withLabel)
{
label = Util.MemorySizeStringSuitableUnits(x, false);
Size size = Drawing.MeasureText(g, label, Program.DefaultFont);
Rectangle rect = new Rectangle(new Point(pos - size.Width/2, text_top), size);
if (LabelShouldBeShown(max, label, x))
{
Drawing.DrawText(g, label, Program.DefaultFont, rect, BallooningColors.Grid, Color.Transparent);
}
}
withLabel = !withLabel;
}
}
}
/// <summary>
/// There are 2 cases:
/// 1. If the maximum is smaller or equal to 1 GB, then show all the labels.
/// 2. If the maximum is greater than 1 GB, then show only the labels that are a multiple of half a GB.
/// </summary>
/// <param name="max"></param>
/// <param name="label"></param>
/// <param name="x"></param>
/// <returns></returns>
private static bool LabelShouldBeShown(double max, string label, long x)
{
return max <= Util.BINARY_GIGA || (x % (0.5 * Util.BINARY_GIGA)) == 0;
}
protected virtual Rectangle barRect
{
get
{
return new Rectangle(20, 30, this.Width - 45, barHeight);
}
}
protected virtual int barHeight
{
get { return 20; }
}
public override void Refresh()
{
toolTipRegions = new Dictionary<Rectangle, string>();
base.Refresh();
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (lastMouseLocation != e.Location)
{
// CA-38305: on some PCs OnMouseMove gets repeatedly called while the mouse cursor is still.
// this if statement ensures that the code below is only called while the mouse is actually moving.
if (ignoreNextOnMouseMove)
{
// The reason for this is that when the tooltip pops up, we get an
// additional onMouseMove which would otherwise hide the tooltip again.
ignoreNextOnMouseMove = false;
return;
}
toolTipTimer.Stop();
toolTip.Hide(this);
lastMouseLocation = e.Location;
toolTipTimer.Start();
}
}
protected override void OnMouseLeave(EventArgs e)
{
toolTipTimer.Stop();
toolTip.Hide(this);
}
private void toolTipTimer_Tick(object sender, EventArgs e)
{
foreach (KeyValuePair<Rectangle, string> pair in toolTipRegions)
{
if (pair.Key.Contains(lastMouseLocation))
{
ShowTooltip(pair.Value, lastMouseLocation);
toolTipTimer.Stop();
}
}
}
private void ShowTooltip(string text, Point location)
{
toolTipText = text;
toolTipLocation = location;
toolTip.Show(text, this, location);
}
private void toolTip_Popup(object sender, PopupEventArgs e)
{
Point correctLocation = lastMouseLocation + new Size(1, -e.ToolTipSize.Height); // put it above-right of the mouse
if (toolTipLocation != correctLocation)
{
ShowTooltip(toolTipText, correctLocation);
return;
}
ignoreNextOnMouseMove = true;
}
}
}