BizHawk/BizHawk.Client.EmuHawk/tools/TAStudio/InputRoll.cs

1775 lines
43 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
2014-08-07 18:32:09 +00:00
using BizHawk.Client.EmuHawk.CustomControls;
using System.Collections;
2014-08-07 18:32:09 +00:00
namespace BizHawk.Client.EmuHawk
{
//Row width depends on font size and padding
//Column width is specified in column headers
//Row width is specified for horizontal orientation
public class InputRoll : Control
{
2014-08-09 13:13:24 +00:00
private readonly GDIRenderer Gdi;
private readonly RollColumns _columns = new RollColumns();
private readonly List<Cell> SelectedItems = new List<Cell>();
2014-08-09 13:13:24 +00:00
2014-08-18 21:38:02 +00:00
private readonly VScrollBar VBar;
2014-08-15 00:42:03 +00:00
2014-08-18 21:38:02 +00:00
private readonly HScrollBar HBar;
2014-08-15 00:42:03 +00:00
private bool _horizontalOrientation = false;
private bool _programmaticallyUpdatingScrollBarValues = false;
private int _maxCharactersInHorizontal = 1;
private int _rowCount = 0;
2014-08-09 16:11:25 +00:00
private Size _charSize;
2014-08-09 13:13:24 +00:00
public InputRoll()
{
2014-08-18 21:38:02 +00:00
2014-08-24 14:31:25 +00:00
UseCustomBackground = true;
GridLines = true;
2014-08-07 18:32:09 +00:00
CellPadding = 3;
CurrentCell = null;
Font = new Font("Courier New", 8); // Only support fixed width
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
2014-08-08 13:36:37 +00:00
SetStyle(ControlStyles.Opaque, true);
2014-08-08 13:42:05 +00:00
Gdi = new GDIRenderer();
using (var g = CreateGraphics())
using (var LCK = Gdi.LockGraphics(g))
{
_charSize = Gdi.MeasureString("A", this.Font);//TODO make this a property so changing it updates other values.
}
2014-08-18 21:38:02 +00:00
UpdateCellSize();
ColumnWidth = CellWidth;
ColumnHeight = CellHeight + 2;
//TODO Figure out how to use the width and height properties of the scrollbars instead of 17
VBar = new VScrollBar
{
Location = new Point(Width - 17, 0),
Visible = false,
Anchor = AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom,
SmallChange = CellHeight,
LargeChange = CellHeight * 20
};
HBar = new HScrollBar
{
Location = new Point(0, Height - 17),
Visible = false,
Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
SmallChange = 1,
LargeChange = 20
};
2014-08-18 21:38:02 +00:00
this.Controls.Add(VBar);
this.Controls.Add(HBar);
2014-08-19 00:37:38 +00:00
VBar.ValueChanged += VerticalBar_ValueChanged;
HBar.ValueChanged += HorizontalBar_ValueChanged;
HorizontalOrientation = false;
2014-08-18 21:38:02 +00:00
RecalculateScrollBars();
_columns.ChangedCallback = ColumnChangedCallback;
}
protected override void Dispose(bool disposing)
{
Gdi.Dispose();
base.Dispose(disposing);
}
#region Properties
2014-08-07 18:32:09 +00:00
/// <summary>
/// Gets or sets the amount of padding on the text inside a cell
/// </summary>
[DefaultValue(3)]
[Category("Behavior")]
2014-08-07 18:32:09 +00:00
public int CellPadding { get; set; }
/// <summary>
/// Displays grid lines around cells
/// </summary>
[Category("Appearance")]
[DefaultValue(true)]
public bool GridLines { get; set; }
/// <summary>
/// Gets or sets whether the control is horizontal or vertical
/// </summary>
[Category("Behavior")]
public bool HorizontalOrientation {
get{
return _horizontalOrientation;
}
set
{
if (_horizontalOrientation != value)
{
_horizontalOrientation = value;
OrientationChanged();
}
}
}
/// <summary>
/// Gets or sets the sets the virtual number of rows to be displayed. Does not include the column header row.
/// </summary>
[Category("Behavior")]
public int RowCount
2014-08-18 21:38:02 +00:00
{
get
{
return _rowCount;
2014-08-18 21:38:02 +00:00
}
set
{
_rowCount = value;
2014-08-18 21:38:02 +00:00
RecalculateScrollBars();
}
}
/// <summary>
/// Gets or sets the sets the columns can be resized
/// </summary>
[Category("Behavior")]
public bool AllowColumnResize { get; set; }
/// <summary>
/// Gets or sets the sets the columns can be reordered
/// </summary>
[Category("Behavior")]
public bool AllowColumnReorder { get; set; }
/// <summary>
/// Indicates whether the entire row will always be selected
/// </summary>
[Category("Appearance")]
[DefaultValue(false)]
public bool FullRowSelect { get; set; }
/// <summary>
/// Allows multiple items to be selected
/// </summary>
[Category("Behavior")]
[DefaultValue(true)]
public bool MultiSelect { get; set; }
/// <summary>
/// Gets or sets whether or not the control is in input painting mode
/// </summary>
[Category("Behavior")]
[DefaultValue(false)]
public bool InputPaintingMode { get; set; }
2014-08-23 13:19:48 +00:00
/// <summary>
/// The columns shown
/// </summary>
[Category("Behavior")]
public RollColumns Columns { get { return _columns; } }
public void SelectAll()
{
var oldFullRowVal = FullRowSelect;
FullRowSelect = true;
for (int i = 0; i < RowCount; i++)
{
SelectRow(i, true);
}
FullRowSelect = oldFullRowVal;
}
public void DeselectAll()
{
SelectedItems.Clear();
}
#endregion
#region Event Handlers
/// <summary>
/// Fire the QueryItemText event which requests the text for the passed cell
/// </summary>
[Category("Virtual")]
public event QueryItemTextHandler QueryItemText;
/// <summary>
/// Fire the QueryItemBkColor event which requests the background color for the passed cell
/// </summary>
[Category("Virtual")]
public event QueryItemBkColorHandler QueryItemBkColor;
/// <summary>
/// Fire the QueryItemIconHandler event which requests an icon for a given cell
/// </summary>
[Category("Virtual")]
public event QueryItemIconHandler QueryItemIcon;
/// <summary>
/// Fires when the mouse moves from one cell to another (including column header cells)
/// </summary>
[Category("Mouse")]
public event CellChangeEventHandler PointedCellChanged;
/// <summary>
/// Occurs when a column header is clicked
/// </summary>
[Category("Action")]
2014-08-23 14:36:55 +00:00
public event System.Windows.Forms.ColumnClickEventHandler ColumnClick;
/// <summary>
/// Occurs whenever the 'SelectedItems' property for this control changes
/// </summary>
[Category("Behavior")]
2014-08-23 14:36:55 +00:00
public event System.EventHandler SelectedIndexChanged;
/// <summary>
/// Occurs whenever the mouse wheel is scrolled while the right mouse button is held
/// </summary>
[Category("Behavior")]
public event RightMouseScrollEventHandler RightMouseScrolled;
/// <summary>
/// Retrieve the text for a cell
/// </summary>
public delegate void QueryItemTextHandler(int index, int column, out string text);
/// <summary>
/// Retrieve the background color for a cell
/// </summary>
public delegate void QueryItemBkColorHandler(int index, int column, ref Color color);
/// <summary>
/// Retrive the image for a given cell
/// </summary>
public delegate void QueryItemIconHandler(int index, int column, ref Bitmap icon);
public delegate void CellChangeEventHandler(object sender, CellEventArgs e);
public delegate void RightMouseScrollEventHandler(object sender, MouseEventArgs e);
public class CellEventArgs
{
public CellEventArgs(Cell oldCell, Cell newCell)
{
OldCell = oldCell;
NewCell = newCell;
}
public Cell OldCell { get; private set; }
public Cell NewCell { get; private set; }
}
#endregion
#region Api
public void SelectRow(int index, bool val)
{
if (_columns.Any())
{
if (val)
{
SelectCell(new Cell
{
RowIndex = index,
Column = _columns[0]
});
}
else
{
var items = SelectedItems.Where(cell => cell.RowIndex == index);
SelectedItems.RemoveAll(x => items.Contains(x));
}
}
}
2014-08-23 16:00:56 +00:00
[Browsable(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public int? FirstSelectedIndex
{
get
{
if (SelectedRows.Any())
2014-08-23 16:00:56 +00:00
{
return SelectedRows
2014-08-23 16:00:56 +00:00
.OrderBy(x => x)
.First();
}
return null;
}
}
2014-08-23 13:50:47 +00:00
[Browsable(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public int? LastSelectedIndex
{
get
{
if (SelectedRows.Any())
2014-08-23 13:50:47 +00:00
{
return SelectedRows
2014-08-23 13:50:47 +00:00
.OrderBy(x => x)
.Last();
}
return null;
}
}
/// <summary>
/// The current Cell that the mouse was in.
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public Cell CurrentCell { get; set; }
/// <summary>
/// The previous Cell that the mouse was in.
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public Cell LastCell { get; set; }
[Browsable(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public bool IsPaintDown { get; set; }
[Browsable(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public bool UseCustomBackground { get; set; }
[Browsable(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public int DrawHeight{ get; private set; }
[Browsable(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public int DrawWidth { get; private set; }
/// <summary>
/// Sets the width of data cells when in Horizontal orientation.
/// </summary>
/// <param name="maxLength">The maximum number of characters the column will support in Horizontal orientation.</param>
public int MaxCharactersInHorizontal{
get
{
return _maxCharactersInHorizontal;
}
set
{
_maxCharactersInHorizontal = value;
UpdateCellSize();
}
}
[Browsable(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public bool RightButtonHeld { get; set; }
public string UserSettingsSerialized()
{
return string.Empty; // TODO
}
2014-08-23 13:50:47 +00:00
/// <summary>
/// Gets or sets the first visible row index, if scrolling is needed
2014-08-23 13:50:47 +00:00
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public int FirstVisibleRow
2014-08-23 13:50:47 +00:00
{
get
{
if (HorizontalOrientation)
{
if (NeedsHScrollbar)
{
return HBar.Value / CellWidth;
2014-08-23 13:50:47 +00:00
}
}
if (NeedsVScrollbar)
{
return VBar.Value / CellHeight;
2014-08-23 13:50:47 +00:00
}
return 0;
}
set
{
if (HorizontalOrientation)
{
if (NeedsHScrollbar)
{
_programmaticallyUpdatingScrollBarValues = true;
HBar.Value = value * CellWidth;
_programmaticallyUpdatingScrollBarValues = false;
2014-08-23 13:50:47 +00:00
}
}
else
2014-08-23 13:50:47 +00:00
{
if (NeedsVScrollbar)
{
_programmaticallyUpdatingScrollBarValues = true;
VBar.Value = value * CellHeight;
_programmaticallyUpdatingScrollBarValues = false;
}
2014-08-23 13:50:47 +00:00
}
}
}
public int LastVisibleRow
{
get
{
return FirstVisibleRow + VisibleRows;
}
set
{
int i = Math.Max(value - VisibleRows, 0);
FirstVisibleRow = i;
}
}
public bool IsVisible(int index)
{
return (index >= FirstVisibleRow) && (index <= LastVisibleRow);
}
2014-08-23 13:50:47 +00:00
/// <summary>
/// Gets the number of rows currently visible including partially visible rows.
2014-08-23 13:50:47 +00:00
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public int VisibleRows
{
get
{
if (HorizontalOrientation)
{
var width = DrawWidth - (NeedsVScrollbar ? VBar.Width : 0);
return (int)((width - ColumnWidth) / CellWidth);
2014-08-23 13:50:47 +00:00
}
var height = DrawHeight - (NeedsHScrollbar ? HBar.Height : 0);
return (int)height / CellHeight;
2014-08-23 13:50:47 +00:00
}
}
// TODO: make IEnumerable, IList is for legacy support
[Browsable(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public IList<int> SelectedRows
{
get
{
return SelectedItems
.Where(cell => cell.RowIndex.HasValue)
.Select(cell => cell.RowIndex.Value)
.Distinct()
.ToList();
}
}
#endregion
#region Paint
protected override void OnPaint(PaintEventArgs e)
2014-08-07 18:32:09 +00:00
{
using (var LCK = Gdi.LockGraphics(e.Graphics))
2014-08-07 18:32:09 +00:00
{
Gdi.StartOffScreenBitmap(Width, Height);
//White Background
Gdi.SetBrush(Color.White);
Gdi.SetSolidPen(Color.White);
Gdi.FillRectangle(0, 0, Width, Height);
if (_columns.Any())
2014-08-09 16:11:25 +00:00
{
DrawColumnBg(e);
DrawColumnText(e);
2014-08-09 16:11:25 +00:00
}
2014-08-07 18:32:09 +00:00
//Background
DrawBg(e);
2014-08-07 18:32:09 +00:00
//Foreground
DrawData(e);
Gdi.CopyToScreen();
Gdi.EndOffScreenBitmap();
2014-08-07 18:32:09 +00:00
}
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
2014-08-08 13:42:05 +00:00
// Do nothing, and this should never be called
}
private void DrawColumnText(PaintEventArgs e)
2014-08-08 02:09:59 +00:00
{
if (HorizontalOrientation)
{
int start = 0;
Gdi.PrepDrawString(this.Font, this.ForeColor);
foreach (var column in _columns)
2014-08-08 02:09:59 +00:00
{
var point = new Point(CellPadding, start + CellPadding);
2014-08-11 01:23:53 +00:00
if (IsHoveringOnColumnCell && column == CurrentCell.Column)
{
Gdi.PrepDrawString(this.Font, SystemColors.HighlightText);
Gdi.DrawString(column.Text, point);
Gdi.PrepDrawString(this.Font, this.ForeColor);
}
else
{
Gdi.DrawString(column.Text, point);
}
2014-08-08 02:09:59 +00:00
start += CellHeight;
}
}
else
{
Gdi.PrepDrawString(this.Font, this.ForeColor);
foreach (var column in _columns)
2014-08-08 02:09:59 +00:00
{
var point = new Point(column.Left.Value + 2* CellPadding - HBar.Value, CellPadding);//TODO: fix this CellPadding issue (2 * CellPadding vs just CellPadding)
2014-08-11 01:23:53 +00:00
if (IsHoveringOnColumnCell && column == CurrentCell.Column)
{
Gdi.PrepDrawString(this.Font, SystemColors.HighlightText);
Gdi.DrawString(column.Text, point);
Gdi.PrepDrawString(this.Font, this.ForeColor);
}
else
{
Gdi.DrawString(column.Text, point);
}
2014-08-08 02:09:59 +00:00
}
}
}
private void DrawData(PaintEventArgs e)
{
if (QueryItemText != null)
{
if (HorizontalOrientation)
{
int startIndex = FirstVisibleRow;
int range = Math.Min(LastVisibleRow, RowCount - 1) - startIndex + 1;
Gdi.PrepDrawString(this.Font, this.ForeColor);
for (int i = 0; i < range; i++)
{
for (int j = 0; j < _columns.Count; j++)
{
string text;
QueryItemText(i + startIndex, j, out text);
//Center Text
int x = RowsToPixels(i) + (CellWidth - text.Length * _charSize.Width) / 2;
int y = j * CellHeight + CellPadding;
var point = new Point(x, y);
if (!string.IsNullOrWhiteSpace(text))
{
Gdi.DrawString(text, point);
}
}
}
}
else
{
int startRow = FirstVisibleRow;
int range = Math.Min(LastVisibleRow, RowCount - 1) - startRow + 1;
Gdi.PrepDrawString(this.Font, this.ForeColor);
int xPadding = CellPadding + 1 - HBar.Value;
for (int i = 0; i < range; i++)//Vertical
{
for (int j = 0; j < _columns.Count; j++)//Horizontal
{
var col = _columns[j];
if (col.Left.Value < 0 || col.Right.Value > DrawWidth)
{
continue;
}
string text;
2014-09-22 15:35:23 +00:00
var point = new Point(col.Left.Value + xPadding, RowsToPixels(i) + CellPadding);
Bitmap image = null;
if (QueryItemIcon != null)
{
QueryItemIcon(i + startRow, j, ref image);
}
if (image != null)
{
2014-09-22 15:35:23 +00:00
Gdi.DrawBitmap(image, new Point(col.Left.Value, point.Y + CellPadding), true);
}
else
{
QueryItemText(i + startRow, j, out text);
if (!string.IsNullOrWhiteSpace(text))
{
Gdi.DrawString(text, point);
}
}
}
}
}
}
}
2014-08-08 13:42:05 +00:00
private void DrawColumnBg(PaintEventArgs e)
{
Gdi.SetBrush(SystemColors.ControlLight);
Gdi.SetSolidPen(Color.Black);
if (HorizontalOrientation)
2014-08-08 02:09:59 +00:00
{
Gdi.FillRectangle(0, 0, ColumnWidth + 1, DrawHeight + 1);
Gdi.Line(0, 0, 0, _columns.Count * CellHeight + 1);
Gdi.Line(ColumnWidth, 0, ColumnWidth, _columns.Count * CellHeight + 1);
int start = 0;
foreach (var column in _columns)
{
Gdi.Line(1, start, ColumnWidth, start);
start += CellHeight;
}
if (_columns.Any())
{
Gdi.Line(1, start, ColumnWidth, start);
}
}
else
{
int bottomEdge = RowsToPixels(0);
//Gray column box and black line underneath
Gdi.FillRectangle(0, 0, Width + 1, bottomEdge + 1);
Gdi.Line(0, 0, TotalColWidth.Value + 1, 0);
Gdi.Line(0, bottomEdge, TotalColWidth.Value + 1, bottomEdge);
//Vertical black seperators
for (int i = 0; i < _columns.Count; i++)
{
int pos = _columns[i].Left.Value - HBar.Value;
Gdi.Line(pos, 0, pos, bottomEdge);
}
////Draw right most line
if (_columns.Any())
{
int right = TotalColWidth.Value - HBar.Value;
Gdi.Line(right, 0, right, bottomEdge);
}
}
2014-08-11 01:23:53 +00:00
// If the user is hovering over a column
if (IsHoveringOnColumnCell)
{
if (HorizontalOrientation)
{
for (int i = 0; i < _columns.Count; i++)
2014-08-11 01:23:53 +00:00
{
if (_columns[i] != CurrentCell.Column)
2014-08-11 01:23:53 +00:00
{
continue;
}
Gdi.SetBrush(SystemColors.Highlight);
Gdi.FillRectangle(1, i * CellHeight + 1, ColumnWidth - 1, ColumnHeight - 1);
2014-08-11 01:23:53 +00:00
}
}
else
{
//TODO multiple selected columns
for (int i = 0; i < _columns.Count; i++)
2014-08-11 01:23:53 +00:00
{
if (_columns[i] == CurrentCell.Column){
//Left of column is to the right of the viewable area or right of column is to the left of the viewable area
if(_columns[i].Left.Value - HBar.Value > Width || _columns[i].Right.Value - HBar.Value < 0){
continue;
}
int left = _columns[i].Left.Value - HBar.Value;
int width = _columns[i].Right.Value - HBar.Value - left;
2014-08-11 01:23:53 +00:00
Gdi.SetBrush(SystemColors.Highlight);
Gdi.FillRectangle(left + 1, 1, width - 1, ColumnHeight - 1);
2014-08-11 01:23:53 +00:00
}
}
}
}
}
//TODO refactor this and DoBackGroundCallback functions.
/// <summary>
/// Draw Gridlines and background colors using QueryItemBkColor.
/// </summary>
/// <param name="e"></param>
private void DrawBg(PaintEventArgs e)
{
2014-08-29 14:49:36 +00:00
if (QueryItemBkColor != null && UseCustomBackground)
{
DoBackGroundCallback(e);
}
if (GridLines)
{
Gdi.SetSolidPen(SystemColors.ControlLight);
if (HorizontalOrientation)
{
// Columns
for (int i = 1; i < VisibleRows + 1; i++)
2014-08-19 00:37:38 +00:00
{
var x = RowsToPixels(i);
var y2 = (_columns.Count * CellHeight) - 1;
if (y2 > Height)
{
y2 = Height - 2;
}
2014-08-19 00:37:38 +00:00
Gdi.Line(x, 1, x, DrawHeight);
}
// Rows
for (int i = 0; i < _columns.Count + 1; i++)
{
Gdi.Line(RowsToPixels(0) + 1, i * CellHeight, DrawWidth, i * CellHeight);
}
}
else
{
// Columns
int y = ColumnHeight + 1;
foreach (var column in _columns)
{
int x = column.Left.Value - HBar.Value;
Gdi.Line(x, y, x, Height - 1);
}
if (_columns.Any())
{
Gdi.Line(TotalColWidth.Value - HBar.Value, y, TotalColWidth.Value - HBar.Value, Height - 1);
}
// Rows
for (int i = 1; i < VisibleRows + 1; i++)
{
Gdi.Line(0, RowsToPixels(i), Width + 1, RowsToPixels(i));
}
}
}
if (SelectedItems.Any())
{
DoSelectionBG(e);
}
}
private void DoSelectionBG(PaintEventArgs e)
{
foreach(var cell in SelectedItems)
{
var relativeCell = new Cell()
{
RowIndex = cell.RowIndex - FirstVisibleRow,
Column = cell.Column,
CurrentText = cell.CurrentText
};
DrawCellBG(SystemColors.Highlight, relativeCell);
}
}
/// <summary>
/// Given a cell with rowindex inbetween 0 and VisibleRows, it draws the background color specified. Do not call with absolute rowindices.
/// </summary>
/// <param name="color"></param>
/// <param name="cell"></param>
private void DrawCellBG(Color color, Cell cell)
{
int x = 0,
y = 0,
w = 0,
h = 0;
if (HorizontalOrientation)
{
x = RowsToPixels(cell.RowIndex.Value) + 1;
w = CellWidth - 1;
y = (CellHeight * _columns.IndexOf(cell.Column)) + 1; // We can't draw without row and column, so assume they exist and fail catastrophically if they don't
h = CellHeight - 1;
if (x < ColumnWidth) { return; }
}
else
{
w = cell.Column.Width.Value - 1;
x = cell.Column.Left.Value - HBar.Value + 1;
y = RowsToPixels(cell.RowIndex.Value) + 1; // We can't draw without row and column, so assume they exist and fail catastrophically if they don't
h = CellHeight - 1;
if (y < ColumnHeight) { return; }
}
if (x > DrawWidth || y > DrawHeight) { return; }//Don't draw if off screen.
Gdi.SetBrush(color);
Gdi.FillRectangle(x, y, w, h);
}
/// <summary>
/// Calls QueryItemBkColor callback for all visible cells and fills in the background of those cells.
/// </summary>
/// <param name="e"></param>
private void DoBackGroundCallback(PaintEventArgs e)
{
if (HorizontalOrientation)
{
int startIndex = FirstVisibleRow;
int range = Math.Min(LastVisibleRow, RowCount - 1) - startIndex + 1;
for (int i = 0; i < range; i++)
{
for (int j = 0; j < _columns.Count; j++)//TODO: Don't query all columns
{
Color color = Color.White;
QueryItemBkColor(i + startIndex, j, ref color);
if (color != Color.White) // An easy optimization, don't draw unless the user specified something other than the default
{
var cell = new Cell()
{
Column = _columns[j],
RowIndex = i
};
DrawCellBG(color, cell);
}
}
}
}
else
{
int startRow = FirstVisibleRow;
int range = Math.Min(LastVisibleRow, RowCount - 1) - startRow + 1;
for (int i = 0; i < range; i++)//Vertical
{
for (int j = 0; j < _columns.Count; j++)//Horizontal
{
Color color = Color.White;
QueryItemBkColor(i + startRow, j, ref color);
if (color != Color.White) // An easy optimization, don't draw unless the user specified something other than the default
{
var cell = new Cell()
{
Column = _columns[j],
RowIndex = i
};
DrawCellBG(color, cell);
}
}
}
}
}
#endregion
#region Mouse and Key Events
protected override void OnMouseMove(MouseEventArgs e)
{
var newCell = CalculatePointedCell(e.X, e.Y);
newCell.RowIndex += FirstVisibleRow;
if (!newCell.Equals(CurrentCell))
{
CellChanged(newCell);
if (IsHoveringOnColumnCell ||
(WasHoveringOnColumnCell && !IsHoveringOnColumnCell))
{
Refresh();
}
}
base.OnMouseMove(e);
}
protected override void OnMouseEnter(EventArgs e)
{
CurrentCell = new Cell
{
Column = null,
RowIndex = null
};
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
CurrentCell = null;
IsPaintDown = false;
2014-08-11 01:23:53 +00:00
Refresh();
base.OnMouseLeave(e);
}
//TODO add query callback of whether to select the cell or not
protected override void OnMouseDown(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && InputPaintingMode)
{
IsPaintDown = true;
}
if (e.Button == MouseButtons.Right)
{
RightButtonHeld = true;
}
if (e.Button == MouseButtons.Left)
{
if (IsHoveringOnColumnCell)
{
ColumnClickEvent(ColumnAtX(e.X));
}
else if (IsHoveringOnDataCell)
{
if (ModifierKeys == Keys.Alt)
{
MessageBox.Show("Alt click logic is not yet implemented");
}
else if (ModifierKeys == Keys.Shift)
{
if (SelectedItems.Any())
{
if (FullRowSelect)
{
var selected = SelectedItems.Any(c => c.RowIndex.HasValue && CurrentCell.RowIndex.HasValue && c.RowIndex == CurrentCell.RowIndex);
if (!selected)
{
var rowIndices = SelectedItems
.Where(c => c.RowIndex.HasValue)
.Select(c => c.RowIndex ?? -1)
.Where(c => c >= 0) // Hack to avoid possible Nullable exceptions
.Distinct();
var firstIndex = rowIndices.Min();
var lastIndex = rowIndices.Max();
if (CurrentCell.RowIndex.Value < firstIndex)
{
for (int i = CurrentCell.RowIndex.Value; i < firstIndex; i++)
{
SelectCell(new Cell
{
RowIndex = i,
Column = CurrentCell.Column
});
}
}
else if (CurrentCell.RowIndex.Value > lastIndex)
{
for (int i = lastIndex + 1; i <= CurrentCell.RowIndex.Value; i++)
{
SelectCell(new Cell
{
RowIndex = i,
Column = CurrentCell.Column
});
}
}
else // Somewhere in between, a scenario that can happen with ctrl-clicking, find the previous and highlight from there
{
var nearest = rowIndices
.Where(x => x < CurrentCell.RowIndex.Value)
.Max();
for (int i = nearest + 1; i <= CurrentCell.RowIndex.Value; i++)
{
SelectCell(new Cell
{
RowIndex = i,
Column = CurrentCell.Column
});
}
}
}
}
else
{
MessageBox.Show("Shift click logic for individual cells has not yet implemented");
}
}
else
{
SelectCell(CurrentCell);
}
}
else if (ModifierKeys == Keys.Control)
{
SelectCell(CurrentCell, toggle: true);
}
else
{
SelectedItems.Clear();
SelectCell(CurrentCell);
}
Refresh();
}
}
base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
IsPaintDown = false;
RightButtonHeld = false;
base.OnMouseUp(e);
}
2014-08-29 15:53:59 +00:00
private void IncrementScrollBar(ScrollBar bar, bool increment)
{
2014-08-29 15:53:59 +00:00
int newVal;
if (increment)
{
newVal = bar.Value + bar.SmallChange;
if (newVal > bar.Maximum)
{
newVal = bar.Maximum;
}
2014-08-29 15:53:59 +00:00
}
else
{
newVal = bar.Value - bar.SmallChange;
if (newVal < 0)
{
2014-08-29 15:53:59 +00:00
newVal = 0;
}
}
_programmaticallyUpdatingScrollBarValues = true;
bar.Value = newVal;
_programmaticallyUpdatingScrollBarValues = false;
}
protected override void OnMouseWheel(MouseEventArgs e)
{
if (RightButtonHeld)
{
DoRightMouseScroll(this, e);
}
else
{
2014-08-29 15:53:59 +00:00
if (HorizontalOrientation)
{
IncrementScrollBar(HBar, e.Delta < 0);
}
else
{
IncrementScrollBar(VBar, e.Delta < 0);
}
Refresh();
}
}
private void DoRightMouseScroll(object sender, MouseEventArgs e)
{
if (RightMouseScrolled != null)
{
RightMouseScrolled(sender, e);
}
}
private void ColumnClickEvent(RollColumn column)
{
if (ColumnClick != null)
{
ColumnClick(this, new ColumnClickEventArgs(_columns.IndexOf(column)));
}
}
#endregion
2014-08-18 21:38:02 +00:00
#region Change Events
protected override void OnResize(EventArgs e)
{
RecalculateScrollBars();
base.OnResize(e);
Refresh();
}
private void OrientationChanged()
{
RecalculateScrollBars();
//TODO scroll to correct positions
if (HorizontalOrientation)
{
VBar.SmallChange = CellHeight;
HBar.SmallChange = CellWidth;
VBar.LargeChange = 10;
HBar.LargeChange = CellWidth * 20;
}
else
{
VBar.SmallChange = CellHeight;
HBar.SmallChange = 1;
VBar.LargeChange = CellHeight * 20;
HBar.LargeChange = 20;
}
RecalculateScrollBars();
Refresh();
}
/// <summary>
/// Call this function to change the CurrentCell to newCell
/// </summary>
/// <param name="oldCell"></param>
/// <param name="newCell"></param>
private void CellChanged(Cell newCell)
{
if (PointedCellChanged != null && newCell != CurrentCell)
{
LastCell = CurrentCell;
CurrentCell = newCell;
PointedCellChanged(this, new CellEventArgs(LastCell, CurrentCell));
}
}
2014-08-19 00:37:38 +00:00
private void VerticalBar_ValueChanged(object sender, EventArgs e)
{
if (!_programmaticallyUpdatingScrollBarValues)
{
Refresh();
}
2014-08-19 00:37:38 +00:00
}
private void HorizontalBar_ValueChanged(object sender, EventArgs e)
{
if (!_programmaticallyUpdatingScrollBarValues)
{
Refresh();
}
2014-08-19 00:37:38 +00:00
}
2014-08-18 23:50:50 +00:00
private void ColumnChangedCallback()
{
RecalculateScrollBars();
if (_columns.Any())
{
ColumnWidth = _columns.Max(c => c.Width.Value) + CellPadding * 4;
}
2014-08-18 23:50:50 +00:00
}
#endregion
#region Helpers
//ScrollBar.Maximum = DesiredValue + ScrollBar.LargeChange - 1
//See MSDN Page for more information on the dumb ScrollBar.Maximum Property
2014-08-18 21:38:02 +00:00
private void RecalculateScrollBars()
{
UpdateDrawSize();
if (HorizontalOrientation)
2014-08-18 21:38:02 +00:00
{
NeedsVScrollbar = _columns.Count > DrawHeight / CellHeight;
NeedsHScrollbar = RowCount > (DrawWidth - ColumnWidth) / CellWidth;
}
else
{
NeedsVScrollbar = RowCount > DrawHeight / CellHeight;
NeedsHScrollbar = TotalColWidth.HasValue && TotalColWidth.Value - DrawWidth + 1 > 0;
}
UpdateDrawSize();
2014-08-21 21:09:21 +00:00
//Update VBar
if (NeedsVScrollbar)
{
2014-08-18 21:38:02 +00:00
if (HorizontalOrientation)
{
VBar.Maximum = (((_columns.Count * CellHeight) - DrawHeight) / CellHeight) + VBar.LargeChange;
2014-08-18 21:38:02 +00:00
}
else
{
VBar.Maximum = RowsToPixels(RowCount + 1) - DrawHeight + VBar.LargeChange - 1;
2014-08-18 21:38:02 +00:00
}
2014-08-21 21:09:21 +00:00
VBar.Location = new Point(Width - 17, 0);
2014-08-21 21:09:21 +00:00
VBar.Size = new Size(VBar.Width, Height);
VBar.Visible = true;
2014-08-18 21:38:02 +00:00
}
else
{
VBar.Visible = false;
VBar.Value = 0;
2014-08-18 21:38:02 +00:00
}
//Update HBar
2014-08-18 21:38:02 +00:00
if (NeedsHScrollbar)
{
HBar.Visible = true;
if (HorizontalOrientation)
{
HBar.Maximum = RowsToPixels(RowCount + 1) - DrawWidth + HBar.LargeChange - 1;
2014-08-18 21:38:02 +00:00
}
else
{
HBar.Maximum = TotalColWidth.Value - DrawWidth + HBar.LargeChange;
}
if (NeedsVScrollbar)
{
HBar.Size = new Size(Width - VBar.Width + 1, HBar.Height);
}
else
{
VBar.Location = new Point(0, Height - 17);
HBar.Size = new Size(Width, HBar.Height);
2014-08-18 21:38:02 +00:00
}
}
else
{
HBar.Visible = false;
HBar.Value = 0;
}
}
private void UpdateDrawSize()
{
if (NeedsVScrollbar)
{
DrawWidth = Width - VBar.Width;
}
else
{
DrawWidth = Width;
}
if (NeedsHScrollbar) {
DrawHeight = Height - HBar.Height;
}
else
{
DrawHeight = Height;
2014-08-18 21:38:02 +00:00
}
}
/// <summary>
/// If FullRowSelect is enabled, selects all cells in the row that contains the given cell. Otherwise only given cell is added.
/// </summary>
/// <param name="cell">The cell to select.</param>
private void SelectCell(Cell cell, bool toggle = false)
{
if (cell.RowIndex.HasValue && cell.RowIndex < RowCount)
{
if (!MultiSelect)
{
SelectedItems.Clear();
}
if (FullRowSelect)
{
if (toggle && SelectedItems.Any(x => x.RowIndex.HasValue && x.RowIndex == cell.RowIndex))
{
var items = SelectedItems
.Where(x => x.RowIndex.HasValue && x.RowIndex == cell.RowIndex)
.ToList();
foreach (var item in items)
{
SelectedItems.Remove(item);
}
}
else
{
foreach (var column in _columns)
{
SelectedItems.Add(new Cell
{
RowIndex = cell.RowIndex,
Column = column
});
}
}
}
else
{
if (toggle && SelectedItems.Any(x => x.RowIndex.HasValue && x.RowIndex == cell.RowIndex))
{
var item = SelectedItems
.FirstOrDefault(x => x.Equals(cell));
if (item != null)
{
SelectedItems.Remove(item);
}
}
else
{
SelectedItems.Add(CurrentCell);
}
}
SelectedIndexChanged(this, new EventArgs());
}
}
/// <summary>
/// Bool that indicates if CurrentCell is a Column Cell.
/// </summary>
2014-08-11 01:23:53 +00:00
private bool IsHoveringOnColumnCell
{
get
{
return CurrentCell != null &&
CurrentCell.Column != null &&
!CurrentCell.RowIndex.HasValue;
}
}
/// <summary>
/// Bool that indicates if CurrentCell is a Data Cell.
/// </summary>
private bool IsHoveringOnDataCell
{
get
{
return CurrentCell != null &&
CurrentCell.Column != null &&
CurrentCell.RowIndex.HasValue;
}
}
/// <summary>
/// Bool that indicates if CurrentCell is a Column Cell.
/// </summary>
private bool WasHoveringOnColumnCell
{
get
{
return LastCell != null &&
LastCell.Column != null &&
!LastCell.RowIndex.HasValue;
}
}
/// <summary>
/// Bool that indicates if CurrentCell is a Data Cell.
/// </summary>
private bool WasHoveringOnDataCell
{
get
{
return LastCell != null &&
LastCell.Column != null &&
LastCell.RowIndex.HasValue;
}
}
/// <summary>
/// Finds the specific cell that contains the (x, y) coordinate.
/// </summary>
/// <remarks>The row number that it returns will be between 0 and VisibleRows, NOT the absolute row number.</remarks>
/// <param name="x">X coordinate point.</param>
/// <param name="y">Y coordinate point.</param>
/// <returns>The cell with row number and RollColumn reference, both of which can be null. </returns>
private Cell CalculatePointedCell(int x, int y)
{
var newCell = new Cell();
// If pointing to a column header
if (_columns.Any())
{
if (HorizontalOrientation)
{
if (x >= ColumnWidth)
{
newCell.RowIndex = PixelsToRows(x);
}
int colIndex = (y / CellHeight);
if (colIndex >= 0 && colIndex < _columns.Count)
{
newCell.Column = _columns[colIndex];
}
}
else
{
if (y >= CellHeight)
{
newCell.RowIndex = PixelsToRows(y);
}
newCell.Column = ColumnAtX(x);
2014-08-11 01:23:53 +00:00
}
}
return newCell;
}
// TODO: Calculate this on Orientation change instead of call it every time
//TODO: find a different solution
2014-08-09 13:13:24 +00:00
private Point StartBg()
2014-08-07 18:32:09 +00:00
{
if (_columns.Any())
2014-08-07 18:32:09 +00:00
{
2014-08-09 13:13:24 +00:00
if (HorizontalOrientation)
2014-08-07 18:32:09 +00:00
{
var x = ColumnWidth;
2014-08-09 13:13:24 +00:00
var y = 0;
return new Point(x, y);
}
else
{
var y = ColumnHeight;
return new Point(0, y);
2014-08-07 18:32:09 +00:00
}
}
2014-08-09 13:13:24 +00:00
return new Point(0, 0);
2014-08-07 23:10:41 +00:00
}
private void DrawRectangleNoFill(GDIRenderer gdi, int x, int y, int width, int height)
{
gdi.Line(x, y, x + width, y);
gdi.Line(x + width, y, x + width, y + height);
gdi.Line(x + width, y + height, x, y + height);
gdi.Line(x, y + height, x, y);
}
/// <summary>
/// A boolean that indicates if the InputRoll is too large vertically and requires a vertical scrollbar.
/// </summary>
private bool NeedsVScrollbar{ get; set; }
2014-08-15 00:42:03 +00:00
/// <summary>
/// A boolean that indicates if the InputRoll is too large horizontally and requires a horizontal scrollbar.
/// </summary>
private bool NeedsHScrollbar{ get; set; }
//TODO rename and find uses
//private void ColumnChanged()
//{
// var text = _columns.Max(c => c.Text.Length);
// ColumnWidth = (text * _charSize.Width) + (CellPadding * 2);
//}
2014-08-09 16:11:25 +00:00
/// <summary>
/// Updates the width of the supplied column.
/// <remarks>Call when changing the ColumnCell text, CellPadding, or text font.</remarks>
/// </summary>
/// <param name="col">The RollColumn object to update.</param>
/// <returns>The new width of the RollColumn object.</returns>
private int UpdateWidth(RollColumn col)
2014-08-09 16:11:25 +00:00
{
col.Width = ((col.Text.Length * _charSize.Width) + (CellPadding * 4));
return col.Width.Value;
2014-08-08 18:30:57 +00:00
}
/// <summary>
/// Gets the total width of all the columns by using the last column's Right property.
/// </summary>
/// <returns>A nullable Int representing total width.</returns>
private int? TotalColWidth
{
get{
if (_columns.Any())
{
return _columns.Last().Right;
}
return null;
}
}
/// <summary>
/// Returns the RollColumn object at the specified visible x coordinate. Coordinate should be between 0 and Width of the InputRoll Control.
/// </summary>
/// <param name="x">The x coordinate.</param>
/// <returns>RollColumn object that contains the x coordinate or null if none exists.</returns>
private RollColumn ColumnAtX(int x)
{
foreach (var column in _columns)
{
if (column.Left.Value - HBar.Value <= x && column.Right.Value - HBar.Value >= x)
{
return column;
}
}
return null;
}
/// <summary>
/// Converts a row number to a horizontal or vertical coordinate.
/// </summary>
/// <param name="pixels">A row number.</param>
/// <returns>A vertical coordinate if Vertical Oriented, otherwise a horizontal coordinate.</returns>
private int RowsToPixels(int index)
{
if (_horizontalOrientation)
{
return index * CellWidth + ColumnWidth;
}
return index * CellHeight + ColumnHeight;
}
/// <summary>
/// Converts a horizontal or vertical coordinate to a row number.
/// </summary>
/// <param name="pixels">A vertical coordinate if Vertical Oriented, otherwise a horizontal coordinate.</param>
/// <returns>A row number between 0 and VisibleRows if it is a Datarow, otherwise a negative number if above all Datarows.</returns>
private int PixelsToRows(int pixels)
{
if (_horizontalOrientation)
{
return (pixels - ColumnWidth) / CellWidth;
}
return (pixels - ColumnHeight) / CellHeight;
}
/// <summary>
/// The width of the largest column cell in Horizontal Orientation
/// </summary>
private int ColumnWidth { get; set; }
/// <summary>
/// The height of a column cell in Vertical Orientation.
/// </summary>
private int ColumnHeight { get; set; }
//Cell defaults
/// <summary>
/// The width of a cell in Horizontal Orientation. Only can be changed by changing the Font or CellPadding.
/// </summary>
private int CellWidth { get; set; }
/// <summary>
/// The height of a cell in Vertical Orientation. Only can be changed by changing the Font or CellPadding.
/// </summary>
private int CellHeight { get; set; }
/// <summary>
/// Call when _charSize, MaxCharactersInHorizontal, or CellPadding is changed.
/// </summary>
private void UpdateCellSize()
{
CellHeight = _charSize.Height + CellPadding * 2;
CellWidth = _charSize.Width * MaxCharactersInHorizontal + CellPadding * 4; // Double the padding for horizontal because it looks better
}
#endregion
#region Classes
public class RollColumns : List<RollColumn>
{
2014-08-23 13:19:48 +00:00
public RollColumn this[string name]
{
get
{
return this.SingleOrDefault(column => column.Name == name);
}
}
2014-08-18 23:50:50 +00:00
public Action ChangedCallback { get; set; }
private void DoChangeCallback()
{
2014-08-18 23:50:50 +00:00
if (ChangedCallback != null)
{
2014-08-18 23:50:50 +00:00
ChangedCallback();
}
}
private void ColumnsChanged()
{
int pos = 0;
for (int i = 0; i < Count; i++)
{
this[i].Left = pos;
pos += this[i].Width.Value;
this[i].Right = pos;
}
DoChangeCallback();
}
2014-08-18 23:50:50 +00:00
public new void Add(RollColumn column)
{
if (this.Any(c => c.Name == column.Name))
{
// The designer sucks, doing nothing for now
return;
//throw new InvalidOperationException("A column with this name already exists.");
2014-08-18 23:50:50 +00:00
}
base.Add(column);
ColumnsChanged();
2014-08-18 23:50:50 +00:00
}
public new void AddRange(IEnumerable<RollColumn> collection)
{
foreach(var column in collection)
{
if (this.Any(c => c.Name == column.Name))
{
// The designer sucks, doing nothing for now
return;
2014-08-18 23:50:50 +00:00
throw new InvalidOperationException("A column with this name already exists.");
}
}
base.AddRange(collection);
ColumnsChanged();
2014-08-18 23:50:50 +00:00
}
public new void Insert(int index, RollColumn column)
{
if (this.Any(c => c.Name == column.Name))
{
throw new InvalidOperationException("A column with this name already exists.");
}
base.Insert(index, column);
ColumnsChanged();
2014-08-18 23:50:50 +00:00
}
public new void InsertRange(int index, IEnumerable<RollColumn> collection)
{
foreach (var column in collection)
{
if (this.Any(c => c.Name == column.Name))
{
throw new InvalidOperationException("A column with this name already exists.");
}
}
base.InsertRange(index, collection);
ColumnsChanged();
2014-08-18 23:50:50 +00:00
}
public new bool Remove(RollColumn column)
{
var result = base.Remove(column);
ColumnsChanged();
2014-08-18 23:50:50 +00:00
return result;
}
public new int RemoveAll(Predicate<RollColumn> match)
{
var result = base.RemoveAll(match);
ColumnsChanged();
2014-08-18 23:50:50 +00:00
return result;
}
public new void RemoveAt(int index)
{
base.RemoveAt(index);
ColumnsChanged();
2014-08-18 23:50:50 +00:00
}
public new void RemoveRange(int index, int count)
{
base.RemoveRange(index, count);
ColumnsChanged();
2014-08-18 23:50:50 +00:00
}
public new void Clear()
{
base.Clear();
ColumnsChanged();
}
2014-08-09 16:11:25 +00:00
public IEnumerable<string> Groups
2014-08-09 16:11:25 +00:00
{
get
{
return this
.Select(x => x.Group)
.Distinct();
}
2014-08-09 16:11:25 +00:00
}
}
public class RollColumn
{
public enum InputType { Boolean, Float, Text, Image }
2014-08-07 18:32:09 +00:00
public string Group { get; set; }
public int? Width { get; set; }
public int? Left { get; set; }
public int? Right { get; set; }
public string Name { get; set; }
public string Text { get; set; }
public InputType Type { get; set; }
}
/// <summary>
///
/// </summary>
public class Cell
{
public RollColumn Column { get; internal set; }
public int? RowIndex { get; internal set; }
public string CurrentText { get; internal set; }
public Cell() { }
public Cell(Cell cell)
{
Column = cell.Column;
RowIndex = cell.RowIndex;
}
public override bool Equals(object obj)
{
if (obj is Cell)
{
var cell = obj as Cell;
return this.Column == cell.Column && this.RowIndex == cell.RowIndex;
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return Column.GetHashCode() + RowIndex.GetHashCode();
}
}
#endregion
}
}