mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-11-24 22:06:59 +01:00
d7b519a53c
Signed-off-by: Konstantina Chremmou <Konstantina.Chremmou@cloud.com>
932 lines
33 KiB
C#
932 lines
33 KiB
C#
/* Copyright (c) Cloud Software Group, Inc.
|
|
*
|
|
* 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.Windows.Forms;
|
|
|
|
namespace XenAdmin.Controls.CustomGridView
|
|
{
|
|
public enum MouseButtonAction
|
|
{
|
|
SingleClick, DoubleClick, MouseDown, StartDrag, SingleRightClick
|
|
}
|
|
|
|
public partial class GridView : Panel
|
|
{
|
|
public List<GridRow> Rows = new List<GridRow>();
|
|
// If currently painting the refresh call will set the PaintingRequired flag which will cause the panel to repaint after
|
|
public bool Painting = false;
|
|
public bool PaintingRequired = false;
|
|
|
|
// This draws to the control
|
|
private Graphics Graphics;
|
|
// This draws to the BackBuffer
|
|
private Graphics BufferGraphics;
|
|
// Stores the image while being drawn
|
|
private Bitmap BackBuffer;
|
|
|
|
/// <summary>
|
|
/// The width of the column containing the expander triangle, if required.
|
|
/// </summary>
|
|
private bool hasLeftExpanders = true;
|
|
public bool HasLeftExpanders
|
|
{
|
|
get { return hasLeftExpanders; }
|
|
set { hasLeftExpanders = value; }
|
|
}
|
|
private const int LEFT_OFFSET = 20;
|
|
public int LeftOffset
|
|
{
|
|
get
|
|
{
|
|
return HasLeftExpanders ? LEFT_OFFSET : 0;
|
|
}
|
|
}
|
|
|
|
// Row selection
|
|
protected string lastClickedRowPath = null;
|
|
public GridRow LastClickedRow
|
|
{
|
|
set
|
|
{
|
|
lastClickedRowPath = (value == null ? null : value.Path);
|
|
}
|
|
}
|
|
Point lastClickedPoint = new Point(0, 0);
|
|
|
|
// Iterate over all rows including child rows, in display order
|
|
public IEnumerable<GridRow> RowsAndChildren
|
|
{
|
|
get
|
|
{
|
|
foreach (GridRow row in Rows)
|
|
{
|
|
yield return row;
|
|
foreach (GridRow child in row.RowsAndChildren)
|
|
yield return child;
|
|
}
|
|
}
|
|
}
|
|
|
|
public GridView()
|
|
{
|
|
InitBuffer();
|
|
InitializeComponent();
|
|
}
|
|
|
|
// The top row in the list, NEVER set this! <- no need to dispose explicitly as will be disposed when the contents of Rows is disposed
|
|
public GridHeaderRow HeaderRow;
|
|
|
|
private bool hold_cursor_change = false;
|
|
private Cursor new_cursor = null;
|
|
public override Cursor Cursor
|
|
{
|
|
set
|
|
{
|
|
if (hold_cursor_change)
|
|
new_cursor = value;
|
|
else
|
|
base.Cursor = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the size of the back buffer to the size of the control
|
|
/// </summary>
|
|
private void InitBuffer()
|
|
{
|
|
Graphics = this.CreateGraphics();
|
|
|
|
Rectangle disp = RealRectangle;
|
|
BackBuffer = new Bitmap(disp.Width > 0 ? disp.Width : 1, disp.Height > 0 ? disp.Height : 1);
|
|
BufferGraphics = Graphics.FromImage(BackBuffer);
|
|
BufferGraphics.TextRenderingHint = Graphics.TextRenderingHint;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update all values in the grid
|
|
/// </summary>
|
|
public override void Refresh()
|
|
{
|
|
//if (Painting)
|
|
//{
|
|
// we are currently painting, set a flag and return
|
|
// the painting will occur when the current paint has finished
|
|
// the idea is that if we get loads of calls to this then we only repaint at most once after each paint
|
|
// this is why i dont think we should have a lock as they would wait then paint all afterwards
|
|
// PaintingRequired = true;
|
|
//}
|
|
//else
|
|
//{
|
|
// we are painting
|
|
//Painting = true;
|
|
Draw(RealRectangle);
|
|
Painting = false; // slight chance of a race occuring but if we end up painting 3 times instead of just 2 it wont be the world
|
|
// if (PaintingRequired)
|
|
// {
|
|
// // we need to paint again
|
|
// PaintingRequired = false;
|
|
// Refresh();
|
|
// }
|
|
//}
|
|
}
|
|
|
|
protected override void OnInvalidated(InvalidateEventArgs e)
|
|
{
|
|
//OnPaint(null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the currently displayed rectangle of the control (ie includes how far we have scrolled)
|
|
/// </summary>
|
|
protected Rectangle RealRectangle
|
|
{
|
|
get
|
|
{
|
|
Rectangle r = ClientRectangle;
|
|
r.Offset(-AutoScrollPosition.X, -AutoScrollPosition.Y);
|
|
return r;
|
|
}
|
|
}
|
|
|
|
// refresh on scroll
|
|
protected override void OnScroll(ScrollEventArgs se)
|
|
{
|
|
Refresh();
|
|
}
|
|
|
|
protected override void OnResize(EventArgs eventargs)
|
|
{
|
|
// ignore this size changed
|
|
}
|
|
|
|
protected override void OnClientSizeChanged(EventArgs e)
|
|
{
|
|
// ignore this size changed
|
|
}
|
|
|
|
protected override void OnSizeChanged(EventArgs e)
|
|
{
|
|
// use this one instead
|
|
base.OnResize(e); // do size change <= calling on resize here may seem barking but it appears to be the only combination that works...
|
|
DisposeBuffer(); // reinitalise buffer
|
|
InitBuffer();
|
|
Refresh(); //refresh
|
|
}
|
|
|
|
// dispose
|
|
private void DisposeBuffer()
|
|
{
|
|
BackBuffer.Dispose();
|
|
BufferGraphics.Dispose();
|
|
Graphics.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Render the contents of the rectangle to the buffer
|
|
/// </summary>
|
|
/// <param name="rectangle"></param>
|
|
protected void Draw(Rectangle rectangle)
|
|
{
|
|
RenderToBuffer(new PaintEventArgs(BufferGraphics, rectangle));
|
|
OnPaint(null);
|
|
}
|
|
|
|
private void RenderToBuffer(PaintEventArgs paintEventArgs)
|
|
{
|
|
if (Disposing || IsDisposed || Program.Exiting)
|
|
return;
|
|
// make rectangle a little bigger just in case
|
|
Rectangle invalid = paintEventArgs.ClipRectangle;
|
|
invalid.Offset(AutoScrollPosition);
|
|
|
|
// paint background
|
|
base.OnPaintBackground(new PaintEventArgs(paintEventArgs.Graphics, invalid));
|
|
|
|
// paint rows
|
|
int height = Padding.Top + AutoScrollPosition.Y;
|
|
int width = TotalWidth();
|
|
int left = Padding.Left + AutoScrollPosition.X;
|
|
|
|
// draw header
|
|
|
|
if (HeaderRow != null)
|
|
{
|
|
int headerrowheight = HeaderRow.RowAndChildrenHeight;
|
|
if (height >= invalid.Top - headerrowheight && height <= invalid.Bottom + headerrowheight)
|
|
HeaderRow.OnPaint(new RowPaintArgs(paintEventArgs.Graphics, invalid, new Rectangle(left, height, width, headerrowheight)));
|
|
height += headerrowheight + HeaderRow.SpaceAfter;
|
|
}
|
|
|
|
int numRows = Rows.Count;
|
|
for (int i = 0; i < numRows; ++i)
|
|
{
|
|
GridRow row = Rows[i];
|
|
int rowheight = row.RowAndChildrenHeight;
|
|
if (height >= invalid.Top - rowheight && height <= invalid.Bottom + rowheight)
|
|
row.OnPaint(new RowPaintArgs(paintEventArgs.Graphics, invalid, new Rectangle(left, height, width, rowheight)));
|
|
height += rowheight;
|
|
if (i < numRows - 1)
|
|
height += row.SpaceAfter;
|
|
}
|
|
height += Padding.Bottom;
|
|
|
|
// set the clientrecangle to update the autoscrollbar
|
|
AutoScrollMinSize = new Size(GetMinColumn(), height - AutoScrollPosition.Y);
|
|
//base.OnPaint(paintEventArgs);
|
|
}
|
|
|
|
private int TotalWidth()
|
|
{
|
|
int allowedwidth = ClientSize.Width - Padding.Horizontal;
|
|
if (HeaderRow == null)
|
|
return allowedwidth;
|
|
int actualwidth = GetMinColumn() - Padding.Horizontal;
|
|
return actualwidth > allowedwidth ? actualwidth : allowedwidth;
|
|
}
|
|
|
|
private int GetMinColumn()
|
|
{
|
|
if (HeaderRow == null)
|
|
return 0;
|
|
int total = Padding.Horizontal + LeftOffset;
|
|
foreach (string col in HeaderRow.Columns)
|
|
{
|
|
total += HeaderRow.GetColumnWidth(col);
|
|
}
|
|
return total;
|
|
}
|
|
|
|
protected override void OnPaint(PaintEventArgs e)
|
|
{
|
|
if (Disposing || IsDisposed || Program.Exiting)
|
|
return;
|
|
|
|
if (!DraggingColumns)
|
|
{
|
|
// we dont need to render again apparently :)
|
|
Core.Drawing.QuickDraw(Graphics, BackBuffer);
|
|
}
|
|
else
|
|
{
|
|
//paint control
|
|
IntPtr pTarget = DragColumnsGraphics.GetHdc();
|
|
IntPtr pSource = Core.Drawing.CreateCompatibleDC(pTarget);
|
|
IntPtr pOrig = Core.Drawing.SelectObject(pSource, BackBuffer.GetHbitmap());
|
|
Core.Drawing.BitBlt(pTarget, 0, 0, BackBuffer.Width, BackBuffer.Height, pSource, 0, 0, Core.Drawing.TernaryRasterOperations.SRCCOPY);
|
|
IntPtr pNew = Core.Drawing.SelectObject(pSource, pOrig);
|
|
Core.Drawing.DeleteObject(pNew);
|
|
Core.Drawing.DeleteDC(pSource);
|
|
//DragGraphics.ReleaseHdc(pTarget);
|
|
|
|
// paint dragged stuff
|
|
//IntPtr pTarget2 = DragGraphics.GetHdc();
|
|
IntPtr pSource2 = Core.Drawing.CreateCompatibleDC(pTarget);
|
|
IntPtr pOrig2 = Core.Drawing.SelectObject(pSource2, DragColumnsData.GetHbitmap());
|
|
Core.Drawing.BitBlt(pTarget, DragColumnsWindowPosition.X, DragColumnsWindowPosition.Y, DragColumnsData.Width, DragColumnsData.Height, pSource2, 0, 0, Core.Drawing.TernaryRasterOperations.SRCAND);
|
|
IntPtr pNew2 = Core.Drawing.SelectObject(pSource2, pOrig2);
|
|
Core.Drawing.DeleteObject(pNew2);
|
|
Core.Drawing.DeleteDC(pSource2);
|
|
DragColumnsGraphics.ReleaseHdc(pTarget);
|
|
|
|
// paint everything to screen
|
|
IntPtr pTarget3 = Graphics.GetHdc();
|
|
IntPtr pSource3 = Core.Drawing.CreateCompatibleDC(pTarget3);
|
|
IntPtr pOrig3 = Core.Drawing.SelectObject(pSource3, DragColumnsBuffer.GetHbitmap());
|
|
Core.Drawing.BitBlt(pTarget3, 0, 0, DragColumnsBuffer.Width, DragColumnsBuffer.Height, pSource3, 0, 0, Core.Drawing.TernaryRasterOperations.SRCCOPY);
|
|
IntPtr pNew3 = Core.Drawing.SelectObject(pSource3, pOrig3);
|
|
Core.Drawing.DeleteObject(pNew3);
|
|
Core.Drawing.DeleteDC(pSource3);
|
|
Graphics.ReleaseHdc(pTarget3);
|
|
}
|
|
}
|
|
|
|
protected override void OnPaintBackground(PaintEventArgs e)
|
|
{
|
|
//base.OnPaintBackground(e);
|
|
}
|
|
|
|
private bool DraggingItems;
|
|
private bool DraggingColumns = false;
|
|
private Bitmap DragColumnsData;
|
|
private Point DragColumnsWindowPosition;
|
|
private Point DragColumnsMouseLocation;
|
|
private string ActiveColumn = "";
|
|
private Bitmap DragColumnsBuffer;
|
|
Graphics DragColumnsGraphics;
|
|
string DragColumnsLastEntered = "";
|
|
GridRow LastRow;
|
|
bool ResizingColumns = false;
|
|
Point ResizeColumnsInitialMouseLocation;
|
|
|
|
protected override void OnMouseUp(MouseEventArgs e)
|
|
{
|
|
if (e.Button == MouseButtons.Left && DraggingColumns)
|
|
{
|
|
string swapwith = "";
|
|
int left = Padding.Left;
|
|
foreach (string index in HeaderRow.Columns)
|
|
{
|
|
int width = GetColumnWidth(index, 1);
|
|
if (e.X - AutoScrollPosition.X < left + (width / 2) && !((GridHeaderItem)HeaderRow.Items[index]).Immovable)
|
|
{
|
|
swapwith = index;
|
|
break;
|
|
}
|
|
left += width;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(swapwith))
|
|
swapwith = "end";
|
|
|
|
DraggingColumns = false;
|
|
|
|
if (!HeaderRow.Items.ContainsKey(ActiveColumn))
|
|
return;
|
|
|
|
((GridHeaderItem)HeaderRow.Items[ActiveColumn]).GreyOut = false;
|
|
if (DragColumnsData != null)
|
|
{
|
|
DragColumnsData.Dispose();
|
|
DragColumnsData = null;
|
|
}
|
|
if (DragColumnsBuffer != null)
|
|
{
|
|
DragColumnsBuffer.Dispose();
|
|
DragColumnsBuffer = null;
|
|
}
|
|
if (DragColumnsGraphics != null)
|
|
{
|
|
DragColumnsGraphics.Dispose();
|
|
DragColumnsGraphics = null;
|
|
}
|
|
if (swapwith == "end")
|
|
{
|
|
HeaderRow.Columns.Remove(ActiveColumn);
|
|
HeaderRow.Columns.Add(ActiveColumn);
|
|
}
|
|
else
|
|
{
|
|
int movedn = HeaderRow.Columns.IndexOf(ActiveColumn);
|
|
int replacedn = HeaderRow.Columns.IndexOf(swapwith);
|
|
if (movedn > replacedn)
|
|
{
|
|
HeaderRow.Columns.Remove(ActiveColumn);
|
|
HeaderRow.Columns.Insert(replacedn, ActiveColumn);
|
|
}
|
|
else if (movedn < replacedn)
|
|
{
|
|
HeaderRow.Columns.Insert(replacedn, ActiveColumn);
|
|
HeaderRow.Columns.Remove(ActiveColumn);
|
|
}
|
|
}
|
|
DragColumnsLastEntered = "";
|
|
Refresh();
|
|
}
|
|
else if (e.Button == MouseButtons.Left && ResizingColumns)
|
|
{
|
|
ResizingColumns = false;
|
|
}
|
|
ActiveColumn = "";
|
|
}
|
|
|
|
public void AddRow(GridRow row)
|
|
{
|
|
Rows.Add(row);
|
|
row.GridView = this;
|
|
}
|
|
|
|
public void SetHeaderRow(GridHeaderRow hr)
|
|
{
|
|
hr.GridView = this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the width of the specified columns (ie index to index + span)
|
|
/// </summary>
|
|
internal int GetColumnWidth(string col, int span)
|
|
{
|
|
if (HeaderRow == null) return 0;
|
|
int total = 0;
|
|
int index = HeaderRow.Columns.IndexOf(col);
|
|
for (int i = index; i < index + span; i++)
|
|
{
|
|
if (HeaderRow.Columns.Count <= i)
|
|
break;
|
|
total += HeaderRow.GetColumnWidth(HeaderRow.Columns[i]);
|
|
}
|
|
return total;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scroll up or down the list only if it has been focused
|
|
/// </summary>
|
|
protected override void OnMouseWheel(MouseEventArgs e)
|
|
{
|
|
AutoScrollPosition = new Point(-AutoScrollPosition.X, -AutoScrollPosition.Y - e.Delta);
|
|
OnScroll(new ScrollEventArgs(ScrollEventType.EndScroll, 0));
|
|
}
|
|
|
|
protected virtual void OnMouseButtonAction(MouseEventArgs e, MouseButtonAction type)
|
|
{
|
|
if (HeaderRow == null)
|
|
return;
|
|
|
|
if (!DraggingColumns && !ResizingColumns)
|
|
{
|
|
this.Focus();
|
|
Point p;
|
|
GridRow row = FindRowFromPoint(e.Location, out p);
|
|
if (row == null)
|
|
return;
|
|
|
|
if (e.Button == MouseButtons.Right && row is GridHeaderRow)
|
|
{
|
|
OpenChooseColumnsMenu(e.Location);
|
|
}
|
|
else if (e.Button == MouseButtons.Left)
|
|
{
|
|
if (Cursor == Cursors.SizeWE)
|
|
{
|
|
if (type == MouseButtonAction.DoubleClick)
|
|
FitColumnWidthToHeaderAndContents(row, p);
|
|
}
|
|
else
|
|
{
|
|
row.OnMouseButtonAction(p, type);
|
|
}
|
|
}
|
|
else if (type == MouseButtonAction.SingleRightClick)
|
|
{
|
|
row.OnMouseButtonAction(p, type);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void FitColumnWidthToHeaderAndContents(GridRow currentRow, Point p)
|
|
{
|
|
GridHeaderItem headerItem = (GridHeaderItem)currentRow.FindItemFromPoint(new Point(p.X - GridHeaderItem.ResizeGutter, p.Y), out ResizeColumnsInitialMouseLocation);
|
|
ResizeColumnsInitialMouseLocation.X += GridHeaderItem.ResizeGutter;
|
|
ActiveColumn = headerItem.ColumnName;
|
|
|
|
if (HeaderRow.Items.ContainsKey(ActiveColumn))
|
|
{
|
|
int maxWidth = headerItem.MinimumWidth;
|
|
|
|
foreach (GridRow row in RowsAndChildren)
|
|
{
|
|
GridItemBase _item = row.GetItem(ActiveColumn);
|
|
int itemWidth = _item.GetGridItemWidth(ActiveColumn);
|
|
|
|
if (itemWidth > maxWidth)
|
|
maxWidth = itemWidth;
|
|
}
|
|
|
|
headerItem.Width = maxWidth;
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
protected override void OnMouseClick(MouseEventArgs e)
|
|
{
|
|
OnMouseButtonAction(e, e.Button == MouseButtons.Left ? MouseButtonAction.SingleClick : MouseButtonAction.SingleRightClick);
|
|
}
|
|
|
|
protected override void OnMouseDoubleClick(MouseEventArgs e)
|
|
{
|
|
OnMouseButtonAction(e, MouseButtonAction.DoubleClick);
|
|
}
|
|
|
|
protected override void OnMouseDown(MouseEventArgs e)
|
|
{
|
|
lastClickedPoint = e.Location;
|
|
OnMouseButtonAction(e, MouseButtonAction.MouseDown);
|
|
}
|
|
|
|
public virtual void OpenChooseColumnsMenu(Point p)
|
|
{
|
|
|
|
}
|
|
|
|
protected override void OnMouseMove(MouseEventArgs e)
|
|
{
|
|
HoldCursorChange(delegate() { OnMouseMove_(e); });
|
|
}
|
|
|
|
private void HoldCursorChange(MethodInvoker handler)
|
|
{
|
|
hold_cursor_change = true;
|
|
try
|
|
{
|
|
handler();
|
|
}
|
|
finally
|
|
{
|
|
hold_cursor_change = false;
|
|
if (new_cursor != null)
|
|
{
|
|
Cursor = new_cursor;
|
|
new_cursor = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnMouseMove_(MouseEventArgs e)
|
|
{
|
|
if (Disposing || IsDisposed || Program.Exiting)
|
|
return;
|
|
|
|
if (HeaderRow == null)
|
|
return;
|
|
|
|
if (DraggingItems)
|
|
return;
|
|
|
|
if (DraggingColumns)
|
|
{
|
|
int x = e.Location.X - DragColumnsMouseLocation.X;
|
|
int xmax = TotalWidth() - DragColumnsData.Width;
|
|
DragColumnsWindowPosition = new Point(x <= 0 ? 0 : x < xmax ? x : xmax, 0);
|
|
string swapwith = "";
|
|
int left = Padding.Left;
|
|
foreach (string index in HeaderRow.Columns)
|
|
{
|
|
int width = GetColumnWidth(index, 1);
|
|
if (((GridHeaderItem)HeaderRow.Items[index]).Immovable)
|
|
{
|
|
left += width;
|
|
continue;
|
|
}
|
|
if (e.X - AutoScrollPosition.X < left + (width / 2))
|
|
{
|
|
swapwith = index;
|
|
break;
|
|
}
|
|
left += width;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(swapwith))
|
|
swapwith = "end";
|
|
|
|
if (swapwith != DragColumnsLastEntered)
|
|
{
|
|
if (swapwith == "end")
|
|
{
|
|
HeaderRow.Columns.Remove(ActiveColumn);
|
|
HeaderRow.Columns.Add(ActiveColumn);
|
|
}
|
|
else
|
|
{
|
|
int movedn = HeaderRow.Columns.IndexOf(ActiveColumn);
|
|
int replacedn = HeaderRow.Columns.IndexOf(swapwith);
|
|
if (movedn > replacedn)
|
|
{
|
|
HeaderRow.Columns.Remove(ActiveColumn);
|
|
HeaderRow.Columns.Insert(replacedn, ActiveColumn);
|
|
}
|
|
else if (movedn < replacedn)
|
|
{
|
|
HeaderRow.Columns.Insert(replacedn, ActiveColumn);
|
|
HeaderRow.Columns.Remove(ActiveColumn);
|
|
}
|
|
}
|
|
DragColumnsLastEntered = swapwith;
|
|
Refresh();
|
|
}
|
|
else
|
|
{
|
|
OnPaint(null);
|
|
}
|
|
}
|
|
else if (ResizingColumns)
|
|
{
|
|
if (HeaderRow.Items.ContainsKey(ActiveColumn))
|
|
{
|
|
GridHeaderItem item = (HeaderRow.Items[ActiveColumn] as GridHeaderItem);
|
|
int newWidth = item.Width + (e.Location.X - ResizeColumnsInitialMouseLocation.X);
|
|
if (newWidth > item.MinimumWidth)
|
|
{
|
|
item.Width = newWidth;
|
|
ResizeColumnsInitialMouseLocation = e.Location;
|
|
}
|
|
else
|
|
{
|
|
item.Width = item.MinimumWidth;
|
|
}
|
|
Refresh();
|
|
}
|
|
}
|
|
else if (e.Button == MouseButtons.Left && string.IsNullOrEmpty(ActiveColumn) &&
|
|
DragDistance(lastClickedPoint, e.Location) >= 2) // don't start dragging unles we've moved a little distance
|
|
// TODO: consider replacing the above with SystemParameters.MinimumHorizontalDragDistance etc. when we move to .NET 3.0
|
|
{
|
|
Point p;
|
|
GridRow row = FindRowFromPoint(lastClickedPoint, out p); // the drag is then based on the mouse-down location
|
|
if (row is GridHeaderRow)
|
|
{
|
|
GridHeaderItem item = (GridHeaderItem)row.FindItemFromPoint(new Point(p.X - GridHeaderItem.ResizeGutter, p.Y), out DragColumnsMouseLocation);
|
|
DragColumnsMouseLocation.X += GridHeaderItem.ResizeGutter;
|
|
if (item == null)
|
|
return;
|
|
|
|
ActiveColumn = item.ColumnName;
|
|
int colwidth = GetColumnWidth(ActiveColumn, 1);
|
|
|
|
if (!item.UnSizable && DragColumnsMouseLocation.X >= colwidth - GridHeaderItem.ResizeGutter)
|
|
{
|
|
ResizingColumns = true;
|
|
ResizeColumnsInitialMouseLocation = lastClickedPoint;
|
|
}
|
|
else if (!item.Immovable)
|
|
{
|
|
DraggingColumns = true;
|
|
item.GreyOut = true;
|
|
DragColumnsData = new Bitmap(GetColumnWidth(ActiveColumn, 1), ClientSize.Height);
|
|
|
|
Graphics ddGraphics = Graphics.FromImage(DragColumnsData);
|
|
ddGraphics.Clear(BackColor);
|
|
XenAdmin.Core.Drawing.QuickDraw(ddGraphics, BackBuffer, new Point((lastClickedPoint.X - DragColumnsMouseLocation.X), 0), new Rectangle(0, 0, DragColumnsData.Width, DragColumnsData.Height));
|
|
ddGraphics.Dispose();
|
|
|
|
DragColumnsBuffer = new Bitmap(ClientSize.Width, ClientSize.Height);
|
|
DragColumnsGraphics = Graphics.FromImage(DragColumnsBuffer);
|
|
}
|
|
}
|
|
else if (row != null)
|
|
row.OnMouseButtonAction(p, MouseButtonAction.StartDrag);
|
|
}
|
|
else
|
|
{
|
|
Point p;
|
|
GridRow row = FindRowFromPoint(e.Location, out p);
|
|
if (LastRow == null && row == null)
|
|
return;
|
|
|
|
if (LastRow == row)
|
|
{
|
|
LastRow.OnMouseMove(p);
|
|
}
|
|
else
|
|
{
|
|
if (LastRow != null)
|
|
LastRow.OnLeave();
|
|
|
|
LastRow = row;
|
|
|
|
if (LastRow != null)
|
|
LastRow.OnEnter(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
// The L_1 distance between two points
|
|
private int DragDistance(Point p1, Point p2)
|
|
{
|
|
int xDisp = p1.X - p2.X;
|
|
int yDisp = p1.Y - p2.Y;
|
|
int totDisp = (xDisp >= 0 ? xDisp : -xDisp) + (yDisp >= 0 ? yDisp : -yDisp);
|
|
return totDisp;
|
|
}
|
|
|
|
protected override void OnMouseLeave(EventArgs e)
|
|
{
|
|
HoldCursorChange(delegate()
|
|
{
|
|
if (LastRow != null)
|
|
LastRow.OnLeave();
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes the point in question (eg location of the mouseclick) and returns the row
|
|
/// that was clicked and the point on that row (ie adjusted so that 0,0 is top left of that row)
|
|
/// </summary>
|
|
private GridRow FindRowFromPoint(Point point, out Point p)
|
|
{
|
|
int height = Padding.Top + AutoScrollPosition.Y;
|
|
|
|
p = Point.Empty;
|
|
|
|
if (HeaderRow != null)
|
|
{
|
|
if (RowContainsPoint(HeaderRow, point, ref height, ref p))
|
|
return HeaderRow;
|
|
}
|
|
|
|
foreach (GridRow row in Rows)
|
|
{
|
|
if (RowContainsPoint(row, point, ref height, ref p))
|
|
return row;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private bool RowContainsPoint(GridRow row, Point point, ref int height, ref Point p)
|
|
{
|
|
int rowheight = row.RowAndChildrenHeight;
|
|
if (point.Y >= height && point.Y < height + rowheight)
|
|
{
|
|
p = new Point(point.X - Padding.Left - AutoScrollPosition.X, point.Y - height);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
height += rowheight + row.SpaceAfter;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private Dictionary<string, RowState> saveState;
|
|
|
|
protected void SaveRowStates()
|
|
{
|
|
saveState = new Dictionary<string, RowState>();
|
|
foreach (GridRow row in RowsAndChildren)
|
|
saveState[row.Path] = row.State;
|
|
}
|
|
|
|
protected void RestoreRowStates()
|
|
{
|
|
RowState state;
|
|
foreach (GridRow row in RowsAndChildren)
|
|
{
|
|
if (saveState.TryGetValue(row.Path, out state))
|
|
row.State = state;
|
|
}
|
|
}
|
|
|
|
internal void UnselectAllRowsExcept(GridRow notThisOne)
|
|
{
|
|
foreach (GridRow row in RowsAndChildren)
|
|
{
|
|
if (row != notThisOne)
|
|
row.Selected = false;
|
|
}
|
|
Refresh();
|
|
}
|
|
|
|
internal void UnselectAllRows()
|
|
{
|
|
UnselectAllRowsExcept(null);
|
|
}
|
|
|
|
internal void SelectRowRange(GridRow newRow)
|
|
{
|
|
UnselectAllRows();
|
|
|
|
// Once through to check we haven't lost either of the endpoints
|
|
// for some reason
|
|
bool foundLast = false;
|
|
bool foundNew = false;
|
|
if (lastClickedRowPath != null)
|
|
{
|
|
foreach (GridRow row in RowsAndChildren)
|
|
{
|
|
if (row.Path == lastClickedRowPath)
|
|
foundLast = true;
|
|
if (row == newRow)
|
|
foundNew = true;
|
|
if (foundLast && foundNew)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If endpoint is missing, turn it into a normal select
|
|
if (!foundLast || !foundNew)
|
|
{
|
|
newRow.Selected = true;
|
|
LastClickedRow = newRow;
|
|
}
|
|
|
|
// Otherwise do the proper range select.
|
|
// Make sure to include both endpoints.
|
|
foundLast = foundNew = false;
|
|
foreach (GridRow row in RowsAndChildren)
|
|
{
|
|
if (row.Path == lastClickedRowPath)
|
|
foundLast = true;
|
|
if (row == newRow)
|
|
foundNew = true;
|
|
if (foundLast || foundNew)
|
|
row.Selected = true;
|
|
if (foundLast && foundNew)
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void StartDragDrop()
|
|
{
|
|
GridRowCollection rows = new GridRowCollection();
|
|
foreach (GridRow row in RowsAndChildren)
|
|
{
|
|
if (row.Selected && IsDraggableRow(row))
|
|
rows.Add(row);
|
|
}
|
|
if (rows.Count > 0)
|
|
DoDragDrop(rows, DragDropEffects.Move);
|
|
}
|
|
|
|
protected virtual bool IsDraggableRow(GridRow row)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Equivalent to Clear(false).
|
|
/// </summary>
|
|
internal void Clear()
|
|
{
|
|
Clear(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all rows from the GridView.
|
|
/// </summary>
|
|
/// <param name="removeHeaders">If true, removes GridHeaderRows too.</param>
|
|
internal void Clear(bool removeHeaders)
|
|
{
|
|
Rows.RemoveAll(new Predicate<GridRow>(delegate(GridRow row)
|
|
{
|
|
return removeHeaders || !(row is GridHeaderRow);
|
|
}));
|
|
}
|
|
|
|
internal virtual void Sort()
|
|
{
|
|
Rows.Sort();
|
|
foreach (GridRow row in Rows)
|
|
{
|
|
row.Sort();
|
|
}
|
|
LastClickedRow = null;
|
|
}
|
|
|
|
public List<KeyValuePair<String, int>> ColumnsAndWidths
|
|
{
|
|
get
|
|
{
|
|
List<KeyValuePair<String, int>> columns = new List<KeyValuePair<String, int>>();
|
|
|
|
if (HeaderRow == null)
|
|
return columns;
|
|
|
|
foreach (String columnName in HeaderRow.Columns)
|
|
{
|
|
if (!HeaderRow.Items.ContainsKey(columnName))
|
|
continue;
|
|
GridHeaderItem item = HeaderRow.Items[columnName] as GridHeaderItem;
|
|
if (item == null)
|
|
continue;
|
|
|
|
columns.Add(new KeyValuePair<String, int>(columnName, item.Width));
|
|
}
|
|
|
|
return columns;
|
|
}
|
|
}
|
|
|
|
protected override void OnDragEnter(DragEventArgs e)
|
|
{
|
|
DraggingItems = true;
|
|
e.Effect = DragDropEffects.None;
|
|
base.OnDragEnter(e);
|
|
}
|
|
|
|
protected override void OnDragLeave(EventArgs e)
|
|
{
|
|
DraggingItems = false;
|
|
base.OnDragLeave(e);
|
|
}
|
|
}
|
|
}
|