Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix ExListView group header collapse-expand button #5428

Merged
merged 1 commit into from
Sep 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions GitUI/GitUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@
<Compile Include="SpellChecker\WordAtCursorExtractor.cs" />
<Compile Include="TaskbarProgress.cs" />
<Compile Include="UserControls\RevisionGrid\CellStyle.cs" />
<Compile Include="UserControls\ListViewGroupHitInfo.cs" />
<Compile Include="UserControls\RevisionGrid\Graph\JunctionColorProvider.cs" />
<Compile Include="UserControls\RevisionGrid\Graph\JunctionStyler.cs" />
<Compile Include="UserControls\RevisionGrid\Columns\MultilineIndicator.cs" />
Expand Down
132 changes: 74 additions & 58 deletions GitUI/UserControls/ExListView.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
Expand Down Expand Up @@ -60,9 +61,14 @@ internal class ExListView : NativeListView
private static readonly PropertyInfo ListViewGroupIdProperty;

/// <summary>
/// Occurs when the user clicks a <see cref="ListViewGroup"/> within the list view control.
/// Occurs when the user presses mouse button in a <see cref="ListViewGroup"/> within the list view control.
/// </summary>
public event EventHandler<ListViewGroupMouseEventArgs> GroupMouseClick;
public event EventHandler<ListViewGroupMouseEventArgs> GroupMouseDown;

/// <summary>
/// Occurs when the user releases mouse button in a <see cref="ListViewGroup"/> within the list view control.
/// </summary>
public event EventHandler<ListViewGroupMouseEventArgs> GroupMouseUp;

static ExListView()
{
Expand Down Expand Up @@ -103,6 +109,9 @@ private static class NativeMethods
public const int LVM_SETGROUPINFO = LVM_FIRST + 147;
public const int LVM_SUBITEMHITTEST = LVM_FIRST + 57;

public const int NM_FIRST = 0;
public const int NM_CUSTOMDRAW = NM_FIRST - 12;

#endregion

[StructLayout(LayoutKind.Sequential)]
Expand Down Expand Up @@ -219,88 +228,95 @@ public struct LVGROUP

protected override void WndProc(ref Message m)
{
var message = m;
var groupHitInfo = new Lazy<ListViewGroupHitInfo>(() => GetGroupHitInfo(message));

switch (m.Msg)
{
case NativeMethods.WM_LBUTTONUP when groupHitInfo.Value?.IsCollapseButton == true:
DefWndProc(ref m); // collapse / expand by clicking button in group header
break;

case NativeMethods.WM_PAINT:
_isInWmPaintMsg = true;
base.WndProc(ref m);
_isInWmPaintMsg = false;
break;
case NativeMethods.WM_REFLECT_NOTIFY:
var nmhdr = (NativeMethods.NMHDR)m.GetLParam(typeof(NativeMethods.NMHDR));
if (nmhdr.code == -12)
{
// NM_CUSTOMDRAW
if (_isInWmPaintMsg)
{
base.WndProc(ref m);
}
}
else
{
base.WndProc(ref m);
}

case NativeMethods.WM_REFLECT_NOTIFY when IsCustomDraw(m) && !_isInWmPaintMsg:
case NativeMethods.WM_RBUTTONUP when IsGroupMouseEventHandled(groupHitInfo.Value, MouseButtons.Right, isDown: false):
case NativeMethods.WM_RBUTTONDOWN when IsGroupMouseEventHandled(groupHitInfo.Value, MouseButtons.Right, isDown: true):
case NativeMethods.WM_LBUTTONUP when IsGroupMouseEventHandled(groupHitInfo.Value, MouseButtons.Left, isDown: false):
case NativeMethods.WM_LBUTTONDOWN when IsGroupMouseEventHandled(groupHitInfo.Value, MouseButtons.Left, isDown: true):
break;
case NativeMethods.WM_LBUTTONUP:
case NativeMethods.WM_LBUTTONDOWN:
{
if (IsListViewGroupClickHandled((uint)m.LParam, MouseButtons.Left))
{
return;
}

base.WndProc(ref m);
break;
}

case NativeMethods.WM_RBUTTONUP:
case NativeMethods.WM_RBUTTONDOWN:
{
if (IsListViewGroupClickHandled((uint)m.LParam, MouseButtons.Right))
{
return;
}

base.WndProc(ref m);
break;
}

default:
base.WndProc(ref m);
break;
}

bool IsListViewGroupClickHandled(uint lparam, MouseButtons button)
bool IsGroupMouseEventHandled(ListViewGroupHitInfo hitInfo, MouseButtons button, bool isDown)
{
var info = new NativeMethods.LVHITTESTINFO
{
pt = NativeMethods.LParamToPOINT(lparam)
};

var handleRef = new HandleRef(this, Handle);
if (NativeMethods.SendMessage(handleRef, NativeMethods.LVM_SUBITEMHITTEST, (IntPtr)(-1), ref info) == new IntPtr(-1))
if (hitInfo == null)
{
return false;
}

if ((info.flags & NativeMethods.LVHITTESTFLAGS.LVHT_EX_GROUP_HEADER) == 0)
var eventArgs = new ListViewGroupMouseEventArgs(button, hitInfo, 1, 0);
if (isDown)
{
return false;
GroupMouseDown?.Invoke(this, eventArgs);
}

foreach (ListViewGroup group in Groups)
else
{
var groupId = GetGroupId(group);
if (info.iItem == groupId)
{
GroupMouseClick?.Invoke(this, new ListViewGroupMouseEventArgs(button, group, 1, info.pt.X, info.pt.Y, 0));
return true;
}
GroupMouseUp?.Invoke(this, eventArgs);
}

return false;
return eventArgs.Handled;
}

bool IsCustomDraw(Message msg)
{
var nmhdr = (NativeMethods.NMHDR)msg.GetLParam(typeof(NativeMethods.NMHDR));
return nmhdr.code == NativeMethods.NM_CUSTOMDRAW;
}
}

private ListViewGroupHitInfo GetGroupHitInfo(Message msg)
{
var point = NativeMethods.LParamToPOINT((uint)msg.LParam);
return GetGroupHitInfo(point);
}

private ListViewGroupHitInfo GetGroupHitInfo(NativeMethods.POINT location)
{
var info = new NativeMethods.LVHITTESTINFO
{
pt = location
};

var handleRef = new HandleRef(this, Handle);
if (NativeMethods.SendMessage(handleRef, NativeMethods.LVM_SUBITEMHITTEST, (IntPtr)(-1), ref info) == new IntPtr(-1))
{
return null;
}

if ((info.flags & NativeMethods.LVHITTESTFLAGS.LVHT_EX_GROUP_HEADER) == 0)
{
return null;
}

foreach (ListViewGroup group in Groups)
{
var groupId = GetGroupId(group);
if (info.iItem == groupId)
{
bool isCollapseButton = (info.flags & NativeMethods.LVHITTESTFLAGS.LVHT_EX_GROUP_COLLAPSE) > 0;
return new ListViewGroupHitInfo(group, isCollapseButton, new Point(location.X, location.Y));
}
}

return null;
}

private static int GetGroupId(ListViewGroup listViewGroup)
Expand Down
4 changes: 3 additions & 1 deletion GitUI/UserControls/FileStatusList.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions GitUI/UserControls/FileStatusList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,15 @@ private void FileStatusListView_MouseMove(object sender, MouseEventArgs e)
}
}

