diff --git a/BizHawk.Util/BizHawk.Util.csproj b/BizHawk.Util/BizHawk.Util.csproj
index c48d936242..4af4cb2658 100644
--- a/BizHawk.Util/BizHawk.Util.csproj
+++ b/BizHawk.Util/BizHawk.Util.csproj
@@ -100,6 +100,9 @@
Component
+
+ Component
+
diff --git a/BizHawk.Util/VirtualListView.cs b/BizHawk.Util/VirtualListView.cs
new file mode 100644
index 0000000000..f73478e0c5
--- /dev/null
+++ b/BizHawk.Util/VirtualListView.cs
@@ -0,0 +1,622 @@
+using System;
+using System.Windows.Forms;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Drawing;
+
+namespace BizHawk
+{
+
+ #region win32interop
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct LVDISPINFO {
+ public NMHDR hdr;
+ public LVITEM item;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct NMHDR {
+ public int hwndFrom;
+ public int idFrom;
+ public int code;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct RECT {
+ public int left, top, right, bottom;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct NMCUSTOMDRAWINFO {
+ public NMHDR hdr;
+ public uint dwDrawStage;
+ public IntPtr hdc;
+ public RECT rc;
+ public int dwItemSpec;
+ public uint uItemState;
+ public int lItemlParam;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct NMLVCUSTOMDRAW {
+ public NMCUSTOMDRAWINFO nmcd;
+ public int clrText;
+ public int clrTextBk;
+ public int iSubItem;
+ }
+
+
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal struct LVITEM {
+ public uint mask;
+ public int iItem;
+ public int iSubItem;
+ public uint state;
+ public uint stateMask;
+ public IntPtr pszText;
+ public int cchTextMax;
+ public int iImage;
+ public IntPtr lParam;
+ //#if (_WIN32_IE >= 0x0300)
+ public int iIndent;
+ //#endif
+ //#if (_WIN32_WINNT >= 0x501)
+ //public int iGroupId;
+ //public uint cColumns;
+ //public IntPtr puColumns;
+ //#endif
+ }
+
+ [FlagsAttribute]
+ enum CUSTOMDRAWRETURNFLAGS {
+ CDRF_DODEFAULT = 0x00000000,
+ CDRF_NEWFONT = 0x00000002,
+ CDRF_SKIPDEFAULT = 0x00000004,
+
+
+ CDRF_NOTIFYPOSTPAINT = 0x00000010,
+ CDRF_NOTIFYITEMDRAW = 0x00000020,
+ //#if (_WIN32_IE >= 0x0400)
+ CDRF_NOTIFYSUBITEMDRAW = 0x00000020,//[sic]
+ //#endif
+ CDRF_NOTIFYPOSTERASE = 0x00000040,
+ }
+
+ [FlagsAttribute]
+ enum CUSTOMDRAWDRAWSTAGEFLAGS {
+ CDDS_PREPAINT = 0x00000001,
+ CDDS_POSTPAINT = 0x00000002,
+ CDDS_PREERASE = 0x00000003,
+ CDDS_POSTERASE = 0x00000004,
+
+ // the 0x000010000 bit means it's individual item specific
+ CDDS_ITEM = 0x00010000,
+ CDDS_ITEMPREPAINT = (CDDS_ITEM | CDDS_PREPAINT),
+ CDDS_ITEMPOSTPAINT = (CDDS_ITEM | CDDS_POSTPAINT),
+ CDDS_ITEMPREERASE = (CDDS_ITEM | CDDS_PREERASE),
+ CDDS_ITEMPOSTERASE = (CDDS_ITEM | CDDS_POSTERASE),
+ //#if (_WIN32_IE >= 0x0400)
+ CDDS_SUBITEM = 0x00020000,
+ CDDS_SUBITEMPREPAINT = (CDDS_SUBITEM | CDDS_ITEMPREPAINT),
+ CDDS_SUBITEMPOSTPAINT = (CDDS_SUBITEM | CDDS_ITEMPOSTPAINT),
+ CDDS_SUBITEMPREERASE = (CDDS_SUBITEM | CDDS_ITEMPREERASE),
+ CDDS_SUBITEMPOSTERASE = (CDDS_SUBITEM | CDDS_ITEMPOSTERASE),
+ //#endif
+ }
+
+ [FlagsAttribute]
+ enum LVHITTESTFLAGS {
+ LVHT_NOWHERE = 0x0001,
+ LVHT_ONITEMICON = 0x0002,
+ LVHT_ONITEMLABEL = 0x0004,
+ LVHT_ONITEMSTATEICON = 0x0008,
+ LVHT_ONITEM = (LVHT_ONITEMICON | LVHT_ONITEMLABEL | LVHT_ONITEMSTATEICON),
+
+ LVHT_ABOVE = 0x0008,
+ LVHT_BELOW = 0x0010,
+ LVHT_TORIGHT = 0x0020,
+ LVHT_TOLEFT = 0x0040
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct POINT {
+ public int x;
+ public int y;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal class LVHITTESTINFO {
+ public POINT pt;
+ public uint flags;
+ public int iItem;
+ public int iSubItem;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct NMLISTVIEW {
+ public NMHDR hdr;
+ public int iItem;
+ public int iSubItem;
+ public uint uNewState;
+ public uint uOldState;
+ public uint uChanged;
+ public POINT ptAction;
+ public IntPtr lParam;
+ }
+
+
+ internal enum ListViewItemMask : short {
+ LVIF_TEXT = 0x0001,
+ LVIF_IMAGE = 0x0002,
+ LVIF_PARAM = 0x0004,
+ LVIF_STATE = 0x0008,
+
+ LVIF_INDENT = 0x0010,
+ LVIF_NORECOMPUTE = 0x0800,
+ LVIF_GROUPID = 0x0100,
+ LVIF_COLUMNS = 0x0200
+ }
+
+ internal enum LVNI {
+ ALL = 0x0000,
+ FOCUSED = 0x0001,
+ SELECTED = 0x0002,
+ CUT = 0x0004,
+ DROPHILITED = 0x0008,
+
+ ABOVE = 0x0100,
+ BELOW = 0x0200,
+ TOLEFT = 0x0400,
+ TORIGHT = 0x0800
+ }
+
+ internal enum ListViewMessages {
+ LVM_FIRST = 0x1000,
+ LVM_GETITEMCOUNT = (LVM_FIRST + 4),
+ LVM_SETCALLBACKMASK = (LVM_FIRST + 11),
+ LVM_GETNEXTITEM = (LVM_FIRST + 12),
+ LVM_HITTEST = (LVM_FIRST + 18),
+ LVM_ENSUREVISIBLE = (LVM_FIRST + 19),
+ LVM_SETITEMSTATE = (LVM_FIRST + 43),
+ LVM_GETITEMSTATE = (LVM_FIRST + 44),
+ LVM_SETITEMCOUNT = (LVM_FIRST + 47),
+ LVM_GETSUBITEMRECT = (LVM_FIRST + 56)
+ }
+
+ internal enum ListViewStyles : short {
+ LVS_OWNERDATA = 0x1000,
+ LVS_SORTASCENDING = 0x0010,
+ LVS_SORTDESCENDING = 0x0020,
+ LVS_SHAREIMAGELISTS = 0x0040,
+ LVS_NOLABELWRAP = 0x0080,
+ LVS_AUTOARRANGE = 0x0100
+ }
+ internal enum ListViewStylesICF : uint {
+ LVSICF_NOINVALIDATEALL = 0x00000001,
+ LVSICF_NOSCROLL = 0x00000002
+ }
+
+ internal enum WindowsMessage : uint {
+ WM_LBUTTONDOWN = 0x0201,
+ WM_LBUTTONUP = 0x0202,
+ WM_LBUTTONDBLCLK = 0x0203,
+ WM_RBUTTONDOWN = 0x0204,
+ WM_RBUTTONUP = 0x0205,
+ WM_RBUTTONDBLCLK = 0x0206,
+ WM_SETFOCUS = 0x0007,
+ WM_NOTIFY = 0x004E,
+ WM_USER = 0x0400,
+ WM_REFLECT = WM_USER + 0x1c00
+ }
+
+ internal enum Notices : int {
+ NM_FIRST = 0,
+ NM_CUSTOMDRAW = NM_FIRST - 12
+ }
+
+ internal enum ListViewNotices : int {
+ LVN_FIRST = (0 - 100),
+ LVN_LAST = (0 - 199),
+ LVN_BEGINDRAG = (int)LVN_FIRST - 9,
+ LVN_BEGINRDRAG = (int)LVN_FIRST - 11,
+ LVN_GETDISPINFOA = (int)LVN_FIRST - 50,
+ LVN_GETDISPINFOW = (int)LVN_FIRST - 77,
+ LVN_SETDISPINFOA = (int)LVN_FIRST - 51,
+ LVN_SETDISPINFOW = (int)LVN_FIRST - 78,
+ LVN_ODCACHEHINT = (int)LVN_FIRST - 13,
+ LVN_ODFINDITEMW = (int)LVN_FIRST - 79
+ }
+
+ [Flags]
+ internal enum ListViewCallBackMask : uint {
+ LVIS_FOCUSED = 0x0001,
+ LVIS_SELECTED = 0x0002,
+ LVIS_CUT = 0x0004,
+ LVIS_DROPHILITED = 0x0008,
+ LVIS_GLOW = 0x0010,
+ LVIS_ACTIVATING = 0x0020,
+
+ LVIS_OVERLAYMASK = 0x0F00,
+ LVIS_STATEIMAGEMASK = 0xF000,
+ }
+
+ internal struct WindowsFunction {
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
+ }
+
+
+ #endregion
+
+ #region VirtualListView Delegates
+ ///
+ /// Retrieve the background color for a Listview cell (item and subitem).
+ ///
+ /// Listview item (row).
+ /// Listview subitem (column).
+ /// Background color to use
+ public delegate void QueryItemBkColorHandler(int item, int subItem, ref Color color);
+
+
+ ///
+ /// Retrieve the text for a Listview cell (item and subitem).
+ ///
+ /// Listview item (row).
+ /// Listview subitem (column).
+ /// Text to display.
+ public delegate void QueryItemTextHandler(int item, int subItem, out string text);
+
+ ///
+ /// Retrieve the image index for a Listview item.
+ ///
+ /// Listview item (row).
+ /// Listview subitem (column) - should always be zero.
+ /// Index of associated ImageList.
+ public delegate void QueryItemImageHandler(int item, int subItem, out int imageIndex);
+
+ ///
+ /// Retrieve the indent for a Listview item. The indent is always width of an image.
+ /// For example, 1 indents the Listview item one image width.
+ ///
+ /// Listview item (row).
+ /// The amount to indent the Listview item.
+ public delegate void QueryItemIndentHandler(int item, out int itemIndent);
+
+ public delegate void QueryItemHandler(int idx, out ListViewItem item);
+ #endregion
+
+ ///
+ /// VirtualListView is a virtual Listview which allows for a large number of items(rows)
+ /// to be displayed. The virtual Listview contains very little actual information -
+ /// mainly item selection and focus information.
+ ///
+ public class VirtualListView : ListView {
+ // store the item count to prevent the call to SendMessage(LVM_GETITEMCOUNT)
+ private int itemCount = 0;
+
+ #region Display query callbacks
+ ///
+ /// Fire the QueryItemBkColor event which requests the background color for the passed Listview cell
+ ///
+ public event QueryItemBkColorHandler QueryItemBkColor;
+
+ ///
+ /// Fire the QueryItemText event which requests the text for the passed Listview cell.
+ ///
+ [Category("Data")]
+ public event QueryItemTextHandler QueryItemText;
+ ///
+ /// Fire the QueryItemImage event which requests the ImageIndex for the passed Listview item.
+ ///
+ [Category("Data")]
+ public event QueryItemImageHandler QueryItemImage;
+ ///
+ /// Fire the QueryItemIndent event which requests the indent for the passed Listview item.
+ ///
+ [Category("Data")]
+ public event QueryItemIndentHandler QueryItemIndent;
+ [Category("Data")]
+ public event QueryItemHandler QueryItem;
+ #endregion
+
+ #region Properties
+ ///
+ /// Gets/sets the sets the virtual number of items to be displayed.
+ ///
+ [Category("Behavior")]
+ public int ItemCount {
+ get {
+ return itemCount;
+ }
+ set {
+ itemCount = value;
+ // if the virtual item count is set before the handle is created
+ // then the image lists don't get loaded properly
+ if(!this.IsHandleCreated)
+ return;
+ SetVirtualItemCount();
+ }
+ }
+
+ ///
+ /// Gets/sets how list items are to be displayed.
+ ///
+ /// Hide the ListView.View property.
+ /// Virtual Listviews only allow Details or List.
+ ///
+ public new System.Windows.Forms.View View {
+ get {
+ return base.View;
+ }
+ set {
+ if(value == View.LargeIcon ||
+ value == View.SmallIcon) {
+ throw new ArgumentException("Icon views are invalid for virtual ListViews", "View");
+ }
+ base.View = value;
+ }
+ }
+
+ ///
+ /// Gets the required creation parameters when the control handle is created.
+ ///
+ /// Use LVS_OWNERDATA to set this as a virtual Listview.
+ ///
+ protected override System.Windows.Forms.CreateParams CreateParams {
+ get {
+ CreateParams cp = base.CreateParams;
+ // LVS_OWNERDATA style must be set when the control is created
+ cp.Style |= (int)ListViewStyles.LVS_OWNERDATA;
+ return cp;
+ }
+ }
+
+ #endregion
+
+ #region Constructors
+ ///
+ /// Create a new instance of this control.
+ ///
+ public VirtualListView() {
+ // virtual listviews must be Details or List view with no sorting
+ View = View.Details;
+ Sorting = SortOrder.None;
+
+ ptrlvhti = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(LVHITTESTINFO)));
+
+ SetStyle(ControlStyles.DoubleBuffer, true);
+ SetStyle(ControlStyles.Opaque, true);
+ }
+
+ ~VirtualListView() {
+ Marshal.FreeHGlobal(ptrlvhti);
+ }
+
+ #endregion
+
+ #region Methods
+ ///
+ /// Set the state of the passed Listview item's index.
+ ///
+ /// Listview item's index.
+ /// Select the passed item?
+ public void SelectItem(int index, bool selected) {
+ IntPtr ptrItem = IntPtr.Zero;
+
+ try {
+ // Determine whether selecting or unselecting.
+ uint select = selected ? (uint)ListViewCallBackMask.LVIS_SELECTED : 0;
+
+ // Fill in the LVITEM structure with state fields.
+ LVITEM stateItem = new LVITEM();
+ stateItem.mask = (uint)ListViewItemMask.LVIF_STATE;
+ stateItem.iItem = index;
+ stateItem.iSubItem = 0;
+ stateItem.state = select;
+ stateItem.stateMask = (uint)ListViewCallBackMask.LVIS_SELECTED;
+
+ // Copy the structure to unmanaged memory.
+ ptrItem = Marshal.AllocHGlobal(Marshal.SizeOf(stateItem.GetType()));
+ Marshal.StructureToPtr(stateItem, ptrItem, true);
+
+ // Send the message to the control window.
+ int result = WindowsFunction.SendMessage(
+ this.Handle,
+ (int)ListViewMessages.LVM_SETITEMSTATE,
+ index,
+ ptrItem.ToInt32());
+ } catch(Exception ex) {
+ System.Diagnostics.Trace.WriteLine("VirtualListView.SetItemState error=" + ex.Message);
+ // TODO: should this eat any exceptions?
+ throw ex;
+ } finally {
+ // Always release the unmanaged memory.
+ if(ptrItem != IntPtr.Zero) {
+ Marshal.FreeHGlobal(ptrItem);
+ }
+ }
+ }
+
+ private void SetVirtualItemCount() {
+ int result;
+ result = WindowsFunction.SendMessage(
+ this.Handle,
+ (int)ListViewMessages.LVM_SETITEMCOUNT,
+ itemCount,
+ 0);
+ }
+
+ protected void OnDispInfoNotice(ref Message m, bool useAnsi) {
+
+ LVDISPINFO info = (LVDISPINFO)m.GetLParam(typeof(LVDISPINFO));
+ string lvtext = null;
+
+ if((info.item.mask & (uint)ListViewItemMask.LVIF_TEXT) > 0) {
+ if(QueryItemText != null) {
+ QueryItemText(info.item.iItem, info.item.iSubItem, out lvtext);
+ if(lvtext != null) {
+ try {
+ int maxIndex = Math.Min(info.item.cchTextMax - 1, lvtext.Length);
+ char[] data = new char[maxIndex + 1];
+ lvtext.CopyTo(0, data, 0, lvtext.Length);
+ data[maxIndex] = '\0';
+ System.Runtime.InteropServices.Marshal.Copy(data, 0, info.item.pszText, data.Length);
+ } catch(Exception e) {
+ Debug.WriteLine("Failed to copy text name from client: " + e.ToString(), "VirtualListView.OnDispInfoNotice");
+ }
+ }
+ }
+ }
+
+ if((info.item.mask & (uint)ListViewItemMask.LVIF_IMAGE) > 0) {
+ int imageIndex = 0;
+ if(QueryItemImage != null) {
+ QueryItemImage(info.item.iItem, info.item.iSubItem, out imageIndex);
+ }
+ info.item.iImage = imageIndex;
+ System.Runtime.InteropServices.Marshal.StructureToPtr(info, m.LParam, false);
+ }
+
+ if((info.item.mask & (uint)ListViewItemMask.LVIF_INDENT) > 0) {
+ int itemIndent = 0;
+ if(QueryItemIndent != null) {
+ QueryItemIndent(info.item.iItem, out itemIndent);
+ }
+ info.item.iIndent = itemIndent;
+ System.Runtime.InteropServices.Marshal.StructureToPtr(info, m.LParam, false);
+ }
+ m.Result = new IntPtr(0);
+ }
+
+ protected void OnCustomDrawNotice(ref System.Windows.Forms.Message m) {
+ NMLVCUSTOMDRAW cd = (NMLVCUSTOMDRAW)m.GetLParam(typeof(NMLVCUSTOMDRAW));
+ switch(cd.nmcd.dwDrawStage) {
+ case (int)CUSTOMDRAWDRAWSTAGEFLAGS.CDDS_ITEMPREPAINT:
+ case (int)CUSTOMDRAWDRAWSTAGEFLAGS.CDDS_PREPAINT:
+ m.Result = new IntPtr((int)CUSTOMDRAWRETURNFLAGS.CDRF_NOTIFYSUBITEMDRAW);
+ break;
+ case (int)CUSTOMDRAWDRAWSTAGEFLAGS.CDDS_SUBITEMPREPAINT:
+ Color color;
+ if(QueryItemBkColor != null) {
+ color = Color.FromArgb(cd.clrTextBk & 0xFF, (cd.clrTextBk >> 8) & 0xFF, (cd.clrTextBk >> 16) & 0xFF);
+ QueryItemBkColor(cd.nmcd.dwItemSpec, cd.iSubItem, ref color);
+ cd.clrTextBk = (color.B << 16) | (color.G << 8) | (color.R);
+ System.Runtime.InteropServices.Marshal.StructureToPtr(cd, m.LParam, false);
+ }
+ m.Result = new IntPtr((int)CUSTOMDRAWRETURNFLAGS.CDRF_DODEFAULT);
+ break;
+ }
+ }
+
+ protected override void WndProc(ref System.Windows.Forms.Message m) {
+ NMHDR nm1;
+ bool messageProcessed = false;
+ switch(m.Msg) {
+ case (int)WindowsMessage.WM_REFLECT + (int)WindowsMessage.WM_NOTIFY:
+ nm1 = (NMHDR)m.GetLParam(typeof(NMHDR));
+ switch(nm1.code) {
+ case (int)Notices.NM_CUSTOMDRAW:
+ OnCustomDrawNotice(ref m);
+ messageProcessed = true;
+ break;
+ case (int)ListViewNotices.LVN_GETDISPINFOW:
+ OnDispInfoNotice(ref m, false);
+ messageProcessed = true;
+ break;
+ case (int)ListViewNotices.LVN_BEGINDRAG:
+ OnBeginItemDrag(MouseButtons.Left, ref m);
+ messageProcessed = true;
+ break;
+ case (int)ListViewNotices.LVN_BEGINRDRAG:
+ OnBeginItemDrag(MouseButtons.Right, ref m);
+ messageProcessed = true;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ if(!messageProcessed) {
+ try {
+ base.WndProc(ref m);
+ } catch(Exception ex) {
+ Trace.WriteLine(string.Format("Message {0} caused an exception: {1}", m, ex.Message));
+ }
+ }
+ }
+
+ protected ListViewItem GetItem(int idx) {
+ ListViewItem item = null;
+ if(QueryItem != null) {
+ QueryItem(idx, out item);
+ }
+ if(item == null) {
+ throw new ArgumentException("cannot find item " + idx.ToString() + " via QueryItem event");
+ }
+ return item;
+ }
+
+ protected void OnBeginItemDrag(MouseButtons mouseButton, ref System.Windows.Forms.Message m) {
+ NMLISTVIEW info = (NMLISTVIEW)m.GetLParam(typeof(NMLISTVIEW));
+ ListViewItem item = null;
+ if(QueryItem != null) {
+ QueryItem(info.iItem, out item);
+ }
+ OnItemDrag(new ItemDragEventArgs(mouseButton, item));
+ }
+
+ protected override void OnHandleCreated(System.EventArgs e) {
+ base.OnHandleCreated(e);
+ // ensure the value for ItemCount is sent to the control properly if the user set it
+ // before the handle was created
+ SetVirtualItemCount();
+ }
+
+ protected override void OnHandleDestroyed(System.EventArgs e) {
+ // the ListView OnHandleDestroyed accesses the Items list for all selected items
+ ItemCount = 0;
+ base.OnHandleDestroyed(e);
+ }
+ #endregion
+
+ LVHITTESTINFO lvhti = new LVHITTESTINFO();
+ IntPtr ptrlvhti;
+ int selection = -1;
+
+ public int hitTest(int x, int y) {
+ lvhti.pt.x = x;
+ lvhti.pt.y = y;
+ Marshal.StructureToPtr(lvhti, ptrlvhti, true);
+ int z = WindowsFunction.SendMessage(this.Handle, (int)ListViewMessages.LVM_HITTEST, 0, ptrlvhti.ToInt32());
+ Marshal.PtrToStructure(ptrlvhti, lvhti);
+ return z;
+ }
+
+ public void ensureVisible(int index) {
+ WindowsFunction.SendMessage(Handle, (int)ListViewMessages.LVM_ENSUREVISIBLE, index, 1);
+ }
+
+ public void ensureVisible() {
+ ensureVisible(selectedItem);
+ }
+
+ public void setSelection(int index) {
+ clearSelection();
+ selection = index;
+ SelectItem(selection, true);
+ }
+
+ public int selectedItem { get { if(SelectedIndices.Count == 0) return -1; else return SelectedIndices[0]; } set { setSelection(value); } }
+
+ public void clearSelection() {
+ if(selection != -1)
+ SelectItem(selection, false);
+ selection = -1;
+ }
+ }
+}