added TN_FLAG_ITEM_DRAG_SELECT

git-svn-id: svn://svn.code.sf.net/p/processhacker/code@4294 21ef857c-d57f-4fe0-8362-d861dc6d29cd
This commit is contained in:
wj32
2011-07-05 07:23:36 +00:00
parent 27b34f8dbc
commit 971c9d8a36
4 changed files with 272 additions and 80 deletions
+1
View File
@@ -88,6 +88,7 @@ VOID PhInitializeProcessTreeList(
ProcessTreeListHandle = hwnd;
PhSetControlTheme(ProcessTreeListHandle, L"explorer");
TreeNew_SetExtendedFlags(hwnd, TN_FLAG_ITEM_DRAG_SELECT, TN_FLAG_ITEM_DRAG_SELECT);
SendMessage(TreeNew_GetTooltips(ProcessTreeListHandle), TTM_SETMAXTIPWIDTH, 0, MAXSHORT);
SendMessage(TreeNew_GetTooltips(ProcessTreeListHandle), TTM_SETDELAYTIME, TTDT_AUTOPOP, 0x7fff);
+8 -1
View File
@@ -91,6 +91,9 @@ typedef struct _PH_TREENEW_NODE
#define TN_STYLE_NO_DIVIDER 0x4
#define TN_STYLE_ANIMATE_DIVIDER 0x8
// Extended flags
#define TN_FLAG_ITEM_DRAG_SELECT 0x1
// Callback flags
#define TN_CACHE 0x1
#define TN_AUTO_FORECOLOR 0x1000
@@ -344,7 +347,8 @@ typedef struct _PH_TREENEW_SEARCH_EVENT
#define TNM_SETFOCUSNODE (WM_USER + 35)
#define TNM_SETMARKNODE (WM_USER + 36)
#define TNM_SETHOTNODE (WM_USER + 37)
#define TNM_LAST (WM_USER + 37)
#define TNM_SETEXTENDEDFLAGS (WM_USER + 38)
#define TNM_LAST (WM_USER + 38)
#define TreeNew_SetCallback(hWnd, Callback, Context) \
SendMessage((hWnd), TNM_SETCALLBACK, (WPARAM)(Context), (LPARAM)(Callback))
@@ -451,6 +455,9 @@ typedef struct _PH_TREENEW_SEARCH_EVENT
#define TreeNew_SetHotNode(hWnd, Node) \
SendMessage((hWnd), TNM_SETHOTNODE, 0, (LPARAM)(Node))
#define TreeNew_SetExtendedFlags(hWnd, Mask, Value) \
SendMessage((hWnd), TNM_SETEXTENDEDFLAGS, (WPARAM)(Mask), (LPARAM)(Value))
typedef struct _PH_TREENEW_VIEW_PARTS
{
RECT ClientRect;
+21 -1
View File
@@ -64,6 +64,7 @@ typedef struct _PH_TREENEW_CONTEXT
};
ULONG Style;
ULONG ExtendedStyle;
ULONG ExtendedFlags;
HFONT Font;
HCURSOR Cursor;
@@ -343,6 +344,16 @@ VOID PhTnpSetRedraw(
__in BOOLEAN Redraw
);
VOID PhTnpSendMouseEvent(
__in PPH_TREENEW_CONTEXT Context,
__in PH_TREENEW_MESSAGE Message,
__in LONG CursorX,
__in LONG CursorY,
__in PPH_TREENEW_NODE Node,
__in PPH_TREENEW_COLUMN Column,
__in ULONG VirtualKeys
);
// Columns
PPH_TREENEW_COLUMN PhTnpLookupColumnById(
@@ -493,6 +504,14 @@ VOID PhTnpSetHotNode(
__in BOOLEAN NewPlusMinusHot
);
VOID PhTnpProcessSelectNode(
__in PPH_TREENEW_CONTEXT Context,
__in PPH_TREENEW_NODE Node,
__in LOGICAL ControlKey,
__in LOGICAL ShiftKey,
__in LOGICAL RightButton
);
BOOLEAN PhTnpEnsureVisibleNode(
__in PPH_TREENEW_CONTEXT Context,
__in ULONG Index
@@ -643,7 +662,8 @@ BOOLEAN PhTnpDetectDrag(
__in PPH_TREENEW_CONTEXT Context,
__in LONG CursorX,
__in LONG CursorY,
__in BOOLEAN DispatchMessages
__in BOOLEAN DispatchMessages,
__out_opt PULONG CancelledByMessage
);
VOID PhTnpDragSelect(
+242 -78
View File
@@ -794,100 +794,155 @@ VOID PhTnpOnXxxButtonXxx(
if (!(hitTest.Flags & TN_HIT_ITEM_PLUSMINUS) && (Message == WM_LBUTTONDOWN || Message == WM_RBUTTONDOWN))
{
LOGICAL realHitItem;
PH_TREENEW_CELL_PARTS parts;
PhTnpPopTooltip(Context);
if (hitTest.Flags & TN_HIT_ITEM)
{
Context->FocusNode = hitTest.Node;
}
if (Message == WM_RBUTTONDOWN)
realHitItem = hitTest.Flags & TN_HIT_ITEM;
if (realHitItem && (Context->ExtendedFlags & TN_FLAG_ITEM_DRAG_SELECT))
{
// To allow drag selection to begin even if the cursor is on an item,
// we check if the cursor is on the item icon or text. If it isn't, then
// don't count that as a hit. The exception is when the item is already
// selected - we always count that as a hit. Also, we always count it as
// a hit if user is beginning to drag the divider.
if (!hitTest.Node->Selected && !startingTracking)
{
// Right button:
// If the current node is selected, then do nothing. This is to allow context
// menus to operate on multiple items.
// If the current node is not selected, select only that node.
if (!controlKey && !shiftKey && !hitTest.Node->Selected)
if (PhTnpGetCellParts(Context, hitTest.Node->Index, hitTest.Column, TN_MEASURE_TEXT, &parts))
{
PhTnpSelectRange(Context, hitTest.Node->Index, hitTest.Node->Index, TN_SELECT_RESET, &changedStart, &changedEnd);
Context->MarkNodeIndex = hitTest.Node->Index;
realHitItem = FALSE;
if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
if ((parts.Flags & TN_PART_ICON) && CursorX >= parts.IconRect.left && CursorX < parts.IconRect.right)
realHitItem = TRUE;
if (parts.Flags & TN_PART_TEXT)
{
InvalidateRect(hwnd, &rect, FALSE);
if (!(hitTest.Column->TextFlags & (DT_CENTER | DT_RIGHT)))
{
if (CursorX >= parts.TextRect.left && CursorX < parts.TextRect.right)
realHitItem = TRUE;
}
else if (parts.Flags & TN_PART_CONTENT)
{
// PhTnpGetCellParts doesn't take into account text alignment. If there's text and
// the cursor is in the content part, it's a hit.
if (parts.Text.Length != 0 && CursorX >= parts.ContentRect.left && CursorX < parts.ContentRect.right)
realHitItem = TRUE;
}
}
}
}
else if (shiftKey && Context->MarkNodeIndex != -1)
{
ULONG start;
ULONG end;
}
// Shift key: select a range from the selection mark node to the current node.
if (hitTest.Node->Index > Context->MarkNodeIndex)
{
start = Context->MarkNodeIndex;
end = hitTest.Node->Index;
}
else
{
start = hitTest.Node->Index;
end = Context->MarkNodeIndex;
}
PhTnpSelectRange(Context, start, end, TN_SELECT_RESET, &changedStart, &changedEnd);
if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
{
InvalidateRect(hwnd, &rect, FALSE);
}
}
else if (controlKey)
{
// Control key: toggle the selection on the current node, and also make it the selection mark.
PhTnpSelectRange(Context, hitTest.Node->Index, hitTest.Node->Index, TN_SELECT_TOGGLE, NULL, NULL);
Context->MarkNodeIndex = hitTest.Node->Index;
if (PhTnpGetRowRects(Context, hitTest.Node->Index, hitTest.Node->Index, TRUE, &rect))
{
InvalidateRect(hwnd, &rect, FALSE);
}
}
else
{
// Normal: select the current node, and also make it the selection mark.
PhTnpSelectRange(Context, hitTest.Node->Index, hitTest.Node->Index, TN_SELECT_RESET, &changedStart, &changedEnd);
Context->MarkNodeIndex = hitTest.Node->Index;
if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
{
InvalidateRect(hwnd, &rect, FALSE);
}
}
PhTnpPopTooltip(Context);
if (realHitItem)
{
PhTnpProcessSelectNode(Context, hitTest.Node, controlKey, shiftKey, Message == WM_RBUTTONDOWN);
}
else
{
BOOLEAN dragSelect;
ULONG indexToSelect;
BOOLEAN selectionProcessed;
dragSelect = FALSE;
indexToSelect = -1;
selectionProcessed = FALSE;
if (!(hitTest.Flags & (TN_HIT_LEFT | TN_HIT_RIGHT | TN_HIT_ABOVE | TN_HIT_BELOW)))
{
// Check for drag selection.
if (PhTnpDetectDrag(Context, CursorX, CursorY, TRUE))
BOOLEAN result;
ULONG saveIndex;
ULONG saveId;
ULONG cancelledByMessage;
// Check for drag selection. PhTnpDetectDrag has its own message loop, so we need to
// clear our pointers before we continue or we will have some access violations when
// items get deleted.
if (hitTest.Node)
saveIndex = hitTest.Node->Index;
else
saveIndex = -1;
if (hitTest.Column)
saveId = hitTest.Column->Id;
else
saveId = -1;
result = PhTnpDetectDrag(Context, CursorX, CursorY, TRUE, &cancelledByMessage);
// Restore the pointers.
if (saveIndex == -1)
hitTest.Node = NULL;
else if (saveIndex < Context->FlatList->Count)
hitTest.Node = Context->FlatList->Items[saveIndex];
else
return;
if (saveId != -1 && !(hitTest.Column = PhTnpLookupColumnById(Context, saveId)))
return;
if (result)
{
dragSelect = TRUE;
if ((hitTest.Flags & TN_HIT_ITEM) && (Context->ExtendedFlags & TN_FLAG_ITEM_DRAG_SELECT))
{
// Include the current node before starting the drag selection, otherwise the user
// will never be able to select the current node.
indexToSelect = hitTest.Node->Index;
}
}
else
{
if ((Message == WM_LBUTTONDOWN && cancelledByMessage == WM_LBUTTONUP) ||
(Message == WM_RBUTTONDOWN && cancelledByMessage == WM_RBUTTONUP))
{
POINT point;
if ((hitTest.Flags & TN_HIT_ITEM) && (Context->ExtendedFlags & TN_FLAG_ITEM_DRAG_SELECT))
{
// The user isn't performing a drag selection, but we didn't count this as a hit earlier.
// It's a hit now.
PhTnpProcessSelectNode(Context, hitTest.Node, controlKey, shiftKey, Message == WM_RBUTTONDOWN);
selectionProcessed = TRUE;
}
// The button up message gets consumed by PhTnpDetectDrag, so send the mouse event here.
// Check if the cursor stayed in the same place.
PhTnpGetMessagePos(Context->Handle, &point);
if (point.x == CursorX && point.y == CursorY)
{
PhTnpSendMouseEvent(
Context,
Message == WM_LBUTTONDOWN ? TreeNewLeftClick : TreeNewRightClick,
CursorX,
CursorY,
hitTest.Node,
hitTest.Column,
VirtualKeys
);
}
}
}
}
if (!controlKey && !shiftKey)
if (!selectionProcessed && !controlKey && !shiftKey)
{
// Nothing: deselect everything.
PhTnpSelectRange(Context, -1, -1, TN_SELECT_RESET, &changedStart, &changedEnd);
PhTnpSelectRange(Context, indexToSelect, indexToSelect, TN_SELECT_RESET, &changedStart, &changedEnd);
if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
{
@@ -899,10 +954,15 @@ VOID PhTnpOnXxxButtonXxx(
{
PhTnpDragSelect(Context, CursorX, CursorY);
}
return;
}
}
// Click, double-click
// Note: If TN_FLAG_ITEM_DRAG_SELECT is enabled, the code below that processes WM_xBUTTONDOWN
// and WM_xBUTTONUP messages only takes effect when the user clicks directly on an item's icon
// or text.
clickMessage = -1;
@@ -947,17 +1007,8 @@ VOID PhTnpOnXxxButtonXxx(
if (!(hitTest.Flags & TN_HIT_ITEM_PLUSMINUS) && clickMessage != -1)
{
PH_TREENEW_MOUSE_EVENT mouseEvent;
mouseEvent.Location.x = CursorX;
mouseEvent.Location.y = CursorY;
mouseEvent.Node = hitTest.Node;
mouseEvent.Column = hitTest.Column;
mouseEvent.KeyFlags = VirtualKeys;
Context->Callback(Context->Handle, clickMessage, &mouseEvent, NULL, Context->CallbackContext);
PhTnpSendMouseEvent(Context, clickMessage, CursorX, CursorY, hitTest.Node, hitTest.Column, VirtualKeys);
}
// TODO: Drag selection
}
VOID PhTnpOnCaptureChanged(
@@ -1698,6 +1749,10 @@ ULONG_PTR PhTnpOnUserMessage(
case TNM_SETHOTNODE:
PhTnpSetHotNode(Context, (PPH_TREENEW_NODE)LParam, FALSE);
return TRUE;
case TNM_SETEXTENDEDFLAGS:
Context->ExtendedFlags &= ~(ULONG)WParam;
Context->ExtendedFlags |= (ULONG)LParam;
return TRUE;
}
return 0;
@@ -2003,6 +2058,26 @@ VOID PhTnpSetRedraw(
}
}
VOID PhTnpSendMouseEvent(
__in PPH_TREENEW_CONTEXT Context,
__in PH_TREENEW_MESSAGE Message,
__in LONG CursorX,
__in LONG CursorY,
__in PPH_TREENEW_NODE Node,
__in PPH_TREENEW_COLUMN Column,
__in ULONG VirtualKeys
)
{
PH_TREENEW_MOUSE_EVENT mouseEvent;
mouseEvent.Location.x = CursorX;
mouseEvent.Location.y = CursorY;
mouseEvent.Node = Node;
mouseEvent.Column = Column;
mouseEvent.KeyFlags = VirtualKeys;
Context->Callback(Context->Handle, Message, &mouseEvent, NULL, Context->CallbackContext);
}
PPH_TREENEW_COLUMN PhTnpLookupColumnById(
__in PPH_TREENEW_CONTEXT Context,
__in ULONG Id
@@ -3347,6 +3422,87 @@ VOID PhTnpSetHotNode(
}
}
VOID PhTnpProcessSelectNode(
__in PPH_TREENEW_CONTEXT Context,
__in PPH_TREENEW_NODE Node,
__in LOGICAL ControlKey,
__in LOGICAL ShiftKey,
__in LOGICAL RightButton
)
{
ULONG changedStart;
ULONG changedEnd;
RECT rect;
if (RightButton)
{
// Right button:
// If the current node is selected, then do nothing. This is to allow context
// menus to operate on multiple items.
// If the current node is not selected, select only that node.
if (!ControlKey && !ShiftKey && !Node->Selected)
{
PhTnpSelectRange(Context, Node->Index, Node->Index, TN_SELECT_RESET, &changedStart, &changedEnd);
Context->MarkNodeIndex = Node->Index;
if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
{
InvalidateRect(Context->Handle, &rect, FALSE);
}
}
}
else if (ShiftKey && Context->MarkNodeIndex != -1)
{
ULONG start;
ULONG end;
// Shift key: select a range from the selection mark node to the current node.
if (Node->Index > Context->MarkNodeIndex)
{
start = Context->MarkNodeIndex;
end = Node->Index;
}
else
{
start = Node->Index;
end = Context->MarkNodeIndex;
}
PhTnpSelectRange(Context, start, end, TN_SELECT_RESET, &changedStart, &changedEnd);
if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
{
InvalidateRect(Context->Handle, &rect, FALSE);
}
}
else if (ControlKey)
{
// Control key: toggle the selection on the current node, and also make it the selection mark.
PhTnpSelectRange(Context, Node->Index, Node->Index, TN_SELECT_TOGGLE, NULL, NULL);
Context->MarkNodeIndex = Node->Index;
if (PhTnpGetRowRects(Context, Node->Index, Node->Index, TRUE, &rect))
{
InvalidateRect(Context->Handle, &rect, FALSE);
}
}
else
{
// Normal: select the current node, and also make it the selection mark.
PhTnpSelectRange(Context, Node->Index, Node->Index, TN_SELECT_RESET, &changedStart, &changedEnd);
Context->MarkNodeIndex = Node->Index;
if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
{
InvalidateRect(Context->Handle, &rect, FALSE);
}
}
}
BOOLEAN PhTnpEnsureVisibleNode(
__in PPH_TREENEW_CONTEXT Context,
__in ULONG Index
@@ -5316,7 +5472,8 @@ BOOLEAN PhTnpDetectDrag(
__in PPH_TREENEW_CONTEXT Context,
__in LONG CursorX,
__in LONG CursorY,
__in BOOLEAN DispatchMessages
__in BOOLEAN DispatchMessages,
__out_opt PULONG CancelledByMessage
)
{
RECT dragRect;
@@ -5332,6 +5489,9 @@ BOOLEAN PhTnpDetectDrag(
SetCapture(Context->Handle);
if (CancelledByMessage)
*CancelledByMessage = 0;
do
{
// It seems that GetMessage dispatches nonqueued messages directly from kernel-mode, so
@@ -5348,7 +5508,11 @@ BOOLEAN PhTnpDetectDrag(
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
ReleaseCapture();
return FALSE;
if (CancelledByMessage)
*CancelledByMessage = msg.message;
break;
case WM_MOUSEMOVE:
if (msg.pt.x < dragRect.left || msg.pt.x >= dragRect.right ||
msg.pt.y < dragRect.top || msg.pt.y >= dragRect.bottom)
@@ -5575,7 +5739,7 @@ VOID PhTnpDragSelect(
ULONG changedEnd;
RECT rect;
PhTnpSelectRange(Context, -1, 0, TN_SELECT_RESET, &changedStart, &changedEnd);
PhTnpSelectRange(Context, -1, -1, TN_SELECT_RESET, &changedStart, &changedEnd);
if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
{