mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-05 01:21:07 +01:00
7122615836
Handling OnMouseMove which fires more repeatedly than OnMouseEnter and OnMouseLeave with an early return to preserve CPU efficiency. Signed-off-by: Aaron Robson <aaron.robson@citrix.com>
286 lines
12 KiB
C#
286 lines
12 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.Drawing.Drawing2D;
|
|
using System.Windows.Forms;
|
|
|
|
namespace XenAdmin.Controls
|
|
{
|
|
public class DataGridViewDropDownSplitButtonCell : DataGridViewTextBoxCell
|
|
{
|
|
private bool active;
|
|
private bool disposed;
|
|
private ToolStripItem _defaultItem;
|
|
|
|
/// <summary>
|
|
/// Excluding right padding of cell
|
|
/// </summary>
|
|
private const int SPLITTER_FROM_RIGHT = 20;
|
|
|
|
private static readonly Padding CELL_PADDING = new Padding(0, 0, 1, 1);
|
|
/// <summary>
|
|
/// Cell height without padding
|
|
/// </summary>
|
|
private const int CELL_HEIGHT = 20;
|
|
|
|
/// <summary>
|
|
/// Should be set on the row where the cell will be added
|
|
/// </summary>
|
|
public const int MIN_ROW_HEIGHT = 22;
|
|
|
|
private static readonly Color BUTTON_BORDER_OUTER = Color.FromArgb(112, 112, 112);
|
|
private static readonly Color BUTTON_BORDER_INNER = Color.FromArgb(252, 252, 252);
|
|
private static readonly Color BUTTON_FACE_TOP = Color.FromArgb(242, 242, 242);
|
|
private static readonly Color BUTTON_FACE_MIDDLE_TOP = Color.FromArgb(235, 235, 235);
|
|
private static readonly Color BUTTON_FACE_MIDDLE_BOTTOM = Color.FromArgb(221, 221, 221);
|
|
private static readonly Color BUTTON_FACE_BOTTOM = Color.FromArgb(207, 207, 207);
|
|
|
|
public DataGridViewDropDownSplitButtonCell()
|
|
{
|
|
Style.Padding = CELL_PADDING;
|
|
Style.Alignment = DataGridViewContentAlignment.TopLeft;
|
|
ContextMenu = new ContextMenuStrip();
|
|
ContextMenu.ItemClicked += _contextMenu_ItemClicked;
|
|
}
|
|
|
|
public DataGridViewDropDownSplitButtonCell(params ToolStripItem[] dropDownItems)
|
|
: this()
|
|
{
|
|
RefreshItems(dropDownItems);
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposed)
|
|
return;
|
|
|
|
if (disposing)
|
|
{
|
|
ContextMenu.Dispose();
|
|
disposed = true;
|
|
}
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
public readonly ContextMenuStrip ContextMenu;
|
|
|
|
public ToolStripItem DefaultItem
|
|
{
|
|
get { return _defaultItem; }
|
|
set
|
|
{
|
|
_defaultItem = value;
|
|
if (_defaultItem != null)
|
|
Value = _defaultItem.Text;
|
|
}
|
|
}
|
|
|
|
public void RefreshItems(params ToolStripItem[] dropDownItems)
|
|
{
|
|
ContextMenu.Items.Clear();
|
|
ContextMenu.Items.AddRange(dropDownItems);
|
|
|
|
foreach (ToolStripItem item in ContextMenu.Items)
|
|
{
|
|
if (item is ToolStripMenuItem)
|
|
{
|
|
DefaultItem = item;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void _contextMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
|
|
{
|
|
DefaultItem = e.ClickedItem;
|
|
}
|
|
|
|
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
|
|
{
|
|
cellStyle.Alignment = DataGridViewContentAlignment.TopLeft;
|
|
|
|
base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);
|
|
|
|
if (active)
|
|
{
|
|
var mode = graphics.SmoothingMode;
|
|
graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
|
|
|
var rec = new Rectangle(cellBounds.Left + cellStyle.Padding.Left,
|
|
cellBounds.Top + cellStyle.Padding.Top,
|
|
cellBounds.Width - cellStyle.Padding.Left - cellStyle.Padding.Right,
|
|
CELL_HEIGHT);
|
|
|
|
var innerRec = new Rectangle(rec.Location, rec.Size);
|
|
innerRec.Inflate(-1, -1);
|
|
|
|
using (GraphicsPath path = new GraphicsPath())
|
|
{
|
|
int diameter = 4;
|
|
var arc = new Rectangle(rec.Location, new Size(diameter, diameter));
|
|
|
|
//top left corner
|
|
path.AddArc(arc, 180, 90);
|
|
|
|
// top right corner
|
|
arc.X = rec.Right - diameter;
|
|
path.AddArc(arc, 270, 90);
|
|
|
|
// bottom right corner
|
|
arc.Y = rec.Bottom - diameter;
|
|
path.AddArc(arc, 0, 90);
|
|
|
|
// bottom left corner
|
|
arc.X = rec.Left;
|
|
path.AddArc(arc, 90, 90);
|
|
|
|
path.CloseFigure();
|
|
|
|
using (var brush = new LinearGradientBrush(rec, BUTTON_FACE_TOP, BUTTON_FACE_BOTTOM, LinearGradientMode.Vertical))
|
|
{
|
|
ColorBlend cb = new ColorBlend();
|
|
cb.Positions = new[] { 0, 1 / 2f, 1 / 2f, 1 };
|
|
cb.Colors = new[] { BUTTON_FACE_TOP, BUTTON_FACE_MIDDLE_TOP, BUTTON_FACE_MIDDLE_BOTTOM, BUTTON_FACE_BOTTOM };
|
|
brush.InterpolationColors = cb;
|
|
graphics.FillPath(brush, path);
|
|
}
|
|
|
|
using (Pen pen = new Pen(BUTTON_BORDER_OUTER, 1))
|
|
graphics.DrawPath(pen, path);
|
|
}
|
|
|
|
using (GraphicsPath path = new GraphicsPath())
|
|
{
|
|
int diameter = 4;
|
|
var arc = new Rectangle(innerRec.Location, new Size(diameter, diameter));
|
|
|
|
//top left corner
|
|
path.AddArc(arc, 180, 90);
|
|
|
|
// top right corner
|
|
arc.X = innerRec.Right - diameter;
|
|
path.AddArc(arc, 270, 90);
|
|
|
|
// bottom right corner
|
|
arc.Y = innerRec.Bottom - diameter;
|
|
path.AddArc(arc, 0, 90);
|
|
|
|
// bottom left corner
|
|
arc.X = innerRec.Left;
|
|
path.AddArc(arc, 90, 90);
|
|
|
|
path.CloseFigure();
|
|
|
|
using (Pen pen = new Pen(BUTTON_BORDER_INNER, 1))
|
|
graphics.DrawPath(pen, path);
|
|
}
|
|
|
|
using (var font = new Font(DataGridView.DefaultCellStyle.Font, FontStyle.Regular))
|
|
graphics.DrawString(value as string, font, SystemBrushes.ControlText, cellBounds);
|
|
|
|
using (Pen pen = new Pen(BUTTON_BORDER_OUTER, 1))
|
|
graphics.DrawLine(pen,
|
|
cellBounds.Right - cellStyle.Padding.Right - SPLITTER_FROM_RIGHT,
|
|
cellBounds.Top + cellStyle.Padding.Top + 2,
|
|
cellBounds.Right - cellStyle.Padding.Right - SPLITTER_FROM_RIGHT,
|
|
cellBounds.Top + cellStyle.Padding.Top + CELL_HEIGHT - 2);
|
|
|
|
//reset graphics mode
|
|
graphics.SmoothingMode = mode;
|
|
}
|
|
|
|
var img = Properties.Resources.expanded_triangle;
|
|
graphics.DrawImage(img,
|
|
cellBounds.Right - cellStyle.Padding.Right - img.Width - (SPLITTER_FROM_RIGHT - img.Width) / 2,
|
|
cellBounds.Top + (CELL_HEIGHT - img.Height) / 2);
|
|
}
|
|
|
|
protected override void OnMouseEnter(int rowIndex)
|
|
{
|
|
active = true;
|
|
DataGridView.InvalidateCell(this);
|
|
}
|
|
|
|
protected override void OnMouseLeave(int rowIndex)
|
|
{
|
|
active = false;
|
|
DataGridView.InvalidateCell(this);
|
|
}
|
|
|
|
protected override void OnMouseMove(DataGridViewCellMouseEventArgs e)
|
|
{
|
|
if (active || e.Button != MouseButtons.None)
|
|
return;
|
|
|
|
active = true;
|
|
DataGridView.InvalidateCell(this);
|
|
}
|
|
|
|
protected override void OnMouseDown(DataGridViewCellMouseEventArgs e)
|
|
{
|
|
var cell = DataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex];
|
|
var rec = DataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, true);
|
|
|
|
var buttonArea = new Rectangle(cell.ContentBounds.Left,
|
|
cell.ContentBounds.Top,
|
|
cell.Size.Width - SPLITTER_FROM_RIGHT - cell.Style.Padding.Left - cell.Style.Padding.Right,
|
|
CELL_HEIGHT);
|
|
|
|
var arrowArea = new Rectangle(cell.ContentBounds.Left + cell.Size.Width - SPLITTER_FROM_RIGHT - cell.Style.Padding.Left - cell.Style.Padding.Right,
|
|
cell.ContentBounds.Top,
|
|
SPLITTER_FROM_RIGHT,
|
|
CELL_HEIGHT);
|
|
|
|
//for half displayed cell we want the dropdown to appear at the end
|
|
//of the visible area rather than the hidden of the cell
|
|
var showPoint = new Point(rec.Left, rec.Top + Math.Min(rec.Height, CELL_HEIGHT));
|
|
|
|
if (buttonArea.Contains(e.Location))
|
|
{
|
|
if (DefaultItem != null)
|
|
DefaultItem.PerformClick();
|
|
else if (ContextMenu.Items.Count > 0)
|
|
ContextMenu.Show(DataGridView, showPoint, ToolStripDropDownDirection.Default);
|
|
}
|
|
else if (arrowArea.Contains(e.Location) && ContextMenu.Items.Count > 0)
|
|
ContextMenu.Show(DataGridView, showPoint, ToolStripDropDownDirection.Default);
|
|
}
|
|
|
|
protected override Size GetPreferredSize(Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex, Size constraintSize)
|
|
{
|
|
var orig = base.GetPreferredSize(graphics, cellStyle, rowIndex, constraintSize);
|
|
return new Size(orig.Width + SPLITTER_FROM_RIGHT, orig.Height);
|
|
}
|
|
}
|
|
}
|