mirror of
https://github.com/ShareX/ShareX.git
synced 2024-08-19 04:02:07 +12:00
499 lines
No EOL
15 KiB
C#
499 lines
No EOL
15 KiB
C#
#region License Information (GPL v3)
|
|
|
|
/*
|
|
ShareX - A program that allows you to take screenshots and share any file type
|
|
Copyright (c) 2007-2017 ShareX Team
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
Optionally you can also view the license at <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#endregion License Information (GPL v3)
|
|
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Runtime.InteropServices;
|
|
using System.Windows.Forms;
|
|
|
|
namespace ShareX.UploadersLib
|
|
{
|
|
/// <summary>
|
|
/// Inherited ListView to allow in-place editing of subitems
|
|
/// </summary>
|
|
public class ListViewEx : ListView
|
|
{
|
|
#region Interop structs, imports and constants
|
|
|
|
/// <summary>
|
|
/// MessageHeader for WM_NOTIFY
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct NMHDR
|
|
{
|
|
public IntPtr hwndFrom;
|
|
public Int32 idFrom;
|
|
public Int32 code;
|
|
}
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wPar, IntPtr lPar);
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Ansi)]
|
|
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, int len, ref int[] order);
|
|
|
|
// ListView messages
|
|
private const int LVM_FIRST = 0x1000;
|
|
private const int LVM_GETCOLUMNORDERARRAY = LVM_FIRST + 59;
|
|
|
|
// Windows Messages that will abort editing
|
|
private const int WM_HSCROLL = 0x114;
|
|
private const int WM_VSCROLL = 0x115;
|
|
private const int WM_SIZE = 0x05;
|
|
private const int WM_NOTIFY = 0x4E;
|
|
|
|
private const int HDN_FIRST = -300;
|
|
private const int HDN_BEGINDRAG = HDN_FIRST - 10;
|
|
private const int HDN_ITEMCHANGINGA = HDN_FIRST - 0;
|
|
private const int HDN_ITEMCHANGINGW = HDN_FIRST - 20;
|
|
|
|
#endregion Interop structs, imports and constants
|
|
|
|
/// <summary>
|
|
/// Required designer variable.
|
|
/// </summary>
|
|
private Container components;
|
|
|
|
public event SubItemEventHandler SubItemClicked;
|
|
public event SubItemEventHandler SubItemBeginEditing;
|
|
public event SubItemEndEditingEventHandler SubItemEndEditing;
|
|
|
|
public ListViewEx()
|
|
{
|
|
DoubleClickActivation = false;
|
|
// This call is required by the Windows.Forms Form Designer.
|
|
InitializeComponent();
|
|
|
|
FullRowSelect = true;
|
|
View = View.Details;
|
|
AllowColumnReorder = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clean up any resources being used.
|
|
/// </summary>
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
if (components != null)
|
|
components.Dispose();
|
|
}
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
#region Component Designer generated code
|
|
|
|
/// <summary>
|
|
/// Required method for Designer support - do not modify
|
|
/// the contents of this method with the code editor.
|
|
/// </summary>
|
|
private void InitializeComponent()
|
|
{
|
|
components = new Container();
|
|
}
|
|
|
|
#endregion Component Designer generated code
|
|
|
|
/// <summary>
|
|
/// Is a double click required to start editing a cell?
|
|
/// </summary>
|
|
public bool DoubleClickActivation { get; set; }
|
|
|
|
/// <summary>
|
|
/// Retrieve the order in which columns appear
|
|
/// </summary>
|
|
/// <returns>Current display order of column indices</returns>
|
|
public int[] GetColumnOrder()
|
|
{
|
|
IntPtr lPar = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(int)) * Columns.Count);
|
|
|
|
IntPtr res = SendMessage(Handle, LVM_GETCOLUMNORDERARRAY, new IntPtr(Columns.Count), lPar);
|
|
if (res.ToInt32() == 0) // Something went wrong
|
|
{
|
|
Marshal.FreeHGlobal(lPar);
|
|
return null;
|
|
}
|
|
|
|
int[] order = new int[Columns.Count];
|
|
Marshal.Copy(lPar, order, 0, Columns.Count);
|
|
|
|
Marshal.FreeHGlobal(lPar);
|
|
|
|
return order;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find ListViewItem and SubItem Index at position (x,y)
|
|
/// </summary>
|
|
/// <param name="x">relative to ListView</param>
|
|
/// <param name="y">relative to ListView</param>
|
|
/// <param name="item">Item at position (x,y)</param>
|
|
/// <returns>SubItem index</returns>
|
|
public int GetSubItemAt(int x, int y, out ListViewItem item)
|
|
{
|
|
item = GetItemAt(x, y);
|
|
|
|
if (item != null)
|
|
{
|
|
int[] order = GetColumnOrder();
|
|
|
|
Rectangle lviBounds = item.GetBounds(ItemBoundsPortion.Entire);
|
|
int subItemX = lviBounds.Left;
|
|
foreach (int t in order)
|
|
{
|
|
ColumnHeader h = Columns[t];
|
|
if (x < subItemX + h.Width)
|
|
{
|
|
return h.Index;
|
|
}
|
|
subItemX += h.Width;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get bounds for a SubItem
|
|
/// </summary>
|
|
/// <param name="Item">Target ListViewItem</param>
|
|
/// <param name="SubItem">Target SubItem index</param>
|
|
/// <returns>Bounds of SubItem (relative to ListView)</returns>
|
|
public Rectangle GetSubItemBounds(ListViewItem Item, int SubItem)
|
|
{
|
|
int[] order = GetColumnOrder();
|
|
|
|
if (SubItem >= order.Length)
|
|
throw new IndexOutOfRangeException("SubItem " + SubItem + " out of range");
|
|
|
|
if (Item == null)
|
|
throw new ArgumentNullException("Item");
|
|
|
|
Rectangle lviBounds = Item.GetBounds(ItemBoundsPortion.Entire);
|
|
int subItemX = lviBounds.Left;
|
|
|
|
ColumnHeader col;
|
|
int i;
|
|
for (i = 0; i < order.Length; i++)
|
|
{
|
|
col = Columns[order[i]];
|
|
if (col.Index == SubItem)
|
|
break;
|
|
subItemX += col.Width;
|
|
}
|
|
Rectangle subItemRect = new Rectangle(subItemX, lviBounds.Top, Columns[order[i]].Width, lviBounds.Height);
|
|
return subItemRect;
|
|
}
|
|
|
|
protected override void WndProc(ref Message msg)
|
|
{
|
|
switch (msg.Msg)
|
|
{
|
|
// Look for WM_VSCROLL,WM_HSCROLL or WM_SIZE messages.
|
|
case WM_VSCROLL:
|
|
case WM_HSCROLL:
|
|
case WM_SIZE:
|
|
EndEditing(false);
|
|
break;
|
|
case WM_NOTIFY:
|
|
// Look for WM_NOTIFY of events that might also change the
|
|
// editor's position/size: Column reordering or resizing
|
|
NMHDR h = (NMHDR)Marshal.PtrToStructure(msg.LParam, typeof(NMHDR));
|
|
if (h.code == HDN_BEGINDRAG ||
|
|
h.code == HDN_ITEMCHANGINGA ||
|
|
h.code == HDN_ITEMCHANGINGW)
|
|
EndEditing(false);
|
|
break;
|
|
}
|
|
|
|
base.WndProc(ref msg);
|
|
}
|
|
|
|
#region Initialize editing depending of DoubleClickActivation property
|
|
|
|
protected override void OnMouseUp(MouseEventArgs e)
|
|
{
|
|
base.OnMouseUp(e);
|
|
|
|
if (DoubleClickActivation)
|
|
{
|
|
return;
|
|
}
|
|
|
|
EditSubitemAt(new Point(e.X, e.Y));
|
|
}
|
|
|
|
protected override void OnDoubleClick(EventArgs e)
|
|
{
|
|
base.OnDoubleClick(e);
|
|
|
|
if (!DoubleClickActivation)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Point pt = PointToClient(Cursor.Position);
|
|
|
|
EditSubitemAt(pt);
|
|
}
|
|
|
|
///<summary>
|
|
/// Fire SubItemClicked
|
|
///</summary>
|
|
///<param name="p">Point of click/doubleclick</param>
|
|
private void EditSubitemAt(Point p)
|
|
{
|
|
ListViewItem item;
|
|
int idx = GetSubItemAt(p.X, p.Y, out item);
|
|
if (idx >= 0)
|
|
{
|
|
OnSubItemClicked(new SubItemEventArgs(item, idx));
|
|
}
|
|
}
|
|
|
|
#endregion Initialize editing depending of DoubleClickActivation property
|
|
|
|
#region In-place editing functions
|
|
|
|
// The control performing the actual editing
|
|
private Control _editingControl;
|
|
// The LVI being edited
|
|
private ListViewItem _editItem;
|
|
// The SubItem being edited
|
|
private int _editSubItem;
|
|
|
|
protected void OnSubItemBeginEditing(SubItemEventArgs e)
|
|
{
|
|
if (SubItemBeginEditing != null)
|
|
SubItemBeginEditing(this, e);
|
|
}
|
|
|
|
protected void OnSubItemEndEditing(SubItemEndEditingEventArgs e)
|
|
{
|
|
if (SubItemEndEditing != null)
|
|
SubItemEndEditing(this, e);
|
|
}
|
|
|
|
protected void OnSubItemClicked(SubItemEventArgs e)
|
|
{
|
|
if (SubItemClicked != null)
|
|
SubItemClicked(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begin in-place editing of given cell
|
|
/// </summary>
|
|
/// <param name="c">Control used as cell editor</param>
|
|
/// <param name="Item">ListViewItem to edit</param>
|
|
/// <param name="SubItem">SubItem index to edit</param>
|
|
public void StartEditing(Control c, ListViewItem Item, int SubItem)
|
|
{
|
|
OnSubItemBeginEditing(new SubItemEventArgs(Item, SubItem));
|
|
|
|
Rectangle rcSubItem = GetSubItemBounds(Item, SubItem);
|
|
|
|
if (rcSubItem.X < 0)
|
|
{
|
|
// Left edge of SubItem not visible - adjust rectangle position and width
|
|
rcSubItem.Width += rcSubItem.X;
|
|
rcSubItem.X = 0;
|
|
}
|
|
if (rcSubItem.X + rcSubItem.Width > Width)
|
|
{
|
|
// Right edge of SubItem not visible - adjust rectangle width
|
|
rcSubItem.Width = Width - rcSubItem.Left;
|
|
}
|
|
|
|
// Subitem bounds are relative to the location of the ListView!
|
|
rcSubItem.Offset(Left, Top);
|
|
|
|
// In case the editing control and the listview are on different parents,
|
|
// account for different origins
|
|
Point origin = new Point(0, 0);
|
|
Point lvOrigin = Parent.PointToScreen(origin);
|
|
Point ctlOrigin = c.Parent.PointToScreen(origin);
|
|
|
|
rcSubItem.Offset(lvOrigin.X - ctlOrigin.X, lvOrigin.Y - ctlOrigin.Y);
|
|
|
|
// Position and show editor
|
|
c.Bounds = rcSubItem;
|
|
c.Text = Item.SubItems[SubItem].Text;
|
|
c.Visible = true;
|
|
c.BringToFront();
|
|
c.Focus();
|
|
|
|
_editingControl = c;
|
|
_editingControl.Leave += _editControl_Leave;
|
|
_editingControl.KeyPress += _editControl_KeyPress;
|
|
|
|
_editItem = Item;
|
|
_editSubItem = SubItem;
|
|
}
|
|
|
|
private void _editControl_Leave(object sender, EventArgs e)
|
|
{
|
|
// cell editor losing focus
|
|
EndEditing(true);
|
|
}
|
|
|
|
private void _editControl_KeyPress(object sender, KeyPressEventArgs e)
|
|
{
|
|
switch (e.KeyChar)
|
|
{
|
|
case (char)(int)Keys.Escape:
|
|
{
|
|
EndEditing(false);
|
|
break;
|
|
}
|
|
|
|
case (char)(int)Keys.Enter:
|
|
{
|
|
EndEditing(true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accept or discard current value of cell editor control
|
|
/// </summary>
|
|
/// <param name="AcceptChanges">Use the _editingControl's Text as new SubItem text or discard changes?</param>
|
|
public void EndEditing(bool AcceptChanges)
|
|
{
|
|
if (_editingControl == null)
|
|
return;
|
|
|
|
SubItemEndEditingEventArgs e = new SubItemEndEditingEventArgs(
|
|
_editItem, // The item being edited
|
|
_editSubItem, // The subitem index being edited
|
|
AcceptChanges ?
|
|
_editingControl.Text : // Use editControl text if changes are accepted
|
|
_editItem.SubItems[_editSubItem].Text, // or the original subitem's text, if changes are discarded
|
|
!AcceptChanges // Cancel?
|
|
);
|
|
|
|
OnSubItemEndEditing(e);
|
|
|
|
if (!string.IsNullOrEmpty(e.DisplayText))
|
|
{
|
|
_editItem.SubItems[_editSubItem].Text = e.DisplayText;
|
|
}
|
|
|
|
_editingControl.Leave -= _editControl_Leave;
|
|
_editingControl.KeyPress -= _editControl_KeyPress;
|
|
|
|
_editingControl.Visible = false;
|
|
|
|
_editingControl = null;
|
|
_editItem = null;
|
|
_editSubItem = -1;
|
|
}
|
|
|
|
#endregion In-place editing functions
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event Handler for SubItem events
|
|
/// </summary>
|
|
public delegate void SubItemEventHandler(object sender, SubItemEventArgs e);
|
|
|
|
/// <summary>
|
|
/// Event Handler for SubItemEndEditing events
|
|
/// </summary>
|
|
public delegate void SubItemEndEditingEventHandler(object sender, SubItemEndEditingEventArgs e);
|
|
|
|
/// <summary>
|
|
/// Event Args for SubItemClicked event
|
|
/// </summary>
|
|
public class SubItemEventArgs : EventArgs
|
|
{
|
|
public SubItemEventArgs(ListViewItem item, int subItem)
|
|
{
|
|
_subItemIndex = subItem;
|
|
_item = item;
|
|
}
|
|
|
|
private int _subItemIndex = -1;
|
|
private ListViewItem _item;
|
|
|
|
public int SubItem
|
|
{
|
|
get
|
|
{
|
|
return _subItemIndex;
|
|
}
|
|
}
|
|
|
|
public ListViewItem Item
|
|
{
|
|
get
|
|
{
|
|
return _item;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event Args for SubItemEndEditingClicked event
|
|
/// </summary>
|
|
public class SubItemEndEditingEventArgs : SubItemEventArgs
|
|
{
|
|
private string _text = "";
|
|
private bool _cancel = true;
|
|
|
|
public SubItemEndEditingEventArgs(ListViewItem item, int subItem, string display, bool cancel) :
|
|
base(item, subItem)
|
|
{
|
|
_text = display;
|
|
_cancel = cancel;
|
|
}
|
|
|
|
public string DisplayText
|
|
{
|
|
get
|
|
{
|
|
return _text;
|
|
}
|
|
set
|
|
{
|
|
_text = value;
|
|
}
|
|
}
|
|
|
|
public bool Cancel
|
|
{
|
|
get
|
|
{
|
|
return _cancel;
|
|
}
|
|
set
|
|
{
|
|
_cancel = value;
|
|
}
|
|
}
|
|
}
|
|
} |