private void FileStatusListView_GroupMouseDown(object sender, ListViewGroupMouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
// prevent selecting all sub-items when left-clicking group
e.Handled = true;
}
}

public int UnfilteredItemsCount => GitItemStatusesWithParents?.Sum(tuple => tuple.statuses.Count) ?? 0;

public int AllItemsCount => FileStatusListView.Items.Count;
Expand Down
19 changes: 19 additions & 0 deletions GitUI/UserControls/ListViewGroupHitInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Drawing;
using System.Windows.Forms;

namespace GitUI.UserControls
{
public class ListViewGroupHitInfo
{
public ListViewGroupHitInfo(ListViewGroup @group, bool isCollapseButton, Point location)
{
Group = @group;
IsCollapseButton = isCollapseButton;
Location = location;
}

public ListViewGroup Group { get; }
public bool IsCollapseButton { get; }
public Point Location { get; }
}
}
9 changes: 5 additions & 4 deletions GitUI/UserControls/ListViewGroupMouseEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ namespace GitUI.UserControls
/// <inheritdoc />
internal class ListViewGroupMouseEventArgs : MouseEventArgs
{
public ListViewGroupMouseEventArgs(MouseButtons button, ListViewGroup group, int clicks, int x, int y, int delta)
: base(button, clicks, x, y, delta)
public ListViewGroupMouseEventArgs(MouseButtons button, ListViewGroupHitInfo groupHitInfo, int clicks, int delta)
: base(button, clicks, groupHitInfo.Location.X, groupHitInfo.Location.Y, delta)
{
Group = group;
GroupInfo = groupHitInfo;
}

public ListViewGroup Group { get; }
public ListViewGroupHitInfo GroupInfo { get; }
public bool Handled { get; set; }
}
}