Process Hacker
treenew.c
Go to the documentation of this file.
1 /*
2  * Process Hacker -
3  * tree new (tree list control)
4  *
5  * Copyright (C) 2011-2015 wj32
6  *
7  * This file is part of Process Hacker.
8  *
9  * Process Hacker is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Process Hacker is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /*
24  * The tree new is a tree view with columns. Unlike the old tree list control,
25  * which was a wrapper around the list view control, this control was written
26  * from scratch.
27  *
28  * Current issues not included in any comments:
29  * * Adding, removing or changing columns does not cause invalidation.
30  * * It is not possible to change a column to make it fixed. The current
31  * fixed column must be removed and the new fixed column must then be added.
32  * * When there are no visible normal columns, the space usually occupied by
33  * the normal column headers is filled with a solid background color. We
34  * should catch this and paint the usual themed background there instead.
35  * * It is not possible to update any TN_STYLE_* flags after the control is
36  * created.
37  *
38  * Possible additions:
39  * * More flexible mouse input callbacks to allow custom controls inside
40  * columns.
41  * * Allow custom drawn columns to customize their behaviour when
42  * TN_FLAG_ITEM_DRAG_SELECT is set (e.g. disable drag selection over certain
43  * areas).
44  * * Virtual mode
45  */
46 
47 #include <phgui.h>
48 #include <windowsx.h>
49 #include <vssym32.h>
50 #include <treenew.h>
51 #include <treenewp.h>
52 
53 static PVOID ComCtl32Handle;
54 static LONG SmallIconWidth;
55 static LONG SmallIconHeight;
56 
58  VOID
59  )
60 {
61  WNDCLASSEX c = { sizeof(c) };
62 
63  c.style = CS_DBLCLKS | CS_GLOBALCLASS;
64  c.lpfnWndProc = PhTnpWndProc;
65  c.cbClsExtra = 0;
66  c.cbWndExtra = sizeof(PVOID);
67  c.hInstance = PhLibImageBase;
68  c.hIcon = NULL;
69  c.hCursor = LoadCursor(NULL, IDC_ARROW);
70  c.hbrBackground = NULL;
71  c.lpszMenuName = NULL;
72  c.lpszClassName = PH_TREENEW_CLASSNAME;
73  c.hIconSm = NULL;
74 
75  if (!RegisterClassEx(&c))
76  return FALSE;
77 
78  ComCtl32Handle = GetModuleHandle(L"comctl32.dll");
79  SmallIconWidth = GetSystemMetrics(SM_CXSMICON);
80  SmallIconHeight = GetSystemMetrics(SM_CYSMICON);
81 
82  return TRUE;
83 }
84 
85 LRESULT CALLBACK PhTnpWndProc(
86  _In_ HWND hwnd,
87  _In_ UINT uMsg,
88  _In_ WPARAM wParam,
89  _In_ LPARAM lParam
90  )
91 {
92  PPH_TREENEW_CONTEXT context;
93 
94  context = (PPH_TREENEW_CONTEXT)GetWindowLongPtr(hwnd, 0);
95 
96  if (uMsg == WM_CREATE)
97  {
98  PhTnpCreateTreeNewContext(&context);
99  SetWindowLongPtr(hwnd, 0, (LONG_PTR)context);
100  }
101 
102  if (!context)
103  return DefWindowProc(hwnd, uMsg, wParam, lParam);
104 
105  if (context->Tracking && (GetAsyncKeyState(VK_ESCAPE) & 0x1))
106  {
107  PhTnpCancelTrack(context);
108  }
109 
110  // Note: if we have suspended restructuring, we *cannot* access any nodes, because
111  // all node pointers are now invalid. Below, we disable all input.
112 
113  switch (uMsg)
114  {
115  case WM_CREATE:
116  {
117  if (!PhTnpOnCreate(hwnd, context, (CREATESTRUCT *)lParam))
118  return -1;
119  }
120  return 0;
121  case WM_NCDESTROY:
122  {
123  context->Callback(hwnd, TreeNewDestroying, NULL, NULL, context->CallbackContext);
125  SetWindowLongPtr(hwnd, 0, (LONG_PTR)NULL);
126  }
127  return 0;
128  case WM_SIZE:
129  {
130  PhTnpOnSize(hwnd, context);
131  }
132  break;
133  case WM_ERASEBKGND:
134  return TRUE;
135  case WM_PAINT:
136  {
137  PhTnpOnPaint(hwnd, context);
138  }
139  return 0;
140  case WM_PRINTCLIENT:
141  {
142  PhTnpOnPrintClient(hwnd, context, (HDC)wParam, (ULONG)lParam);
143  }
144  return 0;
145  case WM_NCPAINT:
146  {
147  if (PhTnpOnNcPaint(hwnd, context, (HRGN)wParam))
148  return 0;
149  }
150  break;
151  case WM_GETFONT:
152  return (LRESULT)context->Font;
153  case WM_SETFONT:
154  {
155  PhTnpOnSetFont(hwnd, context, (HFONT)wParam, LOWORD(lParam));
156  }
157  break;
158  case WM_STYLECHANGED:
159  {
160  PhTnpOnStyleChanged(hwnd, context, (LONG)wParam, (STYLESTRUCT *)lParam);
161  }
162  break;
163  case WM_SETTINGCHANGE:
164  {
165  PhTnpOnSettingChange(hwnd, context);
166  }
167  break;
168  case WM_THEMECHANGED:
169  {
170  PhTnpOnThemeChanged(hwnd, context);
171  }
172  break;
173  case WM_GETDLGCODE:
174  return PhTnpOnGetDlgCode(hwnd, context, (ULONG)wParam, (PMSG)lParam);
175  case WM_SETFOCUS:
176  {
177  context->HasFocus = TRUE;
178  InvalidateRect(context->Handle, NULL, FALSE);
179  }
180  return 0;
181  case WM_KILLFOCUS:
182  {
183  context->HasFocus = FALSE;
184  InvalidateRect(context->Handle, NULL, FALSE);
185  }
186  return 0;
187  case WM_SETCURSOR:
188  {
189  if (PhTnpOnSetCursor(hwnd, context, (HWND)wParam))
190  return TRUE;
191  }
192  break;
193  case WM_TIMER:
194  {
195  PhTnpOnTimer(hwnd, context, (ULONG)wParam);
196  }
197  return 0;
198  case WM_MOUSEMOVE:
199  {
200  if (!context->SuspendUpdateStructure)
201  PhTnpOnMouseMove(hwnd, context, (ULONG)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
202  else
203  context->SuspendUpdateMoveMouse = TRUE;
204  }
205  break;
206  case WM_MOUSELEAVE:
207  {
208  if (!context->SuspendUpdateStructure)
209  PhTnpOnMouseLeave(hwnd, context);
210  }
211  break;
212  case WM_LBUTTONDOWN:
213  case WM_LBUTTONUP:
214  case WM_LBUTTONDBLCLK:
215  case WM_RBUTTONDOWN:
216  case WM_RBUTTONUP:
217  case WM_RBUTTONDBLCLK:
218  case WM_MBUTTONDOWN:
219  case WM_MBUTTONUP:
220  case WM_MBUTTONDBLCLK:
221  {
222  if (!context->SuspendUpdateStructure)
223  PhTnpOnXxxButtonXxx(hwnd, context, uMsg, (ULONG)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
224  }
225  break;
226  case WM_CAPTURECHANGED:
227  {
228  PhTnpOnCaptureChanged(hwnd, context);
229  }
230  break;
231  case WM_KEYDOWN:
232  {
233  if (!context->SuspendUpdateStructure)
234  PhTnpOnKeyDown(hwnd, context, (ULONG)wParam, (ULONG)lParam);
235  }
236  break;
237  case WM_CHAR:
238  {
239  if (!context->SuspendUpdateStructure)
240  PhTnpOnChar(hwnd, context, (ULONG)wParam, (ULONG)lParam);
241  }
242  return 0;
243  case WM_MOUSEWHEEL:
244  {
245  PhTnpOnMouseWheel(hwnd, context, (SHORT)HIWORD(wParam), LOWORD(wParam), GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
246  }
247  break;
248  case WM_MOUSEHWHEEL:
249  {
250  PhTnpOnMouseHWheel(hwnd, context, (SHORT)HIWORD(wParam), LOWORD(wParam), GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
251  }
252  break;
253  case WM_CONTEXTMENU:
254  {
255  PhTnpOnContextMenu(hwnd, context, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
256  }
257  return 0;
258  case WM_VSCROLL:
259  {
260  PhTnpOnVScroll(hwnd, context, LOWORD(wParam), HIWORD(wParam));
261  }
262  return 0;
263  case WM_HSCROLL:
264  {
265  PhTnpOnHScroll(hwnd, context, LOWORD(wParam), HIWORD(wParam));
266  }
267  return 0;
268  case WM_NOTIFY:
269  {
270  LRESULT result;
271 
272  if (PhTnpOnNotify(hwnd, context, (NMHDR *)lParam, &result))
273  return result;
274  }
275  break;
276  }
277 
278  if (uMsg >= TNM_FIRST && uMsg <= TNM_LAST)
279  {
280  return PhTnpOnUserMessage(hwnd, context, uMsg, wParam, lParam);
281  }
282 
283  switch (uMsg)
284  {
285  case WM_MOUSEMOVE:
286  case WM_LBUTTONDOWN:
287  case WM_LBUTTONUP:
288  case WM_RBUTTONDOWN:
289  case WM_RBUTTONUP:
290  case WM_MBUTTONDOWN:
291  case WM_MBUTTONUP:
292  {
293  if (context->TooltipsHandle)
294  {
295  MSG message;
296 
297  message.hwnd = hwnd;
298  message.message = uMsg;
299  message.wParam = wParam;
300  message.lParam = lParam;
301  SendMessage(context->TooltipsHandle, TTM_RELAYEVENT, 0, (LPARAM)&message);
302  }
303  }
304  break;
305  }
306 
307  return DefWindowProc(hwnd, uMsg, wParam, lParam);
308 }
309 
310 BOOLEAN NTAPI PhTnpNullCallback(
311  _In_ HWND hwnd,
312  _In_ PH_TREENEW_MESSAGE Message,
313  _In_opt_ PVOID Parameter1,
314  _In_opt_ PVOID Parameter2,
315  _In_opt_ PVOID Context
316  )
317 {
318  return FALSE;
319 }
320 
322  _Out_ PPH_TREENEW_CONTEXT *Context
323  )
324 {
325  PPH_TREENEW_CONTEXT context;
326 
327  context = PhAllocate(sizeof(PH_TREENEW_CONTEXT));
328  memset(context, 0, sizeof(PH_TREENEW_CONTEXT));
329 
330  context->FixedWidthMinimum = 20;
331  context->RowHeight = 1; // must never be 0
332  context->HotNodeIndex = -1;
333  context->Callback = PhTnpNullCallback;
334  context->FlatList = PhCreateList(64);
335  context->TooltipIndex = -1;
336  context->TooltipId = -1;
337  context->TooltipColumnId = -1;
338  context->EnableRedraw = 1;
339 
340  *Context = context;
341 }
342 
344  _In_ PPH_TREENEW_CONTEXT Context
345  )
346 {
347  ULONG i;
348 
349  if (Context->Columns)
350  {
351  for (i = 0; i < Context->NextId; i++)
352  {
353  if (Context->Columns[i])
354  PhFree(Context->Columns[i]);
355  }
356 
357  PhFree(Context->Columns);
358  }
359 
360  if (Context->ColumnsByDisplay)
361  PhFree(Context->ColumnsByDisplay);
362 
363  PhDereferenceObject(Context->FlatList);
364 
365  if (Context->FontOwned)
366  DeleteObject(Context->Font);
367 
368  if (Context->ThemeData)
369  CloseThemeData_I(Context->ThemeData);
370 
371  if (Context->SearchString)
372  PhFree(Context->SearchString);
373 
374  if (Context->TooltipText)
375  PhDereferenceObject(Context->TooltipText);
376 
377  if (Context->BufferedContext)
379 
380  if (Context->SuspendUpdateRegion)
381  DeleteObject(Context->SuspendUpdateRegion);
382 
383  PhFree(Context);
384 }
385 
387  _In_ HWND hwnd,
388  _In_ PPH_TREENEW_CONTEXT Context,
389  _In_ CREATESTRUCT *CreateStruct
390  )
391 {
392  ULONG headerStyle;
393 
394  Context->Handle = hwnd;
395  Context->InstanceHandle = CreateStruct->hInstance;
396  Context->Style = CreateStruct->style;
397  Context->ExtendedStyle = CreateStruct->dwExStyle;
398 
399  if (Context->Style & TN_STYLE_DOUBLE_BUFFERED)
400  Context->DoubleBuffered = TRUE;
401  if ((Context->Style & TN_STYLE_ANIMATE_DIVIDER) && Context->DoubleBuffered)
402  Context->AnimateDivider = TRUE;
403 
404  headerStyle = HDS_HORZ | HDS_FULLDRAG;
405 
406  if (!(Context->Style & TN_STYLE_NO_COLUMN_SORT))
407  headerStyle |= HDS_BUTTONS;
408  if (!(Context->Style & TN_STYLE_NO_COLUMN_HEADER))
409  headerStyle |= WS_VISIBLE;
410 
411  if (!(Context->FixedHeaderHandle = CreateWindow(
412  WC_HEADER,
413  NULL,
414  WS_CHILD | WS_CLIPSIBLINGS | headerStyle,
415  0,
416  0,
417  0,
418  0,
419  hwnd,
420  NULL,
421  CreateStruct->hInstance,
422  NULL
423  )))
424  {
425  return FALSE;
426  }
427 
428  if (!(Context->Style & TN_STYLE_NO_COLUMN_REORDER))
429  headerStyle |= HDS_DRAGDROP;
430 
431  if (!(Context->HeaderHandle = CreateWindow(
432  WC_HEADER,
433  NULL,
434  WS_CHILD | WS_CLIPSIBLINGS | headerStyle,
435  0,
436  0,
437  0,
438  0,
439  hwnd,
440  NULL,
441  CreateStruct->hInstance,
442  NULL
443  )))
444  {
445  return FALSE;
446  }
447 
448  if (!(Context->VScrollHandle = CreateWindow(
449  L"SCROLLBAR",
450  NULL,
451  WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | SBS_VERT,
452  0,
453  0,
454  0,
455  0,
456  hwnd,
457  NULL,
458  CreateStruct->hInstance,
459  NULL
460  )))
461  {
462  return FALSE;
463  }
464 
465  if (!(Context->HScrollHandle = CreateWindow(
466  L"SCROLLBAR",
467  NULL,
468  WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | SBS_HORZ,
469  0,
470  0,
471  0,
472  0,
473  hwnd,
474  NULL,
475  CreateStruct->hInstance,
476  NULL
477  )))
478  {
479  return FALSE;
480  }
481 
482  if (!(Context->FillerBoxHandle = CreateWindow(
483  L"STATIC",
484  NULL,
485  WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE,
486  0,
487  0,
488  0,
489  0,
490  hwnd,
491  NULL,
492  CreateStruct->hInstance,
493  NULL
494  )))
495  {
496  return FALSE;
497  }
498 
499  PhTnpSetFont(Context, NULL, FALSE); // use default font
500  PhTnpUpdateSystemMetrics(Context);
501  PhTnpInitializeTooltips(Context);
502 
503  return TRUE;
504 }
505 
507  _In_ HWND hwnd,
508  _In_ PPH_TREENEW_CONTEXT Context
509  )
510 {
511  GetClientRect(hwnd, &Context->ClientRect);
512 
513  if (Context->BufferedContext && (
514  Context->BufferedContextRect.right < Context->ClientRect.right ||
515  Context->BufferedContextRect.bottom < Context->ClientRect.bottom))
516  {
517  // Invalidate the buffered context because the client size has increased.
519  }
520 
521  PhTnpLayout(Context);
522 
523  if (Context->TooltipsHandle)
524  {
525  TOOLINFO toolInfo;
526 
527  memset(&toolInfo, 0, sizeof(TOOLINFO));
528  toolInfo.cbSize = sizeof(TOOLINFO);
529  toolInfo.hwnd = hwnd;
530  toolInfo.uId = TNP_TOOLTIPS_ITEM;
531  toolInfo.rect = Context->ClientRect;
532  SendMessage(Context->TooltipsHandle, TTM_NEWTOOLRECT, 0, (LPARAM)&toolInfo);
533  }
534 }
535 
537  _In_ HWND hwnd,
538  _In_ PPH_TREENEW_CONTEXT Context,
539  _In_opt_ HFONT Font,
540  _In_ LOGICAL Redraw
541  )
542 {
543  PhTnpSetFont(Context, Font, !!Redraw);
544  PhTnpLayout(Context);
545 }
546 
548  _In_ HWND hwnd,
549  _In_ PPH_TREENEW_CONTEXT Context,
550  _In_ LONG Type,
551  _In_ STYLESTRUCT *StyleStruct
552  )
553 {
554  if (Type == GWL_EXSTYLE)
555  Context->ExtendedStyle = StyleStruct->styleNew;
556 }
557 
559  _In_ HWND hwnd,
560  _In_ PPH_TREENEW_CONTEXT Context
561  )
562 {
563  PhTnpUpdateSystemMetrics(Context);
564  PhTnpUpdateTextMetrics(Context);
565  PhTnpLayout(Context);
566 }
567 
569  _In_ HWND hwnd,
570  _In_ PPH_TREENEW_CONTEXT Context
571  )
572 {
573  PhTnpUpdateThemeData(Context);
574 }
575 
577  _In_ HWND hwnd,
578  _In_ PPH_TREENEW_CONTEXT Context,
579  _In_ ULONG VirtualKey,
580  _In_opt_ PMSG Message
581  )
582 {
583  ULONG code;
584 
585  if (Context->Callback(hwnd, TreeNewGetDialogCode, UlongToPtr(VirtualKey), &code, Context->CallbackContext))
586  {
587  return code;
588  }
589 
590  return DLGC_WANTARROWS | DLGC_WANTCHARS;
591 }
592 
594  _In_ HWND hwnd,
595  _In_ PPH_TREENEW_CONTEXT Context
596  )
597 {
598  RECT updateRect;
599  HDC hdc;
600  PAINTSTRUCT paintStruct;
601 
602  if (GetUpdateRect(hwnd, &updateRect, FALSE) && (updateRect.left | updateRect.right | updateRect.top | updateRect.bottom))
603  {
604  if (Context->EnableRedraw <= 0)
605  {
606  HRGN updateRegion;
607 
608  updateRegion = CreateRectRgn(0, 0, 0, 0);
609  GetUpdateRgn(hwnd, updateRegion, FALSE);
610 
611  if (!Context->SuspendUpdateRegion)
612  {
613  Context->SuspendUpdateRegion = updateRegion;
614  }
615  else
616  {
617  CombineRgn(Context->SuspendUpdateRegion, Context->SuspendUpdateRegion, updateRegion, RGN_OR);
618  DeleteObject(updateRegion);
619  }
620 
621  // Pretend we painted something; this ensures the update region is validated properly.
622  if (BeginPaint(hwnd, &paintStruct))
623  EndPaint(hwnd, &paintStruct);
624 
625  return;
626  }
627 
628  if (Context->DoubleBuffered)
629  {
630  if (!Context->BufferedContext)
631  {
633  }
634  }
635 
636  if (hdc = BeginPaint(hwnd, &paintStruct))
637  {
638  updateRect = paintStruct.rcPaint;
639 
640  if (Context->BufferedContext)
641  {
642  PhTnpPaint(hwnd, Context, Context->BufferedContext, &updateRect);
643  BitBlt(
644  hdc,
645  updateRect.left,
646  updateRect.top,
647  updateRect.right - updateRect.left,
648  updateRect.bottom - updateRect.top,
649  Context->BufferedContext,
650  updateRect.left,
651  updateRect.top,
652  SRCCOPY
653  );
654  }
655  else
656  {
657  PhTnpPaint(hwnd, Context, hdc, &updateRect);
658  }
659 
660  EndPaint(hwnd, &paintStruct);
661  }
662  }
663 }
664 
666  _In_ HWND hwnd,
667  _In_ PPH_TREENEW_CONTEXT Context,
668  _In_ HDC hdc,
669  _In_ ULONG Flags
670  )
671 {
672  PhTnpPaint(hwnd, Context, hdc, &Context->ClientRect);
673 }
674 
676  _In_ HWND hwnd,
677  _In_ PPH_TREENEW_CONTEXT Context,
678  _In_opt_ HRGN UpdateRegion
679  )
680 {
681  PhTnpInitializeThemeData(Context);
682 
683  // Themed border
684  if ((Context->ExtendedStyle & WS_EX_CLIENTEDGE) && Context->ThemeData)
685  {
686  HDC hdc;
687  ULONG flags;
688 
689  if (UpdateRegion == HRGN_FULL)
690  UpdateRegion = NULL;
691 
692  // Note the use of undocumented flags below. GetDCEx doesn't work without these.
693 
694  flags = DCX_WINDOW | DCX_LOCKWINDOWUPDATE | 0x10000;
695 
696  if (UpdateRegion)
697  flags |= DCX_INTERSECTRGN | 0x40000;
698 
699  if (hdc = GetDCEx(hwnd, UpdateRegion, flags))
700  {
701  PhTnpDrawThemedBorder(Context, hdc);
702  ReleaseDC(hwnd, hdc);
703  return TRUE;
704  }
705  }
706 
707  return FALSE;
708 }
709 
711  _In_ HWND hwnd,
712  _In_ PPH_TREENEW_CONTEXT Context,
713  _In_ HWND CursorWindowHandle
714  )
715 {
716  POINT point;
717 
718  PhTnpGetMessagePos(hwnd, &point);
719 
720  if (TNP_HIT_TEST_FIXED_DIVIDER(point.x, Context))
721  {
722  if (!Context->DividerCursor)
723  Context->DividerCursor = LoadCursor(ComCtl32Handle, MAKEINTRESOURCE(106)); // HACK (the divider icon resource has been 106 for quite a while...)
724 
725  SetCursor(Context->DividerCursor);
726  return TRUE;
727  }
728 
729  if (Context->Cursor)
730  {
731  SetCursor(Context->Cursor);
732  return TRUE;
733  }
734 
735  return FALSE;
736 }
737 
739  _In_ HWND hwnd,
740  _In_ PPH_TREENEW_CONTEXT Context,
741  _In_ ULONG Id
742  )
743 {
744  if (Id == TNP_TIMER_ANIMATE_DIVIDER)
745  {
746  RECT dividerRect;
747 
748  dividerRect.left = Context->FixedWidth;
749  dividerRect.top = Context->HeaderHeight;
750  dividerRect.right = Context->FixedWidth + 1;
751  dividerRect.bottom = Context->ClientRect.bottom;
752 
753  if (Context->AnimateDividerFadingIn)
754  {
755  Context->DividerHot += TNP_ANIMATE_DIVIDER_INCREMENT;
756 
757  if (Context->DividerHot >= 100)
758  {
759  Context->DividerHot = 100;
760  Context->AnimateDividerFadingIn = FALSE;
761  KillTimer(hwnd, TNP_TIMER_ANIMATE_DIVIDER);
762  }
763 
764  InvalidateRect(hwnd, &dividerRect, FALSE);
765  }
766  else if (Context->AnimateDividerFadingOut)
767  {
768  if (Context->DividerHot <= TNP_ANIMATE_DIVIDER_DECREMENT)
769  {
770  Context->DividerHot = 0;
771  Context->AnimateDividerFadingOut = FALSE;
772  KillTimer(hwnd, TNP_TIMER_ANIMATE_DIVIDER);
773  }
774  else
775  {
776  Context->DividerHot -= TNP_ANIMATE_DIVIDER_DECREMENT;
777  }
778 
779  InvalidateRect(hwnd, &dividerRect, FALSE);
780  }
781  }
782 }
783 
785  _In_ HWND hwnd,
786  _In_ PPH_TREENEW_CONTEXT Context,
787  _In_ ULONG VirtualKeys,
788  _In_ LONG CursorX,
789  _In_ LONG CursorY
790  )
791 {
792  TRACKMOUSEEVENT trackMouseEvent;
793 
794  trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
795  trackMouseEvent.dwFlags = TME_LEAVE;
796  trackMouseEvent.hwndTrack = hwnd;
797  trackMouseEvent.dwHoverTime = 0;
798  TrackMouseEvent(&trackMouseEvent);
799 
800  if (Context->Tracking)
801  {
802  ULONG newFixedWidth;
803 
804  newFixedWidth = Context->TrackOldFixedWidth + (CursorX - Context->TrackStartX);
805  PhTnpSetFixedWidth(Context, newFixedWidth);
806  }
807 
808  PhTnpProcessMoveMouse(Context, CursorX, CursorY);
809 }
810 
812  _In_ HWND hwnd,
813  _In_ PPH_TREENEW_CONTEXT Context
814  )
815 {
816  RECT rect;
817 
818  if (Context->HotNodeIndex != -1 && Context->ThemeData)
819  {
820  // Update the old hot node because it may have a different non-hot background and plus minus part.
821  if (PhTnpGetRowRects(Context, Context->HotNodeIndex, Context->HotNodeIndex, TRUE, &rect))
822  {
823  InvalidateRect(Context->Handle, &rect, FALSE);
824  }
825  }
826 
827  Context->HotNodeIndex = -1;
828 
829  if (Context->AnimateDivider && Context->FixedDividerVisible)
830  {
831  if ((Context->DividerHot != 0 || Context->AnimateDividerFadingIn) && !Context->AnimateDividerFadingOut)
832  {
833  // Fade out the divider.
834  Context->AnimateDividerFadingOut = TRUE;
835  Context->AnimateDividerFadingIn = FALSE;
836  SetTimer(Context->Handle, TNP_TIMER_ANIMATE_DIVIDER, TNP_ANIMATE_DIVIDER_INTERVAL, NULL);
837  }
838  }
839 }
840 
842  _In_ HWND hwnd,
843  _In_ PPH_TREENEW_CONTEXT Context,
844  _In_ ULONG Message,
845  _In_ ULONG VirtualKeys,
846  _In_ LONG CursorX,
847  _In_ LONG CursorY
848  )
849 {
850  BOOLEAN startingTracking;
851  PH_TREENEW_HIT_TEST hitTest;
852  LOGICAL controlKey;
853  LOGICAL shiftKey;
854  RECT rect;
855  ULONG changedStart;
856  ULONG changedEnd;
857  PH_TREENEW_MESSAGE clickMessage;
858 
859  // Focus
860 
861  if (Message == WM_LBUTTONDOWN || Message == WM_RBUTTONDOWN)
862  SetFocus(hwnd);
863 
864  // Divider tracking
865 
866  startingTracking = FALSE;
867 
868  switch (Message)
869  {
870  case WM_LBUTTONDOWN:
871  {
872  if (TNP_HIT_TEST_FIXED_DIVIDER(CursorX, Context))
873  {
874  startingTracking = TRUE;
875  Context->Tracking = TRUE;
876  Context->TrackStartX = CursorX;
877  Context->TrackOldFixedWidth = Context->FixedWidth;
878  SetCapture(hwnd);
879 
880  SetTimer(hwnd, TNP_TIMER_NULL, 100, NULL); // make sure we get messages once in a while so we can detect the escape key
881  GetAsyncKeyState(VK_ESCAPE);
882  }
883  }
884  break;
885  case WM_LBUTTONUP:
886  {
887  if (Context->Tracking)
888  {
889  ReleaseCapture();
890  }
891  }
892  break;
893  case WM_RBUTTONDOWN:
894  {
895  if (Context->Tracking)
896  {
897  PhTnpCancelTrack(Context);
898  }
899  }
900  break;
901  }
902 
903  if (!startingTracking && Context->Tracking) // still OK to process further if the user is only starting to drag the divider
904  return;
905 
906  hitTest.Point.x = CursorX;
907  hitTest.Point.y = CursorY;
909  PhTnpHitTest(Context, &hitTest);
910 
911  controlKey = VirtualKeys & MK_CONTROL;
912  shiftKey = VirtualKeys & MK_SHIFT;
913 
914  // Plus minus glyph
915 
916  if ((hitTest.Flags & TN_HIT_ITEM_PLUSMINUS) && Message == WM_LBUTTONDOWN)
917  {
918  PhTnpSetExpandedNode(Context, hitTest.Node, !hitTest.Node->Expanded);
919  }
920 
921  // Selection
922 
923  if (!(hitTest.Flags & TN_HIT_ITEM_PLUSMINUS) && (Message == WM_LBUTTONDOWN || Message == WM_RBUTTONDOWN))
924  {
925  LOGICAL allowDragSelect;
926  PH_TREENEW_CELL_PARTS parts;
927 
928  PhTnpPopTooltip(Context);
929  allowDragSelect = TRUE;
930 
931  if (hitTest.Flags & TN_HIT_ITEM)
932  {
933  allowDragSelect = FALSE;
934  Context->FocusNode = hitTest.Node;
935 
936  if (Context->ExtendedFlags & TN_FLAG_ITEM_DRAG_SELECT)
937  {
938  // To allow drag selection to begin even if the cursor is on an item,
939  // we check if the cursor is on the item icon or text. Exceptions are:
940  // * When the item is already selected
941  // * When user is beginning to drag the divider
942 
943  if (!hitTest.Node->Selected && !startingTracking)
944  {
945  if (PhTnpGetCellParts(Context, hitTest.Node->Index, hitTest.Column, TN_MEASURE_TEXT, &parts))
946  {
947  allowDragSelect = TRUE;
948 
949  if ((parts.Flags & TN_PART_ICON) && CursorX >= parts.IconRect.left && CursorX < parts.IconRect.right)
950  allowDragSelect = FALSE;
951 
952  if ((parts.Flags & TN_PART_CONTENT) && (parts.Flags & TN_PART_TEXT))
953  {
954  if (CursorX >= parts.TextRect.left && CursorX < parts.TextRect.right)
955  allowDragSelect = FALSE;
956  }
957  }
958  }
959  }
960 
961  PhTnpProcessSelectNode(Context, hitTest.Node, controlKey, shiftKey, Message == WM_RBUTTONDOWN);
962  }
963 
964  if (allowDragSelect)
965  {
966  BOOLEAN dragSelect;
967  ULONG indexToSelect;
968  BOOLEAN selectionProcessed;
969  BOOLEAN showContextMenu;
970 
971  dragSelect = FALSE;
972  indexToSelect = -1;
973  selectionProcessed = FALSE;
974  showContextMenu = FALSE;
975 
976  if (!(hitTest.Flags & (TN_HIT_LEFT | TN_HIT_RIGHT | TN_HIT_ABOVE | TN_HIT_BELOW)) && !startingTracking) // don't interfere with divider
977  {
978  BOOLEAN result;
979  ULONG saveIndex;
980  ULONG saveId;
981  ULONG cancelledByMessage;
982 
983  // Check for drag selection. PhTnpDetectDrag has its own message loop, so we need to
984  // clear our pointers before we continue or we will have some access violations when
985  // items get deleted.
986 
987  if (hitTest.Node)
988  saveIndex = hitTest.Node->Index;
989  else
990  saveIndex = -1;
991 
992  if (hitTest.Column)
993  saveId = hitTest.Column->Id;
994  else
995  saveId = -1;
996 
997  result = PhTnpDetectDrag(Context, CursorX, CursorY, TRUE, &cancelledByMessage);
998 
999  // Restore the pointers.
1000 
1001  if (saveIndex == -1)
1002  hitTest.Node = NULL;
1003  else if (saveIndex < Context->FlatList->Count)
1004  hitTest.Node = Context->FlatList->Items[saveIndex];
1005  else
1006  return;
1007 
1008  if (saveId != -1 && !(hitTest.Column = PhTnpLookupColumnById(Context, saveId)))
1009  return;
1010 
1011  if (result)
1012  {
1013  dragSelect = TRUE;
1014 
1015  if ((hitTest.Flags & TN_HIT_ITEM) && (Context->ExtendedFlags & TN_FLAG_ITEM_DRAG_SELECT))
1016  {
1017  // Include the current node before starting the drag selection, otherwise the user
1018  // will never be able to select the current node.
1019  indexToSelect = hitTest.Node->Index;
1020  }
1021  }
1022  else
1023  {
1024  if ((Message == WM_LBUTTONDOWN && cancelledByMessage == WM_LBUTTONUP) ||
1025  (Message == WM_RBUTTONDOWN && cancelledByMessage == WM_RBUTTONUP))
1026  {
1027  POINT point;
1028 
1029  if ((hitTest.Flags & TN_HIT_ITEM) && (Context->ExtendedFlags & TN_FLAG_ITEM_DRAG_SELECT))
1030  {
1031  // The user isn't performing a drag selection, so prevent deselection.
1032  selectionProcessed = TRUE;
1033  }
1034 
1035  // The button up message gets consumed by PhTnpDetectDrag, so send the mouse event here.
1036  // Check if the cursor stayed in the same place.
1037 
1038  PhTnpGetMessagePos(Context->Handle, &point);
1039 
1040  if (point.x == CursorX && point.y == CursorY)
1041  {
1043  Context,
1044  Message == WM_LBUTTONDOWN ? TreeNewLeftClick : TreeNewRightClick,
1045  CursorX,
1046  CursorY,
1047  hitTest.Node,
1048  hitTest.Column,
1049  VirtualKeys
1050  );
1051  }
1052 
1053  if (Message == WM_RBUTTONDOWN)
1054  showContextMenu = TRUE;
1055  }
1056  }
1057  }
1058 
1059  if (!selectionProcessed && !controlKey && !shiftKey)
1060  {
1061  // Nothing: deselect everything.
1062 
1063  PhTnpSelectRange(Context, indexToSelect, indexToSelect, TN_SELECT_RESET, &changedStart, &changedEnd);
1064 
1065  if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
1066  {
1067  InvalidateRect(hwnd, &rect, FALSE);
1068  }
1069  }
1070 
1071  if (dragSelect)
1072  {
1073  PhTnpDragSelect(Context, CursorX, CursorY);
1074  }
1075 
1076  if (showContextMenu)
1077  {
1078  SendMessage(Context->Handle, WM_CONTEXTMENU, (WPARAM)Context->Handle, GetMessagePos());
1079  }
1080 
1081  return;
1082  }
1083  }
1084 
1085  // Click, double-click
1086  // Note: If TN_FLAG_ITEM_DRAG_SELECT is enabled, the code below that processes WM_xBUTTONDOWN
1087  // and WM_xBUTTONUP messages only takes effect when the user clicks directly on an item's icon
1088  // or text.
1089 
1090  clickMessage = -1;
1091 
1092  if (Message == WM_LBUTTONDOWN || Message == WM_RBUTTONDOWN)
1093  {
1094  if (Context->MouseDownLast != 0 && Context->MouseDownLast != Message)
1095  {
1096  // User pressed one button and pressed the other without letting go of the first one.
1097  // This counts as a click.
1098 
1099  if (Context->MouseDownLast == WM_LBUTTONDOWN)
1100  clickMessage = TreeNewLeftClick;
1101  else
1102  clickMessage = TreeNewRightClick;
1103  }
1104 
1105  Context->MouseDownLast = Message;
1106  Context->MouseDownLocation.x = CursorX;
1107  Context->MouseDownLocation.y = CursorY;
1108  }
1109  else if (Message == WM_LBUTTONUP || Message == WM_RBUTTONUP)
1110  {
1111  if (Context->MouseDownLast != 0 &&
1112  Context->MouseDownLocation.x == CursorX && Context->MouseDownLocation.y == CursorY)
1113  {
1114  if (Context->MouseDownLast == WM_LBUTTONDOWN)
1115  clickMessage = TreeNewLeftClick;
1116  else
1117  clickMessage = TreeNewRightClick;
1118  }
1119 
1120  Context->MouseDownLast = 0;
1121  }
1122  else if (Message == WM_LBUTTONDBLCLK)
1123  {
1124  clickMessage = TreeNewLeftDoubleClick;
1125  }
1126  else if (Message == WM_RBUTTONDBLCLK)
1127  {
1128  clickMessage = TreeNewRightDoubleClick;
1129  }
1130 
1131  if (!(hitTest.Flags & TN_HIT_ITEM_PLUSMINUS) && clickMessage != -1)
1132  {
1133  PhTnpSendMouseEvent(Context, clickMessage, CursorX, CursorY, hitTest.Node, hitTest.Column, VirtualKeys);
1134  }
1135 }
1136 
1138  _In_ HWND hwnd,
1139  _In_ PPH_TREENEW_CONTEXT Context
1140  )
1141 {
1142  Context->Tracking = FALSE;
1143  KillTimer(hwnd, TNP_TIMER_NULL);
1144 }
1145 
1147  _In_ HWND hwnd,
1148  _In_ PPH_TREENEW_CONTEXT Context,
1149  _In_ ULONG VirtualKey,
1150  _In_ ULONG Data
1151  )
1152 {
1153  PH_TREENEW_KEY_EVENT keyEvent;
1154 
1155  keyEvent.Handled = FALSE;
1156  keyEvent.VirtualKey = VirtualKey;
1157  keyEvent.Data = Data;
1158  Context->Callback(Context->Handle, TreeNewKeyDown, &keyEvent, NULL, Context->CallbackContext);
1159 
1160  if (keyEvent.Handled)
1161  return;
1162 
1163  if (PhTnpProcessFocusKey(Context, VirtualKey))
1164  return;
1165  if (PhTnpProcessNodeKey(Context, VirtualKey))
1166  return;
1167 }
1168 
1170  _In_ HWND hwnd,
1171  _In_ PPH_TREENEW_CONTEXT Context,
1172  _In_ ULONG Character,
1173  _In_ ULONG Data
1174  )
1175 {
1176  // Make sure the character is printable.
1177  if (Character >= ' ' && Character <= '~')
1178  {
1179  PhTnpProcessSearchKey(Context, Character);
1180  }
1181 }
1182 
1184  _In_ HWND hwnd,
1185  _In_ PPH_TREENEW_CONTEXT Context,
1186  _In_ LONG Distance,
1187  _In_ ULONG VirtualKeys,
1188  _In_ LONG CursorX,
1189  _In_ LONG CursorY
1190  )
1191 {
1192  // The normal mouse wheel can affect both the vertical scrollbar and the horizontal scrollbar,
1193  // but the vertical scrollbar takes precedence.
1194  if (Context->VScrollVisible)
1195  {
1196  PhTnpProcessMouseVWheel(Context, -Distance);
1197  }
1198  else if (Context->HScrollVisible)
1199  {
1200  PhTnpProcessMouseHWheel(Context, -Distance);
1201  }
1202 }
1203 
1205  _In_ HWND hwnd,
1206  _In_ PPH_TREENEW_CONTEXT Context,
1207  _In_ LONG Distance,
1208  _In_ ULONG VirtualKeys,
1209  _In_ LONG CursorX,
1210  _In_ LONG CursorY
1211  )
1212 {
1213  PhTnpProcessMouseHWheel(Context, Distance);
1214 }
1215 
1217  _In_ HWND hwnd,
1218  _In_ PPH_TREENEW_CONTEXT Context,
1219  _In_ LONG CursorScreenX,
1220  _In_ LONG CursorScreenY
1221  )
1222 {
1223  POINT clientPoint;
1224  BOOLEAN keyboardInvoked;
1225  PH_TREENEW_HIT_TEST hitTest;
1226  PH_TREENEW_CONTEXT_MENU contextMenu;
1227 
1228  if (CursorScreenX == -1 && CursorScreenY == -1)
1229  {
1230  ULONG i;
1231  BOOLEAN found;
1232  RECT windowRect;
1233  RECT rect;
1234 
1235  keyboardInvoked = TRUE;
1236 
1237  // Context menu was invoked via keyboard. Display the context menu at
1238  // the selected item.
1239 
1240  found = FALSE;
1241 
1242  for (i = 0; i < Context->FlatList->Count; i++)
1243  {
1244  if (((PPH_TREENEW_NODE)Context->FlatList->Items[i])->Selected)
1245  {
1246  found = TRUE;
1247  break;
1248  }
1249  }
1250 
1251  if (found && PhTnpGetRowRects(Context, i, i, FALSE, &rect) &&
1252  rect.top >= Context->ClientRect.top && rect.top < Context->ClientRect.bottom)
1253  {
1254  clientPoint.x = rect.left + SmallIconWidth / 2;
1255  clientPoint.y = rect.top + Context->RowHeight / 2;
1256  }
1257  else
1258  {
1259  clientPoint.x = 0;
1260  clientPoint.y = 0;
1261  }
1262 
1263  GetWindowRect(hwnd, &windowRect);
1264  CursorScreenX = windowRect.left + clientPoint.x;
1265  CursorScreenY = windowRect.top + clientPoint.y;
1266  }
1267  else
1268  {
1269  keyboardInvoked = FALSE;
1270 
1271  clientPoint.x = CursorScreenX;
1272  clientPoint.y = CursorScreenY;
1273  ScreenToClient(hwnd, &clientPoint);
1274 
1275  if (clientPoint.y < Context->HeaderHeight)
1276  {
1277  // Already handled by TreeNewHeaderRightClick.
1278  return;
1279  }
1280  }
1281 
1282  hitTest.Point = clientPoint;
1283  hitTest.InFlags = TN_TEST_COLUMN;
1284  PhTnpHitTest(Context, &hitTest);
1285 
1286  contextMenu.Location.x = CursorScreenX;
1287  contextMenu.Location.y = CursorScreenY;
1288  contextMenu.ClientLocation = clientPoint;
1289  contextMenu.Node = hitTest.Node;
1290  contextMenu.Column = hitTest.Column;
1291  contextMenu.KeyboardInvoked = keyboardInvoked;
1292  Context->Callback(hwnd, TreeNewContextMenu, &contextMenu, NULL, Context->CallbackContext);
1293 }
1294 
1296  _In_ HWND hwnd,
1297  _In_ PPH_TREENEW_CONTEXT Context,
1298  _In_ ULONG Request,
1299  _In_ USHORT Position
1300  )
1301 {
1302  SCROLLINFO scrollInfo;
1303  LONG oldPosition;
1304 
1305  scrollInfo.cbSize = sizeof(SCROLLINFO);
1306  scrollInfo.fMask = SIF_ALL;
1307  GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo);
1308  oldPosition = scrollInfo.nPos;
1309 
1310  switch (Request)
1311  {
1312  case SB_LINEUP:
1313  scrollInfo.nPos--;
1314  break;
1315  case SB_LINEDOWN:
1316  scrollInfo.nPos++;
1317  break;
1318  case SB_PAGEUP:
1319  scrollInfo.nPos -= scrollInfo.nPage;
1320  break;
1321  case SB_PAGEDOWN:
1322  scrollInfo.nPos += scrollInfo.nPage;
1323  break;
1324  case SB_THUMBPOSITION:
1325  // Touch scrolling seems to give us Position but not nTrackPos. The problem is that
1326  // Position is a 16-bit value, so don't use it if we have too many rows.
1327  if (Context->FlatList->Count <= 0xffff)
1328  scrollInfo.nPos = Position;
1329  break;
1330  case SB_THUMBTRACK:
1331  scrollInfo.nPos = scrollInfo.nTrackPos;
1332  break;
1333  case SB_TOP:
1334  scrollInfo.nPos = 0;
1335  break;
1336  case SB_BOTTOM:
1337  scrollInfo.nPos = MAXINT;
1338  break;
1339  }
1340 
1341  scrollInfo.fMask = SIF_POS;
1342  SetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo, TRUE);
1343  GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo);
1344 
1345  if (scrollInfo.nPos != oldPosition)
1346  {
1347  Context->VScrollPosition = scrollInfo.nPos;
1348  PhTnpProcessScroll(Context, scrollInfo.nPos - oldPosition, 0);
1349  }
1350 }
1351 
1353  _In_ HWND hwnd,
1354  _In_ PPH_TREENEW_CONTEXT Context,
1355  _In_ ULONG Request,
1356  _In_ USHORT Position
1357  )
1358 {
1359  SCROLLINFO scrollInfo;
1360  LONG oldPosition;
1361 
1362  scrollInfo.cbSize = sizeof(SCROLLINFO);
1363  scrollInfo.fMask = SIF_ALL;
1364  GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo);
1365  oldPosition = scrollInfo.nPos;
1366 
1367  switch (Request)
1368  {
1369  case SB_LINELEFT:
1370  scrollInfo.nPos -= Context->TextMetrics.tmAveCharWidth;
1371  break;
1372  case SB_LINERIGHT:
1373  scrollInfo.nPos += Context->TextMetrics.tmAveCharWidth;
1374  break;
1375  case SB_PAGELEFT:
1376  scrollInfo.nPos -= scrollInfo.nPage;
1377  break;
1378  case SB_PAGERIGHT:
1379  scrollInfo.nPos += scrollInfo.nPage;
1380  break;
1381  case SB_THUMBPOSITION:
1382  // Touch scrolling seems to give us Position but not nTrackPos. The problem is that
1383  // Position is a 16-bit value, so don't use it if we have too many rows.
1384  if (Context->FlatList->Count <= 0xffff)
1385  scrollInfo.nPos = Position;
1386  break;
1387  case SB_THUMBTRACK:
1388  scrollInfo.nPos = scrollInfo.nTrackPos;
1389  break;
1390  case SB_LEFT:
1391  scrollInfo.nPos = 0;
1392  break;
1393  case SB_RIGHT:
1394  scrollInfo.nPos = MAXINT;
1395  break;
1396  }
1397 
1398  scrollInfo.fMask = SIF_POS;
1399  SetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo, TRUE);
1400  GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo);
1401 
1402  if (scrollInfo.nPos != oldPosition)
1403  {
1404  Context->HScrollPosition = scrollInfo.nPos;
1405  PhTnpProcessScroll(Context, 0, scrollInfo.nPos - oldPosition);
1406  }
1407 }
1408 
1410  _In_ HWND hwnd,
1411  _In_ PPH_TREENEW_CONTEXT Context,
1412  _In_ NMHDR *Header,
1413  _Out_ LRESULT *Result
1414  )
1415 {
1416  switch (Header->code)
1417  {
1418  case HDN_ITEMCHANGING:
1419  case HDN_ITEMCHANGED:
1420  {
1421  NMHEADER *nmHeader = (NMHEADER *)Header;
1422 
1423  if (Header->code == HDN_ITEMCHANGING && Header->hwndFrom == Context->FixedHeaderHandle)
1424  {
1425  if (nmHeader->pitem->mask & HDI_WIDTH)
1426  {
1427  if (Context->FixedColumnVisible)
1428  {
1429  Context->FixedWidth = nmHeader->pitem->cxy - 1;
1430 
1431  if (Context->FixedWidth < Context->FixedWidthMinimum)
1432  Context->FixedWidth = Context->FixedWidthMinimum;
1433 
1434  Context->NormalLeft = Context->FixedWidth + 1;
1435  nmHeader->pitem->cxy = Context->FixedWidth + 1;
1436  }
1437  else
1438  {
1439  Context->FixedWidth = 0;
1440  Context->NormalLeft = 0;
1441  }
1442  }
1443  }
1444 
1445  if (Header->hwndFrom == Context->FixedHeaderHandle || Header->hwndFrom == Context->HeaderHandle)
1446  {
1447  if (nmHeader->pitem->mask & HDI_WIDTH)
1448  {
1449  // A column has been resized. Update our stored information.
1450  PhTnpUpdateColumnHeaders(Context);
1451  PhTnpUpdateColumnMaps(Context);
1452 
1453  if (Header->code == HDN_ITEMCHANGING)
1454  {
1455  HDITEM item;
1456 
1457  item.mask = HDI_WIDTH | HDI_LPARAM;
1458 
1459  if (Header_GetItem(Header->hwndFrom, nmHeader->iItem, &item))
1460  {
1461  Context->ResizingColumn = (PPH_TREENEW_COLUMN)item.lParam;
1462  Context->OldColumnWidth = item.cxy;
1463  }
1464  else
1465  {
1466  Context->ResizingColumn = NULL;
1467  Context->OldColumnWidth = -1;
1468  }
1469  }
1470  else if (Header->code == HDN_ITEMCHANGED)
1471  {
1472  if (Context->ResizingColumn)
1473  {
1474  LONG delta;
1475 
1476  delta = nmHeader->pitem->cxy - Context->OldColumnWidth;
1477 
1478  if (delta != 0)
1479  {
1480  PhTnpProcessResizeColumn(Context, Context->ResizingColumn, delta);
1481  Context->Callback(Context->Handle, TreeNewColumnResized, Context->ResizingColumn, NULL, Context->CallbackContext);
1482  }
1483 
1484  Context->ResizingColumn = NULL;
1485 
1486  // Redraw the entire window if we are displaying empty text.
1487  if (Context->FlatList->Count == 0 && Context->EmptyText.Length != 0)
1488  InvalidateRect(Context->Handle, NULL, FALSE);
1489  }
1490  else
1491  {
1492  // An error occurred during HDN_ITEMCHANGED, so redraw the entire window.
1493  InvalidateRect(Context->Handle, NULL, FALSE);
1494  }
1495  }
1496  }
1497  }
1498  }
1499  break;
1500  case HDN_ITEMCLICK:
1501  {
1502  if ((Header->hwndFrom == Context->FixedHeaderHandle || Header->hwndFrom == Context->HeaderHandle) &&
1503  !(Context->Style & TN_STYLE_NO_COLUMN_SORT))
1504  {
1505  NMHEADER *nmHeader = (NMHEADER *)Header;
1506  HDITEM item;
1507  PPH_TREENEW_COLUMN column;
1508 
1509  // A column has been clicked, so update the sort state.
1510 
1511  item.mask = HDI_LPARAM;
1512 
1513  if (Header_GetItem(Header->hwndFrom, nmHeader->iItem, &item))
1514  {
1515  column = (PPH_TREENEW_COLUMN)item.lParam;
1516  PhTnpProcessSortColumn(Context, column);
1517  }
1518  }
1519  }
1520  break;
1521  case HDN_ENDDRAG:
1522  case NM_RELEASEDCAPTURE:
1523  {
1524  if (Header->hwndFrom == Context->HeaderHandle)
1525  {
1526  // Columns have been re-ordered, so refresh our information.
1527  // Note: The fixed column cannot be re-ordered.
1528  PhTnpUpdateColumnHeaders(Context);
1529  PhTnpUpdateColumnMaps(Context);
1530  Context->Callback(Context->Handle, TreeNewColumnReordered, NULL, NULL, Context->CallbackContext);
1531  InvalidateRect(Context->Handle, NULL, FALSE);
1532  }
1533  }
1534  break;
1535  case HDN_DIVIDERDBLCLICK:
1536  {
1537  if (Header->hwndFrom == Context->FixedHeaderHandle || Header->hwndFrom == Context->HeaderHandle)
1538  {
1539  NMHEADER *nmHeader = (NMHEADER *)Header;
1540  HDITEM item;
1541 
1542  if (Context->SuspendUpdateStructure)
1543  break;
1544 
1545  item.mask = HDI_LPARAM;
1546 
1547  if (Header_GetItem(Header->hwndFrom, nmHeader->iItem, &item))
1548  {
1550  Context,
1551  Header->hwndFrom,
1552  (PPH_TREENEW_COLUMN)item.lParam,
1553  0
1554  );
1555  }
1556  }
1557  }
1558  break;
1559  case NM_RCLICK:
1560  {
1561  if (Header->hwndFrom == Context->FixedHeaderHandle || Header->hwndFrom == Context->HeaderHandle)
1562  {
1563  PH_TREENEW_HEADER_MOUSE_EVENT mouseEvent;
1564  ULONG position;
1565 
1566  position = GetMessagePos();
1567  mouseEvent.ScreenLocation.x = GET_X_LPARAM(position);
1568  mouseEvent.ScreenLocation.y = GET_Y_LPARAM(position);
1569 
1570  mouseEvent.Location = mouseEvent.ScreenLocation;
1571  ScreenToClient(hwnd, &mouseEvent.Location);
1572  mouseEvent.HeaderLocation = mouseEvent.ScreenLocation;
1573  ScreenToClient(Header->hwndFrom, &mouseEvent.HeaderLocation);
1574  mouseEvent.Column = PhTnpHitTestHeader(Context, Header->hwndFrom == Context->FixedHeaderHandle, &mouseEvent.HeaderLocation, NULL);
1575  Context->Callback(hwnd, TreeNewHeaderRightClick, &mouseEvent, NULL, Context->CallbackContext);
1576  }
1577  }
1578  break;
1579  case TTN_GETDISPINFO:
1580  {
1581  if (Header->hwndFrom == Context->TooltipsHandle)
1582  {
1583  NMTTDISPINFO *info = (NMTTDISPINFO *)Header;
1584  POINT point;
1585 
1586  PhTnpGetMessagePos(hwnd, &point);
1587  PhTnpGetTooltipText(Context, &point, &info->lpszText);
1588  }
1589  }
1590  break;
1591  case TTN_SHOW:
1592  {
1593  if (Header->hwndFrom == Context->TooltipsHandle)
1594  {
1595  *Result = PhTnpPrepareTooltipShow(Context);
1596  return TRUE;
1597  }
1598  }
1599  break;
1600  case TTN_POP:
1601  {
1602  if (Header->hwndFrom == Context->TooltipsHandle)
1603  {
1604  PhTnpPrepareTooltipPop(Context);
1605  }
1606  }
1607  break;
1608  }
1609 
1610  return FALSE;
1611 }
1612 
1614  _In_ HWND hwnd,
1615  _In_ PPH_TREENEW_CONTEXT Context,
1616  _In_ ULONG Message,
1617  _In_ ULONG_PTR WParam,
1618  _In_ ULONG_PTR LParam
1619  )
1620 {
1621  switch (Message)
1622  {
1623  case TNM_SETCALLBACK:
1624  {
1625  Context->Callback = (PPH_TREENEW_CALLBACK)LParam;
1626  Context->CallbackContext = (PVOID)WParam;
1627 
1628  if (!Context->Callback)
1629  Context->Callback = PhTnpNullCallback;
1630  }
1631  return TRUE;
1632  case TNM_NODESSTRUCTURED:
1633  {
1634  if (Context->EnableRedraw <= 0)
1635  {
1636  Context->SuspendUpdateStructure = TRUE;
1637  Context->SuspendUpdateLayout = TRUE;
1638  InvalidateRect(Context->Handle, NULL, FALSE);
1639  return TRUE;
1640  }
1641 
1642  PhTnpRestructureNodes(Context);
1643  PhTnpLayout(Context);
1644  InvalidateRect(Context->Handle, NULL, FALSE);
1645  }
1646  return TRUE;
1647  case TNM_ADDCOLUMN:
1648  return PhTnpAddColumn(Context, (PPH_TREENEW_COLUMN)LParam);
1649  case TNM_REMOVECOLUMN:
1650  return PhTnpRemoveColumn(Context, (ULONG)WParam);
1651  case TNM_GETCOLUMN:
1652  return PhTnpCopyColumn(Context, (ULONG)WParam, (PPH_TREENEW_COLUMN)LParam);
1653  case TNM_SETCOLUMN:
1654  {
1655  PPH_TREENEW_COLUMN column = (PPH_TREENEW_COLUMN)LParam;
1656 
1657  return PhTnpChangeColumn(Context, (ULONG)WParam, column->Id, column);
1658  }
1659  break;
1661  {
1662  ULONG count = (ULONG)WParam;
1663  PULONG order = (PULONG)LParam;
1664  ULONG i;
1665 
1666  if (count != Context->NumberOfColumnsByDisplay)
1667  return FALSE;
1668 
1669  for (i = 0; i < count; i++)
1670  {
1671  order[i] = Context->ColumnsByDisplay[i]->Id;
1672  }
1673  }
1674  return TRUE;
1676  {
1677  ULONG count = (ULONG)WParam;
1678  PULONG order = (PULONG)LParam;
1679  ULONG i;
1680  PULONG newOrder;
1681  PPH_TREENEW_COLUMN column;
1682 
1683  newOrder = PhAllocate(count * sizeof(ULONG));
1684 
1685  for (i = 0; i < count; i++)
1686  {
1687  if (!(column = PhTnpLookupColumnById(Context, order[i])))
1688  {
1689  PhFree(newOrder);
1690  return FALSE;
1691  }
1692 
1693  newOrder[i] = column->s.ViewIndex;
1694  }
1695 
1696  if (!Header_SetOrderArray(Context->HeaderHandle, count, newOrder))
1697  {
1698  PhFree(newOrder);
1699  return FALSE;
1700  }
1701 
1702  PhFree(newOrder);
1703 
1704  PhTnpUpdateColumnHeaders(Context);
1705  PhTnpUpdateColumnMaps(Context);
1706  }
1707  return TRUE;
1708  case TNM_SETCURSOR:
1709  {
1710  Context->Cursor = (HCURSOR)LParam;
1711  }
1712  return TRUE;
1713  case TNM_GETSORT:
1714  {
1715  PULONG sortColumn = (PULONG)WParam;
1716  PPH_SORT_ORDER sortOrder = (PPH_SORT_ORDER)LParam;
1717 
1718  if (sortColumn)
1719  *sortColumn = Context->SortColumn;
1720  if (sortOrder)
1721  *sortOrder = Context->SortOrder;
1722  }
1723  return TRUE;
1724  case TNM_SETSORT:
1725  {
1726  ULONG sortColumn = (ULONG)WParam;
1727  PH_SORT_ORDER sortOrder = (PH_SORT_ORDER)LParam;
1728  PPH_TREENEW_COLUMN column;
1729 
1730  if (sortOrder != NoSortOrder)
1731  {
1732  if (!(column = PhTnpLookupColumnById(Context, sortColumn)))
1733  return FALSE;
1734  }
1735  else
1736  {
1737  sortColumn = 0;
1738  column = NULL;
1739  }
1740 
1741  Context->SortColumn = sortColumn;
1742  Context->SortOrder = sortOrder;
1743 
1744  PhTnpSetColumnHeaderSortIcon(Context, column);
1745 
1746  Context->Callback(Context->Handle, TreeNewSortChanged, NULL, NULL, Context->CallbackContext);
1747  }
1748  return TRUE;
1749  case TNM_SETTRISTATE:
1750  Context->TriState = !!WParam;
1751  return TRUE;
1752  case TNM_ENSUREVISIBLE:
1753  return PhTnpEnsureVisibleNode(Context, ((PPH_TREENEW_NODE)LParam)->Index);
1754  case TNM_SCROLL:
1755  PhTnpScroll(Context, (LONG)WParam, (LONG)LParam);
1756  return TRUE;
1757  case TNM_GETFLATNODECOUNT:
1758  return (LRESULT)Context->FlatList->Count;
1759  case TNM_GETFLATNODE:
1760  {
1761  ULONG index = (ULONG)WParam;
1762 
1763  if (index >= Context->FlatList->Count)
1764  return (LRESULT)NULL;
1765 
1766  return (LRESULT)Context->FlatList->Items[index];
1767  }
1768  break;
1769  case TNM_GETCELLTEXT:
1770  {
1772 
1773  return PhTnpGetCellText(
1774  Context,
1775  getCellText->Node,
1776  getCellText->Id,
1777  &getCellText->Text
1778  );
1779  }
1780  break;
1781  case TNM_SETNODEEXPANDED:
1782  PhTnpSetExpandedNode(Context, (PPH_TREENEW_NODE)LParam, !!WParam);
1783  return TRUE;
1784  case TNM_GETMAXID:
1785  return (LRESULT)(Context->NextId - 1);
1786  case TNM_SETMAXID:
1787  {
1788  ULONG maxId = (ULONG)WParam;
1789 
1790  if (Context->NextId < maxId + 1)
1791  {
1792  Context->NextId = maxId + 1;
1793 
1794  if (Context->AllocatedColumns < Context->NextId)
1795  {
1796  PhTnpExpandAllocatedColumns(Context);
1797  }
1798  }
1799  }
1800  return TRUE;
1801  case TNM_INVALIDATENODE:
1802  {
1803  PPH_TREENEW_NODE node = (PPH_TREENEW_NODE)LParam;
1804  RECT rect;
1805 
1806  if (!node->Visible)
1807  return FALSE;
1808 
1809  if (!PhTnpGetRowRects(Context, node->Index, node->Index, TRUE, &rect))
1810  return FALSE;
1811 
1812  InvalidateRect(hwnd, &rect, FALSE);
1813  }
1814  return TRUE;
1815  case TNM_INVALIDATENODES:
1816  {
1817  RECT rect;
1818 
1819  if (!PhTnpGetRowRects(Context, (ULONG)WParam, (ULONG)LParam, TRUE, &rect))
1820  return FALSE;
1821 
1822  InvalidateRect(hwnd, &rect, FALSE);
1823  }
1824  return TRUE;
1825  case TNM_GETFIXEDHEADER:
1826  return (LRESULT)Context->FixedHeaderHandle;
1827  case TNM_GETHEADER:
1828  return (LRESULT)Context->HeaderHandle;
1829  case TNM_GETTOOLTIPS:
1830  return (LRESULT)Context->TooltipsHandle;
1831  case TNM_SELECTRANGE:
1832  case TNM_DESELECTRANGE:
1833  {
1834  ULONG flags;
1835  ULONG changedStart;
1836  ULONG changedEnd;
1837  RECT rect;
1838 
1839  flags = 0;
1840 
1841  if (Message == TNM_DESELECTRANGE)
1842  flags |= TN_SELECT_DESELECT;
1843 
1844  PhTnpSelectRange(Context, (ULONG)WParam, (ULONG)LParam, flags, &changedStart, &changedEnd);
1845 
1846  if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
1847  {
1848  InvalidateRect(hwnd, &rect, FALSE);
1849  }
1850  }
1851  return TRUE;
1852  case TNM_GETCOLUMNCOUNT:
1853  return (LRESULT)Context->NumberOfColumns;
1854  case TNM_SETREDRAW:
1855  PhTnpSetRedraw(Context, !!WParam);
1856  return (LRESULT)Context->EnableRedraw;
1857  case TNM_GETVIEWPARTS:
1858  {
1860 
1861  parts->ClientRect = Context->ClientRect;
1862  parts->HeaderHeight = Context->HeaderHeight;
1863  parts->RowHeight = Context->RowHeight;
1864  parts->VScrollWidth = Context->VScrollVisible ? Context->VScrollWidth : 0;
1865  parts->HScrollHeight = Context->HScrollHeight ? Context->HScrollHeight : 0;
1866  parts->VScrollPosition = Context->VScrollPosition;
1867  parts->HScrollPosition = Context->HScrollPosition;
1868  parts->FixedWidth = Context->FixedWidth;
1869  parts->NormalLeft = Context->NormalLeft;
1870  parts->NormalWidth = Context->TotalViewX;
1871  }
1872  return TRUE;
1873  case TNM_GETFIXEDCOLUMN:
1874  return (LRESULT)Context->FixedColumn;
1875  case TNM_GETFIRSTCOLUMN:
1876  return (LRESULT)Context->FirstColumn;
1877  case TNM_SETFOCUSNODE:
1878  Context->FocusNode = (PPH_TREENEW_NODE)LParam;
1879  return TRUE;
1880  case TNM_SETMARKNODE:
1881  Context->MarkNodeIndex = ((PPH_TREENEW_NODE)LParam)->Index;
1882  return TRUE;
1883  case TNM_SETHOTNODE:
1884  PhTnpSetHotNode(Context, (PPH_TREENEW_NODE)LParam, FALSE);
1885  return TRUE;
1886  case TNM_SETEXTENDEDFLAGS:
1887  Context->ExtendedFlags = (Context->ExtendedFlags & ~(ULONG)WParam) | ((ULONG)LParam & (ULONG)WParam);
1888  return TRUE;
1889  case TNM_GETCALLBACK:
1890  {
1891  PPH_TREENEW_CALLBACK *callback = (PPH_TREENEW_CALLBACK *)LParam;
1892  PVOID *callbackContext = (PVOID *)WParam;
1893 
1894  if (callback)
1895  {
1896  if (Context->Callback != PhTnpNullCallback)
1897  *callback = Context->Callback;
1898  else
1899  *callback = NULL;
1900  }
1901 
1902  if (callbackContext)
1903  {
1904  *callbackContext = Context->CallbackContext;
1905  }
1906  }
1907  return TRUE;
1908  case TNM_HITTEST:
1909  PhTnpHitTest(Context, (PPH_TREENEW_HIT_TEST)LParam);
1910  return TRUE;
1912  return Context->NumberOfColumnsByDisplay + (Context->FixedColumnVisible ? 1 : 0);
1913  case TNM_AUTOSIZECOLUMN:
1914  {
1915  ULONG id = (ULONG)WParam;
1916  ULONG flags = (ULONG)LParam;
1917  PPH_TREENEW_COLUMN column;
1918 
1919  if (!(column = PhTnpLookupColumnById(Context, id)))
1920  return FALSE;
1921 
1922  if (!column->Visible)
1923  return FALSE;
1924 
1926  Context,
1927  column->Fixed ? Context->FixedHeaderHandle : Context->HeaderHandle,
1928  column,
1929  flags
1930  );
1931  }
1932  return TRUE;
1933  case TNM_SETEMPTYTEXT:
1934  {
1935  PPH_STRINGREF text = (PPH_STRINGREF)LParam;
1936  ULONG flags = (ULONG)WParam;
1937 
1938  Context->EmptyText = *text;
1939  }
1940  return TRUE;
1941  case TNM_SETROWHEIGHT:
1942  {
1943  LONG rowHeight = (LONG)WParam;
1944 
1945  if (rowHeight != 0)
1946  {
1947  Context->CustomRowHeight = TRUE;
1948  Context->RowHeight = rowHeight;
1949  }
1950  else
1951  {
1952  Context->CustomRowHeight = FALSE;
1953  PhTnpUpdateTextMetrics(Context);
1954  }
1955  }
1956  return TRUE;
1957  }
1958 
1959  return 0;
1960 }
1961 
1963  _In_ PPH_TREENEW_CONTEXT Context,
1964  _In_opt_ HFONT Font,
1965  _In_ BOOLEAN Redraw
1966  )
1967 {
1968  if (Context->FontOwned)
1969  {
1970  DeleteObject(Context->Font);
1971  Context->FontOwned = FALSE;
1972  }
1973 
1974  Context->Font = Font;
1975 
1976  if (!Context->Font)
1977  {
1978  LOGFONT logFont;
1979 
1980  if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &logFont, 0))
1981  {
1982  Context->Font = CreateFontIndirect(&logFont);
1983  Context->FontOwned = TRUE;
1984  }
1985  }
1986 
1987  SendMessage(Context->FixedHeaderHandle, WM_SETFONT, (WPARAM)Context->Font, Redraw);
1988  SendMessage(Context->HeaderHandle, WM_SETFONT, (WPARAM)Context->Font, Redraw);
1989 
1990  if (Context->TooltipsHandle)
1991  {
1992  SendMessage(Context->TooltipsHandle, WM_SETFONT, (WPARAM)Context->Font, FALSE);
1993  Context->TooltipFont = Context->Font;
1994  }
1995 
1996  PhTnpUpdateTextMetrics(Context);
1997 }
1998 
2000  _In_ PPH_TREENEW_CONTEXT Context
2001  )
2002 {
2003  Context->VScrollWidth = GetSystemMetrics(SM_CXVSCROLL);
2004  Context->HScrollHeight = GetSystemMetrics(SM_CYHSCROLL);
2005  Context->SystemBorderX = GetSystemMetrics(SM_CXBORDER);
2006  Context->SystemBorderY = GetSystemMetrics(SM_CYBORDER);
2007  Context->SystemEdgeX = GetSystemMetrics(SM_CXEDGE);
2008  Context->SystemEdgeY = GetSystemMetrics(SM_CYEDGE);
2009  Context->SystemDragX = GetSystemMetrics(SM_CXDRAG);
2010  Context->SystemDragY = GetSystemMetrics(SM_CYDRAG);
2011 
2012  if (Context->SystemDragX < 2)
2013  Context->SystemDragX = 2;
2014  if (Context->SystemDragY < 2)
2015  Context->SystemDragY = 2;
2016 }
2017 
2019  _In_ PPH_TREENEW_CONTEXT Context
2020  )
2021 {
2022  HDC hdc;
2023 
2024  if (hdc = GetDC(Context->Handle))
2025  {
2026  SelectObject(hdc, Context->Font);
2027  GetTextMetrics(hdc, &Context->TextMetrics);
2028 
2029  if (!Context->CustomRowHeight)
2030  {
2031  // Below we try to match the row height as calculated by
2032  // the list view, even if it involves magic numbers.
2033  // On Vista and above there seems to be extra padding.
2034 
2035  Context->RowHeight = Context->TextMetrics.tmHeight;
2036 
2037  if (Context->Style & TN_STYLE_ICONS)
2038  {
2039  if (Context->RowHeight < SmallIconHeight)
2040  Context->RowHeight = SmallIconHeight;
2041  }
2042  else
2043  {
2044  if (WindowsVersion >= WINDOWS_VISTA && !(Context->Style & TN_STYLE_THIN_ROWS))
2045  Context->RowHeight += 1; // HACK
2046  }
2047 
2048  Context->RowHeight += 1; // HACK
2049 
2050  if (WindowsVersion >= WINDOWS_VISTA && !(Context->Style & TN_STYLE_THIN_ROWS))
2051  Context->RowHeight += 2; // HACK
2052  }
2053 
2054  ReleaseDC(Context->Handle, hdc);
2055  }
2056 }
2057 
2059  _In_ PPH_TREENEW_CONTEXT Context
2060  )
2061 {
2062  if (
2063  IsThemeActive_I &&
2064  OpenThemeData_I &&
2065  CloseThemeData_I &&
2069  )
2070  {
2071  Context->ThemeActive = !!IsThemeActive_I();
2072 
2073  if (Context->ThemeData)
2074  {
2075  CloseThemeData_I(Context->ThemeData);
2076  Context->ThemeData = NULL;
2077  }
2078 
2079  Context->ThemeData = OpenThemeData_I(Context->Handle, L"TREEVIEW");
2080 
2081  if (Context->ThemeData)
2082  {
2083  Context->ThemeHasItemBackground = !!IsThemePartDefined_I(Context->ThemeData, TVP_TREEITEM, 0);
2084  Context->ThemeHasGlyph = !!IsThemePartDefined_I(Context->ThemeData, TVP_GLYPH, 0);
2085  Context->ThemeHasHotGlyph = !!IsThemePartDefined_I(Context->ThemeData, TVP_HOTGLYPH, 0);
2086  }
2087  else
2088  {
2089  Context->ThemeHasItemBackground = FALSE;
2090  Context->ThemeHasGlyph = FALSE;
2091  Context->ThemeHasHotGlyph = FALSE;
2092  }
2093  }
2094  else
2095  {
2096  Context->ThemeData = NULL;
2097  Context->ThemeActive = FALSE;
2098  Context->ThemeHasItemBackground = FALSE;
2099  Context->ThemeHasGlyph = FALSE;
2100  Context->ThemeHasHotGlyph = FALSE;
2101  }
2102 }
2103 
2105  _In_ PPH_TREENEW_CONTEXT Context
2106  )
2107 {
2108  if (!Context->ThemeInitialized)
2109  {
2110  PhTnpUpdateThemeData(Context);
2111  Context->ThemeInitialized = TRUE;
2112  }
2113 }
2114 
2116  _In_ PPH_TREENEW_CONTEXT Context
2117  )
2118 {
2119  PhTnpSetFixedWidth(Context, Context->TrackOldFixedWidth);
2120  ReleaseCapture();
2121 }
2122 
2124  _In_ PPH_TREENEW_CONTEXT Context
2125  )
2126 {
2127  RECT clientRect;
2128 
2129  if (Context->EnableRedraw <= 0)
2130  {
2131  Context->SuspendUpdateLayout = TRUE;
2132  return;
2133  }
2134 
2135  clientRect = Context->ClientRect;
2136 
2137  PhTnpUpdateScrollBars(Context);
2138 
2139  // Vertical scroll bar
2140  if (Context->VScrollVisible)
2141  {
2142  MoveWindow(
2143  Context->VScrollHandle,
2144  clientRect.right - Context->VScrollWidth,
2145  0,
2146  Context->VScrollWidth,
2147  clientRect.bottom - (Context->HScrollVisible ? Context->HScrollHeight : 0),
2148  TRUE
2149  );
2150  }
2151 
2152  // Horizontal scroll bar
2153  if (Context->HScrollVisible)
2154  {
2155  MoveWindow(
2156  Context->HScrollHandle,
2157  Context->NormalLeft,
2158  clientRect.bottom - Context->HScrollHeight,
2159  clientRect.right - Context->NormalLeft - (Context->VScrollVisible ? Context->VScrollWidth : 0),
2160  Context->HScrollHeight,
2161  TRUE
2162  );
2163  }
2164 
2165  // Filler box
2166  if (Context->VScrollVisible && Context->HScrollVisible)
2167  {
2168  MoveWindow(
2169  Context->FillerBoxHandle,
2170  clientRect.right - Context->VScrollWidth,
2171  clientRect.bottom - Context->HScrollHeight,
2172  Context->VScrollWidth,
2173  Context->HScrollHeight,
2174  TRUE
2175  );
2176  }
2177 
2178  PhTnpLayoutHeader(Context);
2179 
2180  // Redraw the entire window if we are displaying empty text.
2181  if (Context->FlatList->Count == 0 && Context->EmptyText.Length != 0)
2182  InvalidateRect(Context->Handle, NULL, FALSE);
2183 }
2184 
2186  _In_ PPH_TREENEW_CONTEXT Context
2187  )
2188 {
2189  RECT rect;
2190  HDLAYOUT hdl;
2191  WINDOWPOS windowPos;
2192 
2193  hdl.prc = &rect;
2194  hdl.pwpos = &windowPos;
2195 
2196  if (!(Context->Style & TN_STYLE_NO_COLUMN_HEADER))
2197  {
2198  // Fixed portion header control
2199  rect.left = 0;
2200  rect.top = 0;
2201  rect.right = Context->NormalLeft;
2202  rect.bottom = Context->ClientRect.bottom;
2203  Header_Layout(Context->FixedHeaderHandle, &hdl);
2204  SetWindowPos(Context->FixedHeaderHandle, NULL, windowPos.x, windowPos.y, windowPos.cx, windowPos.cy, windowPos.flags);
2205  Context->HeaderHeight = windowPos.cy;
2206 
2207  // Normal portion header control
2208  rect.left = Context->NormalLeft - Context->HScrollPosition;
2209  rect.top = 0;
2210  rect.right = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0);
2211  rect.bottom = Context->ClientRect.bottom;
2212  Header_Layout(Context->HeaderHandle, &hdl);
2213  SetWindowPos(Context->HeaderHandle, NULL, windowPos.x, windowPos.y, windowPos.cx, windowPos.cy, windowPos.flags);
2214  }
2215  else
2216  {
2217  Context->HeaderHeight = 0;
2218  }
2219 
2220  if (Context->TooltipsHandle)
2221  {
2222  TOOLINFO toolInfo;
2223 
2224  memset(&toolInfo, 0, sizeof(TOOLINFO));
2225  toolInfo.cbSize = sizeof(TOOLINFO);
2226  toolInfo.hwnd = Context->FixedHeaderHandle;
2227  toolInfo.uId = TNP_TOOLTIPS_FIXED_HEADER;
2228  GetClientRect(Context->FixedHeaderHandle, &toolInfo.rect);
2229  SendMessage(Context->TooltipsHandle, TTM_NEWTOOLRECT, 0, (LPARAM)&toolInfo);
2230 
2231  toolInfo.hwnd = Context->HeaderHandle;
2232  toolInfo.uId = TNP_TOOLTIPS_HEADER;
2233  GetClientRect(Context->HeaderHandle, &toolInfo.rect);
2234  SendMessage(Context->TooltipsHandle, TTM_NEWTOOLRECT, 0, (LPARAM)&toolInfo);
2235  }
2236 }
2237 
2239  _In_ PPH_TREENEW_CONTEXT Context,
2240  _In_ ULONG FixedWidth
2241  )
2242 {
2243  HDITEM item;
2244 
2245  if (Context->FixedColumnVisible)
2246  {
2247  Context->FixedWidth = FixedWidth;
2248 
2249  if (Context->FixedWidth < Context->FixedWidthMinimum)
2250  Context->FixedWidth = Context->FixedWidthMinimum;
2251 
2252  Context->NormalLeft = Context->FixedWidth + 1;
2253 
2254  item.mask = HDI_WIDTH;
2255  item.cxy = Context->FixedWidth + 1;
2256  Header_SetItem(Context->FixedHeaderHandle, 0, &item);
2257  }
2258  else
2259  {
2260  Context->FixedWidth = 0;
2261  Context->NormalLeft = 0;
2262  }
2263 }
2264 
2266  _In_ PPH_TREENEW_CONTEXT Context,
2267  _In_ BOOLEAN Redraw
2268  )
2269 {
2270  if (Redraw)
2271  Context->EnableRedraw++;
2272  else
2273  Context->EnableRedraw--;
2274 
2275  if (Context->EnableRedraw == 1)
2276  {
2277  if (Context->SuspendUpdateStructure)
2278  {
2279  PhTnpRestructureNodes(Context);
2280  }
2281 
2282  if (Context->SuspendUpdateLayout)
2283  {
2284  PhTnpLayout(Context);
2285  }
2286 
2287  if (Context->SuspendUpdateMoveMouse)
2288  {
2289  POINT point;
2290 
2291  PhTnpGetMessagePos(Context->Handle, &point);
2292  PhTnpProcessMoveMouse(Context, point.x, point.y);
2293  }
2294 
2295  Context->SuspendUpdateStructure = FALSE;
2296  Context->SuspendUpdateLayout = FALSE;
2297  Context->SuspendUpdateMoveMouse = FALSE;
2298 
2299  if (Context->SuspendUpdateRegion)
2300  {
2301  InvalidateRgn(Context->Handle, Context->SuspendUpdateRegion, FALSE);
2302  DeleteObject(Context->SuspendUpdateRegion);
2303  Context->SuspendUpdateRegion = NULL;
2304  }
2305  }
2306 }
2307 
2309  _In_ PPH_TREENEW_CONTEXT Context,
2310  _In_ PH_TREENEW_MESSAGE Message,
2311  _In_ LONG CursorX,
2312  _In_ LONG CursorY,
2313  _In_ PPH_TREENEW_NODE Node,
2314  _In_ PPH_TREENEW_COLUMN Column,
2315  _In_ ULONG VirtualKeys
2316  )
2317 {
2318  PH_TREENEW_MOUSE_EVENT mouseEvent;
2319 
2320  mouseEvent.Location.x = CursorX;
2321  mouseEvent.Location.y = CursorY;
2322  mouseEvent.Node = Node;
2323  mouseEvent.Column = Column;
2324  mouseEvent.KeyFlags = VirtualKeys;
2325  Context->Callback(Context->Handle, Message, &mouseEvent, NULL, Context->CallbackContext);
2326 }
2327 
2329  _In_ PPH_TREENEW_CONTEXT Context,
2330  _In_ ULONG Id
2331  )
2332 {
2333  if (Id >= Context->AllocatedColumns)
2334  return NULL;
2335 
2336  return Context->Columns[Id];
2337 }
2338 
2340  _In_ PPH_TREENEW_CONTEXT Context,
2341  _In_ PPH_TREENEW_COLUMN Column
2342  )
2343 {
2344  PPH_TREENEW_COLUMN realColumn;
2345 
2346  // Check if a column with the same ID already exists.
2347  if (Column->Id < Context->AllocatedColumns && Context->Columns[Column->Id])
2348  return FALSE;
2349 
2350  if (Context->NextId < Column->Id + 1)
2351  Context->NextId = Column->Id + 1;
2352 
2353  realColumn = PhAllocateCopy(Column, sizeof(PH_TREENEW_COLUMN));
2354 
2355  if (Context->AllocatedColumns < Context->NextId)
2356  {
2357  PhTnpExpandAllocatedColumns(Context);
2358  }
2359 
2360  Context->Columns[Column->Id] = realColumn;
2361  Context->NumberOfColumns++;
2362 
2363  if (realColumn->Fixed)
2364  {
2365  if (Context->FixedColumn)
2366  {
2367  // We already have a fixed column, and we can't have two. Make this new column un-fixed.
2368  realColumn->Fixed = FALSE;
2369  }
2370  else
2371  {
2372  Context->FixedColumn = realColumn;
2373  }
2374 
2375  realColumn->DisplayIndex = 0;
2376  realColumn->s.ViewX = 0;
2377  }
2378 
2379  if (realColumn->Visible)
2380  {
2381  BOOLEAN updateHeaders;
2382 
2383  updateHeaders = FALSE;
2384 
2385  if (!realColumn->Fixed && realColumn->DisplayIndex != Header_GetItemCount(Context->HeaderHandle))
2386  updateHeaders = TRUE;
2387 
2388  realColumn->s.ViewIndex = PhTnpInsertColumnHeader(Context, realColumn);
2389 
2390  if (updateHeaders)
2391  PhTnpUpdateColumnHeaders(Context);
2392  }
2393  else
2394  {
2395  realColumn->s.ViewIndex = -1;
2396  }
2397 
2398  PhTnpUpdateColumnMaps(Context);
2399 
2400  if (realColumn->Visible)
2401  PhTnpLayout(Context);
2402 
2403  return TRUE;
2404 }
2405 
2407  _In_ PPH_TREENEW_CONTEXT Context,
2408  _In_ ULONG Id
2409  )
2410 {
2411  PPH_TREENEW_COLUMN realColumn;
2412  BOOLEAN updateLayout;
2413 
2414  if (!(realColumn = PhTnpLookupColumnById(Context, Id)))
2415  return FALSE;
2416 
2417  updateLayout = FALSE;
2418 
2419  if (realColumn->Visible)
2420  updateLayout = TRUE;
2421 
2422  PhTnpDeleteColumnHeader(Context, realColumn);
2423  Context->Columns[realColumn->Id] = NULL;
2424  PhFree(realColumn);
2425  PhTnpUpdateColumnMaps(Context);
2426 
2427  if (updateLayout)
2428  PhTnpLayout(Context);
2429 
2430  Context->NumberOfColumns--;
2431 
2432  return TRUE;
2433 }
2434 
2436  _In_ PPH_TREENEW_CONTEXT Context,
2437  _In_ ULONG Id,
2438  _Out_ PPH_TREENEW_COLUMN Column
2439  )
2440 {
2441  PPH_TREENEW_COLUMN realColumn;
2442 
2443  if (!(realColumn = PhTnpLookupColumnById(Context, Id)))
2444  return FALSE;
2445 
2446  memcpy(Column, realColumn, sizeof(PH_TREENEW_COLUMN));
2447 
2448  return TRUE;
2449 }
2450 
2452  _In_ PPH_TREENEW_CONTEXT Context,
2453  _In_ ULONG Mask,
2454  _In_ ULONG Id,
2455  _In_ PPH_TREENEW_COLUMN Column
2456  )
2457 {
2458  PPH_TREENEW_COLUMN realColumn;
2459  BOOLEAN addingOrRemoving;
2460 
2461  if (!(realColumn = PhTnpLookupColumnById(Context, Id)))
2462  return FALSE;
2463 
2464  addingOrRemoving = FALSE;
2465 
2466  if (Mask & TN_COLUMN_FLAG_VISIBLE)
2467  {
2468  if (realColumn->Visible != Column->Visible)
2469  {
2470  addingOrRemoving = TRUE;
2471  }
2472  }
2473 
2474  if (Mask & TN_COLUMN_FLAG_CUSTOMDRAW)
2475  {
2476  realColumn->CustomDraw = Column->CustomDraw;
2477  }
2478 
2479  if (Mask & TN_COLUMN_FLAG_SORTDESCENDING)
2480  {
2481  realColumn->SortDescending = Column->SortDescending;
2482  }
2483 
2485  {
2486  BOOLEAN updateHeaders;
2487  BOOLEAN updateMaps;
2488  BOOLEAN updateLayout;
2489 
2490  updateHeaders = FALSE;
2491  updateMaps = FALSE;
2492  updateLayout = FALSE;
2493 
2494  if (Mask & TN_COLUMN_TEXT)
2495  {
2496  realColumn->Text = Column->Text;
2497  }
2498 
2499  if (Mask & TN_COLUMN_WIDTH)
2500  {
2501  realColumn->Width = Column->Width;
2502  updateMaps = TRUE;
2503  }
2504 
2505  if (Mask & TN_COLUMN_ALIGNMENT)
2506  {
2507  realColumn->Alignment = Column->Alignment;
2508  }
2509 
2510  if (Mask & TN_COLUMN_DISPLAYINDEX)
2511  {
2512  realColumn->DisplayIndex = Column->DisplayIndex;
2513  updateHeaders = TRUE;
2514  updateMaps = TRUE;
2515  updateLayout = TRUE;
2516  }
2517 
2518  if (!addingOrRemoving && realColumn->Visible)
2519  {
2520  PhTnpChangeColumnHeader(Context, Mask, realColumn);
2521 
2522  if (updateHeaders)
2523  PhTnpUpdateColumnHeaders(Context);
2524  if (updateMaps)
2525  PhTnpUpdateColumnMaps(Context);
2526  if (updateLayout)
2527  PhTnpLayout(Context);
2528  }
2529  }
2530 
2531  if (Mask & TN_COLUMN_CONTEXT)
2532  {
2533  realColumn->Context = Column->Context;
2534  }
2535 
2536  if (Mask & TN_COLUMN_TEXTFLAGS)
2537  {
2538  realColumn->TextFlags = Column->TextFlags;
2539  }
2540 
2541  if (addingOrRemoving)
2542  {
2543  if (Column->Visible)
2544  {
2545  BOOLEAN updateHeaders;
2546 
2547  updateHeaders = FALSE;
2548 
2549  if (realColumn->Fixed)
2550  {
2551  realColumn->DisplayIndex = 0;
2552  }
2553  else
2554  {
2555  if (Mask & TN_COLUMN_DISPLAYINDEX)
2556  updateHeaders = TRUE;
2557  else
2558  realColumn->DisplayIndex = Header_GetItemCount(Context->HeaderHandle);
2559  }
2560 
2561  realColumn->s.ViewIndex = PhTnpInsertColumnHeader(Context, realColumn);
2562 
2563  if (updateHeaders)
2564  PhTnpUpdateColumnHeaders(Context);
2565  }
2566  else
2567  {
2568  PhTnpDeleteColumnHeader(Context, realColumn);
2569  }
2570 
2571  PhTnpUpdateColumnMaps(Context);
2572  PhTnpLayout(Context);
2573  }
2574 
2575  return TRUE;
2576 }
2577 
2579  _In_ PPH_TREENEW_CONTEXT Context
2580  )
2581 {
2582  if (Context->Columns)
2583  {
2584  ULONG oldAllocatedColumns;
2585 
2586  oldAllocatedColumns = Context->AllocatedColumns;
2587  Context->AllocatedColumns *= 2;
2588 
2589  if (Context->AllocatedColumns < Context->NextId)
2590  Context->AllocatedColumns = Context->NextId;
2591 
2592  Context->Columns = PhReAllocate(
2593  Context->Columns,
2594  Context->AllocatedColumns * sizeof(PPH_TREENEW_COLUMN)
2595  );
2596 
2597  // Zero the newly allocated portion.
2598  memset(
2599  &Context->Columns[oldAllocatedColumns],
2600  0,
2601  (Context->AllocatedColumns - oldAllocatedColumns) * sizeof(PPH_TREENEW_COLUMN)
2602  );
2603  }
2604  else
2605  {
2606  Context->AllocatedColumns = 16;
2607 
2608  if (Context->AllocatedColumns < Context->NextId)
2609  Context->AllocatedColumns = Context->NextId;
2610 
2611  Context->Columns = PhAllocate(
2612  Context->AllocatedColumns * sizeof(PPH_TREENEW_COLUMN)
2613  );
2614  memset(Context->Columns, 0, Context->AllocatedColumns * sizeof(PPH_TREENEW_COLUMN));
2615  }
2616 }
2617 
2619  _In_ PPH_TREENEW_CONTEXT Context
2620  )
2621 {
2622  ULONG i;
2623  LONG x;
2624 
2625  if (Context->AllocatedColumnsByDisplay < Context->NumberOfColumns)
2626  {
2627  if (Context->ColumnsByDisplay)
2628  PhFree(Context->ColumnsByDisplay);
2629 
2630  Context->ColumnsByDisplay = PhAllocate(sizeof(PPH_TREENEW_COLUMN) * Context->NumberOfColumns);
2631  Context->AllocatedColumnsByDisplay = Context->NumberOfColumns;
2632  }
2633 
2634  memset(Context->ColumnsByDisplay, 0, sizeof(PPH_TREENEW_COLUMN) * Context->AllocatedColumnsByDisplay);
2635 
2636  for (i = 0; i < Context->NextId; i++)
2637  {
2638  if (!Context->Columns[i])
2639  continue;
2640 
2641  if (Context->Columns[i]->Visible && !Context->Columns[i]->Fixed && Context->Columns[i]->DisplayIndex != -1)
2642  {
2643  if (Context->Columns[i]->DisplayIndex >= Context->NumberOfColumns)
2644  PhRaiseStatus(STATUS_INTERNAL_ERROR);
2645 
2646  Context->ColumnsByDisplay[Context->Columns[i]->DisplayIndex] = Context->Columns[i];
2647  }
2648  }
2649 
2650  x = 0;
2651 
2652  for (i = 0; i < Context->AllocatedColumnsByDisplay; i++)
2653  {
2654  if (!Context->ColumnsByDisplay[i])
2655  break;
2656 
2657  Context->ColumnsByDisplay[i]->s.ViewX = x;
2658  x += Context->ColumnsByDisplay[i]->Width;
2659  }
2660 
2661  Context->NumberOfColumnsByDisplay = i;
2662  Context->TotalViewX = x;
2663 
2664  if (Context->FixedColumnVisible)
2665  Context->FirstColumn = Context->FixedColumn;
2666  else if (Context->NumberOfColumnsByDisplay != 0)
2667  Context->FirstColumn = Context->ColumnsByDisplay[0];
2668  else
2669  Context->FirstColumn = NULL;
2670 
2671  if (Context->NumberOfColumnsByDisplay != 0)
2672  Context->LastColumn = Context->ColumnsByDisplay[Context->NumberOfColumnsByDisplay - 1];
2673  else if (Context->FixedColumnVisible)
2674  Context->LastColumn = Context->FixedColumn;
2675  else
2676  Context->LastColumn = NULL;
2677 }
2678 
2680  _In_ PPH_TREENEW_CONTEXT Context,
2681  _In_ PPH_TREENEW_COLUMN Column
2682  )
2683 {
2684  HDITEM item;
2685 
2686  if (Column->Fixed)
2687  {
2688  if (Column->Width < Context->FixedWidthMinimum)
2689  Column->Width = Context->FixedWidthMinimum;
2690 
2691  Context->FixedWidth = Column->Width;
2692  Context->NormalLeft = Context->FixedWidth + 1;
2693  Context->FixedColumnVisible = TRUE;
2694 
2695  if (!(Context->Style & TN_STYLE_NO_DIVIDER))
2696  Context->FixedDividerVisible = TRUE;
2697  }
2698 
2699  memset(&item, 0, sizeof(HDITEM));
2700  item.mask = HDI_WIDTH | HDI_TEXT | HDI_FORMAT | HDI_LPARAM | HDI_ORDER;
2701  item.cxy = Column->Width;
2702  item.pszText = Column->Text;
2703  item.fmt = 0;
2704  item.lParam = (LPARAM)Column;
2705 
2706  if (Column->Fixed)
2707  item.cxy++;
2708 
2709  if (Column->Fixed)
2710  item.iOrder = 0;
2711  else
2712  item.iOrder = Column->DisplayIndex;
2713 
2714  if (Column->Alignment & PH_ALIGN_LEFT)
2715  item.fmt |= HDF_LEFT;
2716  else if (Column->Alignment & PH_ALIGN_RIGHT)
2717  item.fmt |= HDF_RIGHT;
2718  else
2719  item.fmt |= HDF_CENTER;
2720 
2721  if (Column->Id == Context->SortColumn)
2722  {
2723  if (Context->SortOrder == AscendingSortOrder)
2724  item.fmt |= HDF_SORTUP;
2725  else if (Context->SortOrder == DescendingSortOrder)
2726  item.fmt |= HDF_SORTDOWN;
2727  }
2728 
2729  Column->Visible = TRUE;
2730 
2731  if (Column->Fixed)
2732  return Header_InsertItem(Context->FixedHeaderHandle, 0, &item);
2733  else
2734  return Header_InsertItem(Context->HeaderHandle, MAXINT, &item);
2735 }
2736 
2738  _In_ PPH_TREENEW_CONTEXT Context,
2739  _In_ ULONG Mask,
2740  _In_ PPH_TREENEW_COLUMN Column
2741  )
2742 {
2743  HDITEM item;
2744 
2745  memset(&item, 0, sizeof(HDITEM));
2746  item.mask = 0;
2747 
2748  if (Mask & TN_COLUMN_TEXT)
2749  {
2750  item.mask |= HDI_TEXT;
2751  item.pszText = Column->Text;
2752  }
2753 
2754  if (Mask & TN_COLUMN_WIDTH)
2755  {
2756  item.mask |= HDI_WIDTH;
2757  item.cxy = Column->Width;
2758 
2759  if (Column->Fixed)
2760  item.cxy++;
2761  }
2762 
2763  if (Mask & TN_COLUMN_ALIGNMENT)
2764  {
2765  item.mask |= HDI_FORMAT;
2766  item.fmt = 0;
2767 
2768  if (Column->Alignment & PH_ALIGN_LEFT)
2769  item.fmt |= HDF_LEFT;
2770  else if (Column->Alignment & PH_ALIGN_RIGHT)
2771  item.fmt |= HDF_RIGHT;
2772  else
2773  item.fmt |= HDF_CENTER;
2774 
2775  if (Column->Id == Context->SortColumn)
2776  {
2777  if (Context->SortOrder == AscendingSortOrder)
2778  item.fmt |= HDF_SORTUP;
2779  else if (Context->SortOrder == DescendingSortOrder)
2780  item.fmt |= HDF_SORTDOWN;
2781  }
2782  }
2783 
2784  if (Mask & TN_COLUMN_DISPLAYINDEX)
2785  {
2786  item.mask |= HDI_ORDER;
2787 
2788  if (Column->Fixed)
2789  item.iOrder = 0;
2790  else
2791  item.iOrder = Column->DisplayIndex;
2792  }
2793 
2794  if (Column->Fixed)
2795  Header_SetItem(Context->FixedHeaderHandle, 0, &item);
2796  else
2797  Header_SetItem(Context->HeaderHandle, Column->s.ViewIndex, &item);
2798 }
2799 
2801  _In_ PPH_TREENEW_CONTEXT Context,
2802  _Inout_ PPH_TREENEW_COLUMN Column
2803  )
2804 {
2805  if (Column->Fixed)
2806  {
2807  Context->FixedColumn = NULL;
2808  Context->FixedWidth = 0;
2809  Context->NormalLeft = 0;
2810  Context->FixedColumnVisible = FALSE;
2811  Context->FixedDividerVisible = FALSE;
2812  }
2813 
2814  if (Column->Fixed)
2815  Header_DeleteItem(Context->FixedHeaderHandle, Column->s.ViewIndex);
2816  else
2817  Header_DeleteItem(Context->HeaderHandle, Column->s.ViewIndex);
2818 
2819  Column->Visible = FALSE;
2820  Column->s.ViewIndex = -1;
2821  PhTnpUpdateColumnHeaders(Context);
2822 }
2823 
2825  _In_ PPH_TREENEW_CONTEXT Context
2826  )
2827 {
2828  ULONG count;
2829  ULONG i;
2830  HDITEM item;
2831  PPH_TREENEW_COLUMN column;
2832 
2833  item.mask = HDI_WIDTH | HDI_LPARAM | HDI_ORDER;
2834 
2835  // Fixed column
2836 
2837  if (Context->FixedColumnVisible && Header_GetItem(Context->FixedHeaderHandle, 0, &item))
2838  {
2839  column = Context->FixedColumn;
2840  column->Width = item.cxy - 1;
2841  }
2842 
2843  // Normal columns
2844 
2845  count = Header_GetItemCount(Context->HeaderHandle);
2846 
2847  if (count != -1)
2848  {
2849  for (i = 0; i < count; i++)
2850  {
2851  if (Header_GetItem(Context->HeaderHandle, i, &item))
2852  {
2853  column = (PPH_TREENEW_COLUMN)item.lParam;
2854  column->s.ViewIndex = i;
2855  column->Width = item.cxy;
2856  column->DisplayIndex = item.iOrder;
2857  }
2858  }
2859  }
2860 }
2861 
2863  _In_ PPH_TREENEW_CONTEXT Context,
2864  _In_ PPH_TREENEW_COLUMN Column,
2865  _In_ LONG Delta
2866  )
2867 {
2868  RECT rect;
2869  LONG columnLeft;
2870 
2871  if (Column->Fixed)
2872  {
2873  columnLeft = 0;
2874  }
2875  else
2876  {
2877  columnLeft = Context->NormalLeft + Column->s.ViewX - Context->HScrollPosition;
2878  }
2879 
2880  // Scroll the content to the right of the column.
2881  // Clip the scroll area to the new width, or the old width if that is further to the left.
2882  // We may have the WS_CLIPCHILDREN style set, so we need to remove the horizontal scrollbar
2883  // from the rectangle, otherwise ScrollWindowEx will want to invalidate the entire region! (The
2884  // horizontal scrollbar is an overlapping child control.)
2885  rect.left = columnLeft + Column->Width;
2886  rect.top = Context->HeaderHeight;
2887  rect.right = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0);
2888  rect.bottom = Context->ClientRect.bottom - (Context->HScrollVisible ? Context->HScrollHeight : 0);
2889 
2890  if (Delta > 0)
2891  rect.left -= Delta; // old width
2892 
2893  // Scroll the window.
2894  ScrollWindowEx(
2895  Context->Handle,
2896  Delta,
2897  0,
2898  &rect,
2899  &rect,
2900  NULL,
2901  NULL,
2902  SW_INVALIDATE
2903  );
2904 
2905  UpdateWindow(Context->Handle); // required
2906 
2907  if (Context->HScrollVisible)
2908  {
2909  // We excluded the bottom region - invalidate it now.
2910  rect.top = rect.bottom;
2911  rect.bottom = Context->ClientRect.bottom;
2912  InvalidateRect(Context->Handle, &rect, FALSE);
2913  }
2914 
2915  PhTnpLayout(Context);
2916 
2917  // Redraw the whole column because the content may depend on the width (e.g. text ellipsis).
2918  rect.left = columnLeft;
2919  rect.top = Context->HeaderHeight;
2920  rect.right = columnLeft + Column->Width;
2921  RedrawWindow(Context->Handle, &rect, NULL, RDW_INVALIDATE | RDW_UPDATENOW); // must be RedrawWindow
2922 }
2923 
2925  _In_ PPH_TREENEW_CONTEXT Context,
2926  _In_ PPH_TREENEW_COLUMN NewColumn
2927  )
2928 {
2929  if (NewColumn->Id == Context->SortColumn)
2930  {
2931  if (Context->TriState)
2932  {
2933  if (!NewColumn->SortDescending)
2934  {
2935  // Ascending -> Descending -> None
2936 
2937  if (Context->SortOrder == AscendingSortOrder)
2938  Context->SortOrder = DescendingSortOrder;
2939  else if (Context->SortOrder == DescendingSortOrder)
2940  Context->SortOrder = NoSortOrder;
2941  else
2942  Context->SortOrder = AscendingSortOrder;
2943  }
2944  else
2945  {
2946  // Descending -> Ascending -> None
2947 
2948  if (Context->SortOrder == DescendingSortOrder)
2949  Context->SortOrder = AscendingSortOrder;
2950  else if (Context->SortOrder == AscendingSortOrder)
2951  Context->SortOrder = NoSortOrder;
2952  else
2953  Context->SortOrder = DescendingSortOrder;
2954  }
2955  }
2956  else
2957  {
2958  if (Context->SortOrder == AscendingSortOrder)
2959  Context->SortOrder = DescendingSortOrder;
2960  else
2961  Context->SortOrder = AscendingSortOrder;
2962  }
2963  }
2964  else
2965  {
2966  Context->SortColumn = NewColumn->Id;
2967 
2968  if (!NewColumn->SortDescending)
2969  Context->SortOrder = AscendingSortOrder;
2970  else
2971  Context->SortOrder = DescendingSortOrder;
2972  }
2973 
2974  PhTnpSetColumnHeaderSortIcon(Context, NewColumn);
2975 
2976  Context->Callback(Context->Handle, TreeNewSortChanged, NULL, NULL, Context->CallbackContext);
2977 }
2978 
2980  _In_ PPH_TREENEW_CONTEXT Context,
2981  _In_opt_ PPH_TREENEW_COLUMN SortColumnPointer
2982  )
2983 {
2984  if (Context->SortOrder == NoSortOrder)
2985  {
2987  Context->FixedHeaderHandle,
2988  -1,
2989  NoSortOrder
2990  );
2992  Context->HeaderHandle,
2993  -1,
2994  NoSortOrder
2995  );
2996 
2997  return TRUE;
2998  }
2999 
3000  if (!SortColumnPointer)
3001  {
3002  if (!(SortColumnPointer = PhTnpLookupColumnById(Context, Context->SortColumn)))
3003  return FALSE;
3004  }
3005 
3006  if (SortColumnPointer->Fixed)
3007  {
3009  Context->FixedHeaderHandle,
3010  0,
3011  Context->SortOrder
3012  );
3014  Context->HeaderHandle,
3015  -1,
3016  NoSortOrder
3017  );
3018  }
3019  else
3020  {
3022  Context->FixedHeaderHandle,
3023  -1,
3024  NoSortOrder
3025  );
3027  Context->HeaderHandle,
3028  SortColumnPointer->s.ViewIndex,
3029  Context->SortOrder
3030  );
3031  }
3032 
3033  return TRUE;
3034 }
3035 
3037  _In_ PPH_TREENEW_CONTEXT Context,
3038  _In_ HWND HeaderHandle,
3039  _In_ PPH_TREENEW_COLUMN Column,
3040  _In_ ULONG Flags
3041  )
3042 {
3043  LONG newWidth;
3044  HDITEM item;
3045 
3046  if (Flags & TN_AUTOSIZE_REMAINING_SPACE)
3047  {
3048  newWidth = Context->ClientRect.right - (Context->TotalViewX - Column->Width);
3049 
3050  if (Context->FixedColumn)
3051  newWidth -= Context->FixedColumn->Width;
3052  if (Context->VScrollVisible)
3053  newWidth -= Context->VScrollWidth;
3054 
3055  if (newWidth <= 0)
3056  return;
3057  }
3058  else
3059  {
3060  ULONG i;
3061  LONG maximumWidth;
3062  PH_TREENEW_CELL_PARTS parts;
3063  LONG width;
3064 
3065  if (Context->FlatList->Count == 0)
3066  return;
3067  if (Column->CustomDraw)
3068  return;
3069 
3070  maximumWidth = 0;
3071 
3072  for (i = 0; i < Context->FlatList->Count; i++)
3073  {
3074  if (PhTnpGetCellParts(Context, i, Column, TN_MEASURE_TEXT, &parts) &&
3075  (parts.Flags & TN_PART_CELL) && (parts.Flags & TN_PART_CONTENT) && (parts.Flags & TN_PART_TEXT))
3076  {
3077  width = parts.TextRect.right - parts.TextRect.left; // text width
3078  width += parts.ContentRect.left - parts.CellRect.left; // left padding
3079 
3080  if (maximumWidth < width)
3081  maximumWidth = width;
3082  }
3083  }
3084 
3085  newWidth = maximumWidth + TNP_CELL_RIGHT_MARGIN; // right padding
3086 
3087  if (Column->Fixed)
3088  newWidth++;
3089  }
3090 
3091  item.mask = HDI_WIDTH;
3092  item.cxy = newWidth;
3093 
3094  Header_SetItem(HeaderHandle, Column->s.ViewIndex, &item);
3095 }
3096 
3098  _In_ PPH_TREENEW_CONTEXT Context,
3099  _In_opt_ PPH_TREENEW_NODE Node,
3100  _Out_ PPH_TREENEW_NODE **Children,
3101  _Out_ PULONG NumberOfChildren
3102  )
3103 {
3104  PH_TREENEW_GET_CHILDREN getChildren;
3105 
3106  getChildren.Flags = 0;
3107  getChildren.Node = Node;
3108  getChildren.Children = NULL;
3109  getChildren.NumberOfChildren = 0;
3110 
3111  if (Context->Callback(
3112  Context->Handle,
3114  &getChildren,
3115  NULL,
3116  Context->CallbackContext
3117  ))
3118  {
3119  *Children = getChildren.Children;
3120  *NumberOfChildren = getChildren.NumberOfChildren;
3121 
3122  return TRUE;
3123  }
3124 
3125  return FALSE;
3126 }
3127 
3129  _In_ PPH_TREENEW_CONTEXT Context,
3130  _In_ PPH_TREENEW_NODE Node
3131  )
3132 {
3133  PH_TREENEW_IS_LEAF isLeaf;
3134 
3135  isLeaf.Flags = 0;
3136  isLeaf.Node = Node;
3137  isLeaf.IsLeaf = TRUE;
3138 
3139  if (Context->Callback(
3140  Context->Handle,
3141  TreeNewIsLeaf,
3142  &isLeaf,
3143  NULL,
3144  Context->CallbackContext
3145  ))
3146  {
3147  return isLeaf.IsLeaf;
3148  }
3149 
3150  // Doesn't matter, decide when we do the get-children callback.
3151  return FALSE;
3152 }
3153 
3155  _In_ PPH_TREENEW_CONTEXT Context,
3156  _In_ PPH_TREENEW_NODE Node,
3157  _In_ ULONG Id,
3158  _Out_ PPH_STRINGREF Text
3159  )
3160 {
3161  PH_TREENEW_GET_CELL_TEXT getCellText;
3162 
3163  if (Id < Node->TextCacheSize && Node->TextCache[Id].Buffer)
3164  {
3165  *Text = Node->TextCache[Id];
3166  return TRUE;
3167  }
3168 
3169  getCellText.Flags = 0;
3170  getCellText.Node = Node;
3171  getCellText.Id = Id;
3172  PhInitializeEmptyStringRef(&getCellText.Text);
3173 
3174  if (Context->Callback(
3175  Context->Handle,
3177  &getCellText,
3178  NULL,
3179  Context->CallbackContext
3180  ) && getCellText.Text.Buffer)
3181  {
3182  *Text = getCellText.Text;
3183 
3184  if ((getCellText.Flags & TN_CACHE) && Id < Node->TextCacheSize)
3185  Node->TextCache[Id] = getCellText.Text;
3186 
3187  return TRUE;
3188  }
3189 
3190  return FALSE;
3191 }
3192 
3194  _In_ PPH_TREENEW_CONTEXT Context
3195  )
3196 {
3197  PPH_TREENEW_NODE *children;
3198  ULONG numberOfChildren;
3199  ULONG i;
3200 
3201  if (!PhTnpGetNodeChildren(Context, NULL, &children, &numberOfChildren))
3202  return;
3203 
3204  // We try to preserve the hot node, the focused node and the selection mark node.
3205  // At this point all node pointers must be regarded as invalid, so we must not
3206  // follow any pointers.
3207 
3208  Context->FocusNodeFound = FALSE;
3209 
3210  PhClearList(Context->FlatList);
3211  Context->CanAnyExpand = FALSE;
3212 
3213  for (i = 0; i < numberOfChildren; i++)
3214  {
3215  PhTnpInsertNodeChildren(Context, children[i], 0);
3216  }
3217 
3218  if (!Context->FocusNodeFound)
3219  Context->FocusNode = NULL; // focused node is no longer present
3220 
3221  if (Context->HotNodeIndex >= Context->FlatList->Count) // covers -1 case as well
3222  Context->HotNodeIndex = -1;
3223 
3224  if (Context->MarkNodeIndex >= Context->FlatList->Count)
3225  Context->MarkNodeIndex = -1;
3226 }
3227 
3229  _In_ PPH_TREENEW_CONTEXT Context,
3230  _In_ PPH_TREENEW_NODE Node,
3231  _In_ ULONG Level
3232  )
3233 {
3234  PPH_TREENEW_NODE *children;
3235  ULONG numberOfChildren;
3236  ULONG i;
3237  ULONG nextLevel;
3238 
3239  if (Node->Visible)
3240  {
3241  Node->Level = Level;
3242 
3243  Node->Index = Context->FlatList->Count;
3244  PhAddItemList(Context->FlatList, Node);
3245 
3246  if (Context->FocusNode == Node)
3247  Context->FocusNodeFound = TRUE;
3248 
3249  nextLevel = Level + 1;
3250  }
3251  else
3252  {
3253  nextLevel = 0; // children of this node should be level 0
3254  }
3255 
3256  if (!(Node->s.IsLeaf = PhTnpIsNodeLeaf(Context, Node)))
3257  {
3258  Context->CanAnyExpand = TRUE;
3259 
3260  if (Node->Expanded)
3261  {
3262  if (PhTnpGetNodeChildren(Context, Node, &children, &numberOfChildren))
3263  {
3264  for (i = 0; i < numberOfChildren; i++)
3265  {
3266  PhTnpInsertNodeChildren(Context, children[i], nextLevel);
3267  }
3268 
3269  if (numberOfChildren == 0)
3270  Node->s.IsLeaf = TRUE;
3271  }
3272  }
3273  }
3274 }
3275 
3277  _In_ PPH_TREENEW_CONTEXT Context,
3278  _In_ PPH_TREENEW_NODE Node,
3279  _In_ BOOLEAN Expanded
3280  )
3281 {
3282  if (Node->Expanded != Expanded)
3283  {
3284  PH_TREENEW_NODE_EVENT nodeEvent;
3285 
3286  memset(&nodeEvent, 0, sizeof(PH_TREENEW_NODE_EVENT));
3287  Context->Callback(Context->Handle, TreeNewNodeExpanding, Node, &nodeEvent, Context->CallbackContext);
3288 
3289  if (!nodeEvent.Handled)
3290  {
3291  if (!Expanded)
3292  {
3293  ULONG i;
3294  PPH_TREENEW_NODE node;
3295  BOOLEAN changed;
3296 
3297  // Make sure no children are selected - we don't want invisible selected nodes.
3298  // Note that this does not cause any UI changes by itself, since we are hiding
3299  // the nodes.
3300 
3301  changed = FALSE;
3302 
3303  for (i = Node->Index + 1; i < Context->FlatList->Count; i++)
3304  {
3305  node = Context->FlatList->Items[i];
3306 
3307  if (node->Level <= Node->Level)
3308  break; // no more children
3309 
3310  if (node->Selected)
3311  {
3312  node->Selected = FALSE;
3313  changed = TRUE;
3314  }
3315  }
3316 
3317  if (changed)
3318  {
3319  Context->Callback(Context->Handle, TreeNewSelectionChanged, NULL, NULL, Context->CallbackContext);
3320  }
3321  }
3322 
3323  Node->Expanded = Expanded;
3324  PhTnpRestructureNodes(Context);
3325  // We need to update the window before the scrollbars get updated in order for the scroll processing
3326  // to work properly.
3327  InvalidateRect(Context->Handle, NULL, FALSE);
3328  UpdateWindow(Context->Handle);
3329  PhTnpLayout(Context);
3330  }
3331  }
3332 }
3333 
3335  _In_ PPH_TREENEW_CONTEXT Context,
3336  _In_ ULONG Index,
3337  _In_opt_ PPH_TREENEW_COLUMN Column,
3338  _In_ ULONG Flags,
3339  _Out_ PPH_TREENEW_CELL_PARTS Parts
3340  )
3341 {
3342  PPH_TREENEW_NODE node;
3343  LONG viewWidth;
3344  LONG nodeY;
3345  LONG iconVerticalMargin;
3346  LONG currentX;
3347 
3348  if (Index >= Context->FlatList->Count)
3349  return FALSE;
3350 
3351  node = Context->FlatList->Items[Index];
3352  nodeY = Context->HeaderHeight + ((LONG)Index - Context->VScrollPosition) * Context->RowHeight;
3353 
3354  Parts->Flags = 0;
3355  Parts->RowRect.left = 0;
3356  Parts->RowRect.right = Context->NormalLeft + Context->TotalViewX - Context->HScrollPosition;
3357  Parts->RowRect.top = nodeY;
3358  Parts->RowRect.bottom = nodeY + Context->RowHeight;
3359 
3360  viewWidth = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0);
3361 
3362  if (Parts->RowRect.right > viewWidth)
3363  Parts->RowRect.right = viewWidth;
3364 
3365  if (!Column)
3366  return TRUE;
3367  if (!Column->Visible)
3368  return FALSE;
3369 
3370  iconVerticalMargin = (Context->RowHeight - SmallIconHeight) / 2;
3371 
3372  if (Column->Fixed)
3373  {
3374  currentX = 0;
3375  }
3376  else
3377  {
3378  currentX = Context->NormalLeft + Column->s.ViewX - Context->HScrollPosition;
3379  }
3380 
3381  Parts->Flags |= TN_PART_CELL;
3382  Parts->CellRect.left = currentX;
3383  Parts->CellRect.right = currentX + Column->Width;
3384  Parts->CellRect.top = Parts->RowRect.top;
3385  Parts->CellRect.bottom = Parts->RowRect.bottom;
3386 
3387  currentX += TNP_CELL_LEFT_MARGIN;
3388 
3389  if (Column == Context->FirstColumn)
3390  {
3391  currentX += (LONG)node->Level * SmallIconWidth;
3392 
3393  if (Context->CanAnyExpand)
3394  {
3395  if (!node->s.IsLeaf)
3396  {
3397  Parts->Flags |= TN_PART_PLUSMINUS;
3398  Parts->PlusMinusRect.left = currentX;
3399  Parts->PlusMinusRect.right = currentX + SmallIconWidth;
3400  Parts->PlusMinusRect.top = Parts->RowRect.top + iconVerticalMargin;
3401  Parts->PlusMinusRect.bottom = Parts->RowRect.bottom - iconVerticalMargin;
3402  }
3403 
3404  currentX += SmallIconWidth;
3405  }
3406 
3407  if (node->Icon)
3408  {
3409  Parts->Flags |= TN_PART_ICON;
3410  Parts->IconRect.left = currentX;
3411  Parts->IconRect.right = currentX + SmallIconWidth;
3412  Parts->IconRect.top = Parts->RowRect.top + iconVerticalMargin;
3413  Parts->IconRect.bottom = Parts->RowRect.bottom - iconVerticalMargin;
3414 
3415  currentX += SmallIconWidth + TNP_ICON_RIGHT_PADDING;
3416  }
3417  }
3418 
3419  Parts->Flags |= TN_PART_CONTENT;
3420  Parts->ContentRect.left = currentX;
3421  Parts->ContentRect.right = Parts->CellRect.right - TNP_CELL_RIGHT_MARGIN;
3422  Parts->ContentRect.top = Parts->RowRect.top;
3423  Parts->ContentRect.bottom = Parts->RowRect.bottom;
3424 
3425  if (Flags & TN_MEASURE_TEXT)
3426  {
3427  HDC hdc;
3428  PH_STRINGREF text;
3429  HFONT font;
3430  SIZE textSize;
3431 
3432  if (hdc = GetDC(Context->Handle))
3433  {
3434  PhTnpPrepareRowForDraw(Context, hdc, node);
3435 
3436  if (PhTnpGetCellText(Context, node, Column->Id, &text))
3437  {
3438  if (node->Font)
3439  font = node->Font;
3440  else
3441  font = Context->Font;
3442 
3443  SelectObject(hdc, font);
3444 
3445  if (GetTextExtentPoint32(hdc, text.Buffer, (ULONG)text.Length / sizeof(WCHAR), &textSize))
3446  {
3447  Parts->Flags |= TN_PART_TEXT;
3448  Parts->TextRect.left = currentX;
3449  Parts->TextRect.right = currentX + textSize.cx;
3450  Parts->TextRect.top = Parts->RowRect.top + (Context->RowHeight - textSize.cy) / 2;
3451  Parts->TextRect.bottom = Parts->RowRect.bottom - (Context->RowHeight - textSize.cy) / 2;
3452 
3453  if (Column->TextFlags & DT_CENTER)
3454  {
3455  Parts->TextRect.left = Parts->ContentRect.left / 2 + (Parts->ContentRect.right - textSize.cx) / 2;
3456  Parts->TextRect.right = Parts->ContentRect.left + textSize.cx;
3457  }
3458  else if (Column->TextFlags & DT_RIGHT)
3459  {
3460  Parts->TextRect.right = Parts->ContentRect.right;
3461  Parts->TextRect.left = Parts->TextRect.right - textSize.cx;
3462  }
3463 
3464  Parts->Text = text;
3465  Parts->Font = font;
3466  }
3467  }
3468 
3469  ReleaseDC(Context->Handle, hdc);
3470  }
3471  }
3472 
3473  return TRUE;
3474 }
3475 
3477  _In_ PPH_TREENEW_CONTEXT Context,
3478  _In_ ULONG Start,
3479  _In_ ULONG End,
3480  _In_ BOOLEAN Clip,
3481  _Out_ PRECT Rect
3482  )
3483 {
3484  LONG startY;
3485  LONG endY;
3486  LONG viewWidth;
3487 
3488  if (End >= Context->FlatList->Count)
3489  return FALSE;
3490  if (Start > End)
3491  return FALSE;
3492 
3493  startY = Context->HeaderHeight + ((LONG)Start - Context->VScrollPosition) * Context->RowHeight;
3494  endY = Context->HeaderHeight + ((LONG)End - Context->VScrollPosition) * Context->RowHeight;
3495 
3496  Rect->left = 0;
3497  Rect->right = Context->NormalLeft + Context->TotalViewX - Context->HScrollPosition;
3498  Rect->top = startY;
3499  Rect->bottom = endY + Context->RowHeight;
3500 
3501  viewWidth = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0);
3502 
3503  if (Rect->right > viewWidth)
3504  Rect->right = viewWidth;
3505 
3506  if (Clip)
3507  {
3508  if (Rect->top < Context->HeaderHeight)
3509  Rect->top = Context->HeaderHeight;
3510  if (Rect->bottom > Context->ClientRect.bottom)
3511  Rect->bottom = Context->ClientRect.bottom;
3512  }
3513 
3514  return TRUE;
3515 }
3516 
3518  _In_ PPH_TREENEW_CONTEXT Context,
3519  _Inout_ PPH_TREENEW_HIT_TEST HitTest
3520  )
3521 {
3522  RECT clientRect;
3523  LONG x;
3524  LONG y;
3525  ULONG index;
3526  PPH_TREENEW_NODE node;
3527 
3528  HitTest->Flags = 0;
3529  HitTest->Node = NULL;
3530  HitTest->Column = NULL;
3531 
3532  clientRect = Context->ClientRect;
3533  x = HitTest->Point.x;
3534  y = HitTest->Point.y;
3535 
3536  if (x < 0)
3537  HitTest->Flags |= TN_HIT_LEFT;
3538  if (x >= clientRect.right)
3539  HitTest->Flags |= TN_HIT_RIGHT;
3540  if (y < 0)
3541  HitTest->Flags |= TN_HIT_ABOVE;
3542  if (y >= clientRect.bottom)
3543  HitTest->Flags |= TN_HIT_BELOW;
3544 
3545  if (HitTest->Flags == 0)
3546  {
3547  if (TNP_HIT_TEST_FIXED_DIVIDER(x, Context))
3548  {
3549  HitTest->Flags |= TN_HIT_DIVIDER;
3550  }
3551 
3552  if (y >= Context->HeaderHeight && x < Context->FixedWidth + Context->TotalViewX)
3553  {
3554  index = (y - Context->HeaderHeight) / Context->RowHeight + Context->VScrollPosition;
3555 
3556  if (index < Context->FlatList->Count)
3557  {
3558  HitTest->Flags |= TN_HIT_ITEM;
3559  node = Context->FlatList->Items[index];
3560  HitTest->Node = node;
3561 
3562  if (HitTest->InFlags & TN_TEST_COLUMN)
3563  {
3564  PPH_TREENEW_COLUMN column;
3565  LONG columnX;
3566 
3567  column = NULL;
3568 
3569  if (x < Context->FixedWidth && Context->FixedColumnVisible)
3570  {
3571  column = Context->FixedColumn;
3572  columnX = 0;
3573  }
3574  else
3575  {
3576  LONG currentX;
3577  ULONG i;
3578  PPH_TREENEW_COLUMN currentColumn;
3579 
3580  currentX = Context->NormalLeft - Context->HScrollPosition;
3581 
3582  for (i = 0; i < Context->NumberOfColumnsByDisplay; i++)
3583  {
3584  currentColumn = Context->ColumnsByDisplay[i];
3585 
3586  if (x >= currentX && x < currentX + currentColumn->Width)
3587  {
3588  column = currentColumn;
3589  columnX = currentX;
3590  break;
3591  }
3592 
3593  currentX += currentColumn->Width;
3594  }
3595  }
3596 
3597  HitTest->Column = column;
3598 
3599  if (column && (HitTest->InFlags & TN_TEST_SUBITEM))
3600  {
3601  BOOLEAN isFirstColumn;
3602  LONG currentX;
3603 
3604  isFirstColumn = HitTest->Column == Context->FirstColumn;
3605 
3606  currentX = columnX;
3607  currentX += TNP_CELL_LEFT_MARGIN;
3608 
3609  if (isFirstColumn)
3610  {
3611  currentX += (LONG)node->Level * SmallIconWidth;
3612 
3613  if (!node->s.IsLeaf)
3614  {
3615  if (x >= currentX && x < currentX + SmallIconWidth)
3616  HitTest->Flags |= TN_HIT_ITEM_PLUSMINUS;
3617 
3618  currentX += SmallIconWidth;
3619  }
3620 
3621  if (node->Icon)
3622  {
3623  if (x >= currentX && x < currentX + SmallIconWidth)
3624  HitTest->Flags |= TN_HIT_ITEM_ICON;
3625 
3626  currentX += SmallIconWidth + TNP_ICON_RIGHT_PADDING;
3627  }
3628  }
3629 
3630  if (x >= currentX)
3631  {
3632  HitTest->Flags |= TN_HIT_ITEM_CONTENT;
3633  }
3634  }
3635  }
3636  }
3637  }
3638  }
3639 }
3640 
3642  _In_ PPH_TREENEW_CONTEXT Context,
3643  _In_ ULONG Start,
3644  _In_ ULONG End,
3645  _In_ ULONG Flags,
3646  _Out_opt_ PULONG ChangedStart,
3647  _Out_opt_ PULONG ChangedEnd
3648  )
3649 {
3650  ULONG maximum;
3651  ULONG i;
3652  PPH_TREENEW_NODE node;
3653  BOOLEAN targetValue;
3654  ULONG changedStart;
3655  ULONG changedEnd;
3656 
3657  if (Context->FlatList->Count == 0)
3658  return;
3659 
3660  maximum = Context->FlatList->Count - 1;
3661 
3662  if (End > maximum)
3663  {
3664  End = maximum;
3665  }
3666 
3667  if (Start > End)
3668  {
3669  // Start is too big, so the selection range becomes empty.
3670  // Set it to max + 1 so Reset still works.
3671  Start = maximum + 1;
3672  End = 0;
3673  }
3674 
3675  targetValue = !(Flags & TN_SELECT_DESELECT);
3676  changedStart = maximum;
3677  changedEnd = 0;
3678 
3679  if (Flags & TN_SELECT_RESET)
3680  {
3681  for (i = 0; i < Start; i++)
3682  {
3683  node = Context->FlatList->Items[i];
3684 
3685  if (node->Selected)
3686  {
3687  node->Selected = FALSE;
3688 
3689  if (changedStart > i)
3690  changedStart = i;
3691  if (changedEnd < i)
3692  changedEnd = i;
3693  }
3694  }
3695  }
3696 
3697  for (i = Start; i <= End; i++)
3698  {
3699  node = Context->FlatList->Items[i];
3700 
3701  if (!node->Unselectable && ((Flags & TN_SELECT_TOGGLE) || node->Selected != targetValue))
3702  {
3703  node->Selected = !node->Selected;
3704 
3705  if (changedStart > i)
3706  changedStart = i;
3707  if (changedEnd < i)
3708  changedEnd = i;
3709  }
3710  }
3711 
3712  if (Flags & TN_SELECT_RESET)
3713  {
3714  for (i = End + 1; i <= maximum; i++)
3715  {
3716  node = Context->FlatList->Items[i];
3717 
3718  if (node->Selected)
3719  {
3720  node->Selected = FALSE;
3721 
3722  if (changedStart > i)
3723  changedStart = i;
3724  if (changedEnd < i)
3725  changedEnd = i;
3726  }
3727  }
3728  }
3729 
3730  if (changedStart <= changedEnd)
3731  {
3732  Context->Callback(Context->Handle, TreeNewSelectionChanged, NULL, NULL, Context->CallbackContext);
3733  }
3734 
3735  if (ChangedStart)
3736  *ChangedStart = changedStart;
3737  if (ChangedEnd)
3738  *ChangedEnd = changedEnd;
3739 }
3740 
3742  _In_ PPH_TREENEW_CONTEXT Context,
3743  _In_opt_ PPH_TREENEW_NODE NewHotNode,
3744  _In_ BOOLEAN NewPlusMinusHot
3745  )
3746 {
3747  ULONG newHotNodeIndex;
3748  RECT rowRect;
3749  BOOLEAN needsInvalidate;
3750 
3751  if (NewHotNode)
3752  newHotNodeIndex = NewHotNode->Index;
3753  else
3754  newHotNodeIndex = -1;
3755 
3756  needsInvalidate = FALSE;
3757 
3758  if (Context->HotNodeIndex != newHotNodeIndex)
3759  {
3760  if (Context->HotNodeIndex != -1)
3761  {
3762  if (Context->ThemeData && PhTnpGetRowRects(Context, Context->HotNodeIndex, Context->HotNodeIndex, TRUE, &rowRect))
3763  {
3764  // Update the old hot node because it may have a different non-hot background and plus minus part.
3765  InvalidateRect(Context->Handle, &rowRect, FALSE);
3766  }
3767  }
3768 
3769  Context->HotNodeIndex = newHotNodeIndex;
3770 
3771  if (NewHotNode)
3772  {
3773  needsInvalidate = TRUE;
3774  }
3775  }
3776 
3777  if (NewHotNode)
3778  {
3779  if (NewHotNode->s.PlusMinusHot != NewPlusMinusHot)
3780  {
3781  NewHotNode->s.PlusMinusHot = NewPlusMinusHot;
3782  needsInvalidate = TRUE;
3783  }
3784 
3785  if (needsInvalidate && Context->ThemeData && PhTnpGetRowRects(Context, newHotNodeIndex, newHotNodeIndex, TRUE, &rowRect))
3786  {
3787  InvalidateRect(Context->Handle, &rowRect, FALSE);
3788  }
3789  }
3790 }
3791 
3793  _In_ PPH_TREENEW_CONTEXT Context,
3794  _In_ PPH_TREENEW_NODE Node,
3795  _In_ LOGICAL ControlKey,
3796  _In_ LOGICAL ShiftKey,
3797  _In_ LOGICAL RightButton
3798  )
3799 {
3800  ULONG changedStart;
3801  ULONG changedEnd;
3802  RECT rect;
3803 
3804  if (RightButton)
3805  {
3806  // Right button:
3807  // If the current node is selected, then do nothing. This is to allow context
3808  // menus to operate on multiple items.
3809  // If the current node is not selected, select only that node.
3810 
3811  if (!ControlKey && !ShiftKey && !Node->Selected)
3812  {
3813  PhTnpSelectRange(Context, Node->Index, Node->Index, TN_SELECT_RESET, &changedStart, &changedEnd);
3814  Context->MarkNodeIndex = Node->Index;
3815 
3816  if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
3817  {
3818  InvalidateRect(Context->Handle, &rect, FALSE);
3819  }
3820  }
3821  }
3822  else if (ShiftKey && Context->MarkNodeIndex != -1)
3823  {
3824  ULONG start;
3825  ULONG end;
3826 
3827  // Shift key: select a range from the selection mark node to the current node.
3828 
3829  if (Node->Index > Context->MarkNodeIndex)
3830  {
3831  start = Context->MarkNodeIndex;
3832  end = Node->Index;
3833  }
3834  else
3835  {
3836  start = Node->Index;
3837  end = Context->MarkNodeIndex;
3838  }
3839 
3840  PhTnpSelectRange(Context, start, end, TN_SELECT_RESET, &changedStart, &changedEnd);
3841 
3842  if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
3843  {
3844  InvalidateRect(Context->Handle, &rect, FALSE);
3845  }
3846  }
3847  else if (ControlKey)
3848  {
3849  // Control key: toggle the selection on the current node, and also make it the selection mark.
3850 
3851  PhTnpSelectRange(Context, Node->Index, Node->Index, TN_SELECT_TOGGLE, NULL, NULL);
3852  Context->MarkNodeIndex = Node->Index;
3853 
3854  if (PhTnpGetRowRects(Context, Node->Index, Node->Index, TRUE, &rect))
3855  {
3856  InvalidateRect(Context->Handle, &rect, FALSE);
3857  }
3858  }
3859  else
3860  {
3861  // Normal: select the current node, and also make it the selection mark.
3862 
3863  PhTnpSelectRange(Context, Node->Index, Node->Index, TN_SELECT_RESET, &changedStart, &changedEnd);
3864  Context->MarkNodeIndex = Node->Index;
3865 
3866  if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
3867  {
3868  InvalidateRect(Context->Handle, &rect, FALSE);
3869  }
3870  }
3871 }
3872 
3874  _In_ PPH_TREENEW_CONTEXT Context,
3875  _In_ ULONG Index
3876  )
3877 {
3878  LONG viewTop;
3879  LONG viewBottom;
3880  LONG rowTop;
3881  LONG rowBottom;
3882  LONG deltaY;
3883  LONG deltaRows;
3884 
3885  if (Index >= Context->FlatList->Count)
3886  return FALSE;
3887 
3888  viewTop = Context->HeaderHeight;
3889  viewBottom = Context->ClientRect.bottom - (Context->HScrollVisible ? Context->HScrollHeight : 0);
3890  rowTop = Context->HeaderHeight + ((LONG)Index - Context->VScrollPosition) * Context->RowHeight;
3891  rowBottom = rowTop + Context->RowHeight;
3892 
3893  // Check if the row is fully visible.
3894  if (rowTop >= viewTop && rowBottom <= viewBottom)
3895  return TRUE;
3896 
3897  deltaY = rowTop - viewTop;
3898 
3899  if (deltaY > 0)
3900  {
3901  // The row is below the view area. We want to scroll the row into view at the bottom of the screen.
3902  // We need to round up when dividing to make sure the node becomes fully visible.
3903  deltaY = rowBottom - viewBottom;
3904  deltaRows = (deltaY + Context->RowHeight - 1) / Context->RowHeight; // divide, round up
3905  }
3906  else
3907  {
3908  deltaRows = deltaY / Context->RowHeight;
3909  }
3910 
3911  PhTnpScroll(Context, deltaRows, 0);
3912 
3913  return TRUE;
3914 }
3915 
3917  _In_ PPH_TREENEW_CONTEXT Context,
3918  _In_ LONG CursorX,
3919  _In_ LONG CursorY
3920  )
3921 {
3922  PH_TREENEW_HIT_TEST hitTest;
3923  PPH_TREENEW_NODE hotNode;
3924 
3925  hitTest.Point.x = CursorX;
3926  hitTest.Point.y = CursorY;
3928  PhTnpHitTest(Context, &hitTest);
3929 
3930  if (hitTest.Flags & TN_HIT_ITEM)
3931  hotNode = hitTest.Node;
3932  else
3933  hotNode = NULL;
3934 
3935  PhTnpSetHotNode(Context, hotNode, !!(hitTest.Flags & TN_HIT_ITEM_PLUSMINUS));
3936 
3937  if (Context->AnimateDivider && Context->FixedDividerVisible)
3938  {
3939  if (hitTest.Flags & TN_HIT_DIVIDER)
3940  {
3941  if ((Context->DividerHot < 100 || Context->AnimateDividerFadingOut) && !Context->AnimateDividerFadingIn)
3942  {
3943  // Begin fading in the divider.
3944  Context->AnimateDividerFadingIn = TRUE;
3945  Context->AnimateDividerFadingOut = FALSE;
3946  SetTimer(Context->Handle, TNP_TIMER_ANIMATE_DIVIDER, TNP_ANIMATE_DIVIDER_INTERVAL, NULL);
3947  }
3948  }
3949  else
3950  {
3951  if ((Context->DividerHot != 0 || Context->AnimateDividerFadingIn) && !Context->AnimateDividerFadingOut)
3952  {
3953  Context->AnimateDividerFadingOut = TRUE;
3954  Context->AnimateDividerFadingIn = FALSE;
3955  SetTimer(Context->Handle, TNP_TIMER_ANIMATE_DIVIDER, TNP_ANIMATE_DIVIDER_INTERVAL, NULL);
3956  }
3957  }
3958  }
3959 
3960  if (Context->TooltipsHandle)
3961  {
3962  ULONG index;
3963  ULONG id;
3964 
3965  if (!(hitTest.Flags & TN_HIT_DIVIDER))
3966  {
3967  index = hitTest.Node ? hitTest.Node->Index : -1;
3968  id = hitTest.Column ? hitTest.Column->Id : -1;
3969  }
3970  else
3971  {
3972  index = -1;
3973  id = -1;
3974  }
3975 
3976  // This pops unnecessarily - when the cell has no tooltip text, and the user is
3977  // moving the mouse over it. However these unnecessary calls seem to fix a
3978  // certain tooltip bug (move the mouse around very quickly over the last column and
3979  // the blank space to the right, and no more tooltips will appear).
3980  if (Context->TooltipIndex != index || Context->TooltipId != id)
3981  {
3982  PhTnpPopTooltip(Context);
3983  }
3984  }
3985 }
3986 
3988  _In_ PPH_TREENEW_CONTEXT Context,
3989  _In_ LONG Distance
3990  )
3991 {
3992  ULONG wheelScrollLines;
3993  FLOAT linesToScroll;
3994  LONG wholeLinesToScroll;
3995  SCROLLINFO scrollInfo;
3996  LONG oldPosition;
3997 
3998  if (!SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheelScrollLines, 0))
3999  wheelScrollLines = 3;
4000 
4001  // If page scrolling is enabled, use the number of visible rows.
4002  if (wheelScrollLines == -1)
4003  wheelScrollLines = (Context->ClientRect.bottom - Context->HeaderHeight - (Context->HScrollVisible ? Context->HScrollHeight : 0)) / Context->RowHeight;
4004 
4005  // Zero the remainder if the direction changed.
4006  if ((Context->VScrollRemainder > 0) != (Distance > 0))
4007  Context->VScrollRemainder = 0;
4008 
4009  linesToScroll = (FLOAT)wheelScrollLines * Distance / WHEEL_DELTA + Context->VScrollRemainder;
4010  wholeLinesToScroll = (LONG)linesToScroll;
4011  Context->VScrollRemainder = linesToScroll - wholeLinesToScroll;
4012 
4013  scrollInfo.cbSize = sizeof(SCROLLINFO);
4014  scrollInfo.fMask = SIF_ALL;
4015  GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo);
4016  oldPosition = scrollInfo.nPos;
4017 
4018  scrollInfo.nPos += wholeLinesToScroll;
4019 
4020  scrollInfo.fMask = SIF_POS;
4021  SetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo, TRUE);
4022  GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo);
4023 
4024  if (scrollInfo.nPos != oldPosition)
4025  {
4026  Context->VScrollPosition = scrollInfo.nPos;
4027  PhTnpProcessScroll(Context, scrollInfo.nPos - oldPosition, 0);
4028 
4029  if (Context->TooltipsHandle)
4030  {
4031  MSG message;
4032  POINT point;
4033 
4034  PhTnpPopTooltip(Context);
4035  PhTnpGetMessagePos(Context->Handle, &point);
4036 
4037  if (point.x >= 0 && point.y >= 0 && point.x < Context->ClientRect.right && point.y < Context->ClientRect.bottom)
4038  {
4039  // Send a fake mouse move message for the new node that the mouse may be hovering over.
4040  message.hwnd = Context->Handle;
4041  message.message = WM_MOUSEMOVE;
4042  message.wParam = 0;
4043  message.lParam = MAKELPARAM(point.x, point.y);
4044  SendMessage(Context->TooltipsHandle, TTM_RELAYEVENT, 0, (LPARAM)&message);
4045  }
4046  }
4047  }
4048 }
4049 
4051  _In_ PPH_TREENEW_CONTEXT Context,
4052  _In_ LONG Distance
4053  )
4054 {
4055  ULONG wheelScrollChars;
4056  FLOAT pixelsToScroll;
4057  LONG wholePixelsToScroll;
4058  SCROLLINFO scrollInfo;
4059  LONG oldPosition;
4060 
4061  if (!SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &wheelScrollChars, 0))
4062  wheelScrollChars = 3;
4063 
4064  // Zero the remainder if the direction changed.
4065  if ((Context->HScrollRemainder > 0) != (Distance > 0))
4066  Context->HScrollRemainder = 0;
4067 
4068  pixelsToScroll = (FLOAT)wheelScrollChars * Context->TextMetrics.tmAveCharWidth * Distance / WHEEL_DELTA + Context->HScrollRemainder;
4069  wholePixelsToScroll = (LONG)pixelsToScroll;
4070  Context->HScrollRemainder = pixelsToScroll - wholePixelsToScroll;
4071 
4072  scrollInfo.cbSize = sizeof(SCROLLINFO);
4073  scrollInfo.fMask = SIF_ALL;
4074  GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo);
4075  oldPosition = scrollInfo.nPos;
4076 
4077  scrollInfo.nPos += wholePixelsToScroll;
4078 
4079  scrollInfo.fMask = SIF_POS;
4080  SetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo, TRUE);
4081  GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo);
4082 
4083  if (scrollInfo.nPos != oldPosition)
4084  {
4085  Context->HScrollPosition = scrollInfo.nPos;
4086  PhTnpProcessScroll(Context, 0, scrollInfo.nPos - oldPosition);
4087  }
4088 }
4089 
4091  _In_ PPH_TREENEW_CONTEXT Context,
4092  _In_ ULONG VirtualKey
4093  )
4094 {
4095  ULONG count;
4096  ULONG index;
4097  BOOLEAN controlKey;
4098  BOOLEAN shiftKey;
4099  ULONG start;
4100  ULONG end;
4101  ULONG changedStart;
4102  ULONG changedEnd;
4103  RECT rect;
4104 
4105  if (VirtualKey != VK_UP && VirtualKey != VK_DOWN &&
4106  VirtualKey != VK_HOME && VirtualKey != VK_END &&
4107  VirtualKey != VK_PRIOR && VirtualKey != VK_NEXT)
4108  {
4109  return FALSE;
4110  }
4111 
4112  count = Context->FlatList->Count;
4113 
4114  if (count == 0)
4115  return TRUE;
4116 
4117  // Find the new node to focus.
4118 
4119  switch (VirtualKey)
4120  {
4121  case VK_UP:
4122  {
4123  if (Context->FocusNode && Context->FocusNode->Index > 0)
4124  {
4125  index = Context->FocusNode->Index - 1;
4126  }
4127  else
4128  {
4129  index = 0;
4130  }
4131  }
4132  break;
4133  case VK_DOWN:
4134  {
4135  if (Context->FocusNode)
4136  {
4137  index = Context->FocusNode->Index + 1;
4138 
4139  if (index >= count)
4140  index = count - 1;
4141  }
4142  else
4143  {
4144  index = 0;
4145  }
4146  }
4147  break;
4148  case VK_HOME:
4149  index = 0;
4150  break;
4151  case VK_END:
4152  index = count - 1;
4153  break;
4154  case VK_PRIOR:
4155  case VK_NEXT:
4156  {
4157  LONG rowsPerPage;
4158 
4159  if (Context->FocusNode)
4160  index = Context->FocusNode->Index;
4161  else
4162  index = 0;
4163 
4164  rowsPerPage = Context->ClientRect.bottom - Context->HeaderHeight - (Context->HScrollVisible ? Context->HScrollHeight : 0);
4165 
4166  if (rowsPerPage < 0)
4167  return TRUE;
4168 
4169  rowsPerPage = rowsPerPage / Context->RowHeight - 1;
4170 
4171  if (rowsPerPage < 0)
4172  return TRUE;
4173 
4174  if (VirtualKey == VK_PRIOR)
4175  {
4176  ULONG startOfPageIndex;
4177 
4178  startOfPageIndex = Context->VScrollPosition;
4179 
4180  if (index > startOfPageIndex)
4181  {
4182  index = startOfPageIndex;
4183  }
4184  else
4185  {
4186  // Already at or before the start of the page. Go back a page.
4187  if (index >= (ULONG)rowsPerPage)
4188  index -= rowsPerPage;
4189  else
4190  index = 0;
4191  }
4192  }
4193  else
4194  {
4195  ULONG endOfPageIndex;
4196 
4197  endOfPageIndex = Context->VScrollPosition + rowsPerPage;
4198 
4199  if (endOfPageIndex >= count)
4200  endOfPageIndex = count - 1;
4201 
4202  if (index < endOfPageIndex)
4203  {
4204  index = endOfPageIndex;
4205  }
4206  else
4207  {
4208  // Already at or after the end of the page. Go forward a page.
4209  index += rowsPerPage;
4210 
4211  if (index >= count)
4212  index = count - 1;
4213  }
4214  }
4215  }
4216  break;
4217  }
4218 
4219  // Select the relevant nodes.
4220 
4221  controlKey = GetKeyState(VK_CONTROL) < 0;
4222  shiftKey = GetKeyState(VK_SHIFT) < 0;
4223 
4224  Context->FocusNode = Context->FlatList->Items[index];
4225  PhTnpSetHotNode(Context, Context->FocusNode, FALSE);
4226 
4227  if (shiftKey && Context->MarkNodeIndex != -1)
4228  {
4229  if (index > Context->MarkNodeIndex)
4230  {
4231  start = Context->MarkNodeIndex;
4232  end = index;
4233  }
4234  else
4235  {
4236  start = index;
4237  end = Context->MarkNodeIndex;
4238  }
4239 
4240  PhTnpSelectRange(Context, start, end, TN_SELECT_RESET, &changedStart, &changedEnd);
4241 
4242  if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
4243  {
4244  InvalidateRect(Context->Handle, &rect, FALSE);
4245  }
4246  }
4247  else if (!controlKey)
4248  {
4249  Context->MarkNodeIndex = Context->FocusNode->Index;
4250  PhTnpSelectRange(Context, index, index, TN_SELECT_RESET, &changedStart, &changedEnd);
4251 
4252  if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
4253  {
4254  InvalidateRect(Context->Handle, &rect, FALSE);
4255  }
4256  }
4257 
4258  PhTnpEnsureVisibleNode(Context, index);
4259  PhTnpPopTooltip(Context);
4260 
4261  return TRUE;
4262 }
4263 
4265  _In_ PPH_TREENEW_CONTEXT Context,
4266  _In_ ULONG VirtualKey
4267  )
4268 {
4269  BOOLEAN controlKey;
4270  BOOLEAN shiftKey;
4271  ULONG changedStart;
4272  ULONG changedEnd;
4273  RECT rect;
4274 
4275  if (VirtualKey != VK_SPACE && VirtualKey != VK_LEFT && VirtualKey != VK_RIGHT)
4276  {
4277  return FALSE;
4278  }
4279 
4280  if (!Context->FocusNode)
4281  return TRUE;
4282 
4283  controlKey = GetKeyState(VK_CONTROL) < 0;
4284  shiftKey = GetKeyState(VK_SHIFT) < 0;
4285 
4286  switch (VirtualKey)
4287  {
4288  case VK_SPACE:
4289  {
4290  if (controlKey)
4291  {
4292  // Control key: toggle the selection on the focused node.
4293 
4294  Context->MarkNodeIndex = Context->FocusNode->Index;
4295  PhTnpSelectRange(Context, Context->FocusNode->Index, Context->FocusNode->Index, TN_SELECT_TOGGLE, &changedStart, &changedEnd);
4296 
4297  if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
4298  {
4299  InvalidateRect(Context->Handle, &rect, FALSE);
4300  }
4301  }
4302  else if (shiftKey)
4303  {
4304  ULONG start;
4305  ULONG end;
4306 
4307  // Shift key: select a range from the selection mark node to the focused node.
4308 
4309  if (Context->FocusNode->Index > Context->MarkNodeIndex)
4310  {
4311  start = Context->MarkNodeIndex;
4312  end = Context->FocusNode->Index;
4313  }
4314  else
4315  {
4316  start = Context->FocusNode->Index;
4317  end = Context->MarkNodeIndex;
4318  }
4319 
4320  PhTnpSelectRange(Context, start, end, TN_SELECT_RESET, &changedStart, &changedEnd);
4321 
4322  if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
4323  {
4324  InvalidateRect(Context->Handle, &rect, FALSE);
4325  }
4326  }
4327  }
4328  break;
4329  case VK_LEFT:
4330  {
4331  ULONG i;
4332  ULONG targetLevel;
4333  PPH_TREENEW_NODE newNode;
4334 
4335  // If the node is expanded, collapse it. Otherwise, select the node's
4336  // parent.
4337  if (!Context->FocusNode->s.IsLeaf && Context->FocusNode->Expanded)
4338  {
4339  PhTnpSetExpandedNode(Context, Context->FocusNode, FALSE);
4340  }
4341  else if (Context->FocusNode->Level != 0)
4342  {
4343  i = Context->FocusNode->Index;
4344  targetLevel = Context->FocusNode->Level - 1;
4345 
4346  while (i != 0)
4347  {
4348  i--;
4349  newNode = Context->FlatList->Items[i];
4350 
4351  if (newNode->Level == targetLevel)
4352  {
4353  Context->FocusNode = newNode;
4354  Context->MarkNodeIndex = newNode->Index;
4355  PhTnpEnsureVisibleNode(Context, i);
4356  PhTnpSetHotNode(Context, newNode, FALSE);
4357  PhTnpSelectRange(Context, i, i, TN_SELECT_RESET, &changedStart, &changedEnd);
4358 
4359  if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
4360  {
4361  InvalidateRect(Context->Handle, &rect, FALSE);
4362  }
4363 
4364  PhTnpPopTooltip(Context);
4365 
4366  break;
4367  }
4368  }
4369  }
4370  }
4371  break;
4372  case VK_RIGHT:
4373  {
4374  PPH_TREENEW_NODE newNode;
4375 
4376  if (!Context->FocusNode->s.IsLeaf)
4377  {
4378  // If the node is collapsed, expand it. Otherwise, select the node's
4379  // first child.
4380  if (!Context->FocusNode->Expanded)
4381  {
4382  PhTnpSetExpandedNode(Context, Context->FocusNode, TRUE);
4383  }
4384  else
4385  {
4386  if (Context->FocusNode->Index + 1 < Context->FlatList->Count)
4387  {
4388  newNode = Context->FlatList->Items[Context->FocusNode->Index + 1];
4389 
4390  if (newNode->Level == Context->FocusNode->Level + 1)
4391  {
4392  Context->FocusNode = newNode;
4393  Context->MarkNodeIndex = newNode->Index;
4394  PhTnpEnsureVisibleNode(Context, Context->FocusNode->Index + 1);
4395  PhTnpSetHotNode(Context, newNode, FALSE);
4396  PhTnpSelectRange(Context, Context->FocusNode->Index, Context->FocusNode->Index, TN_SELECT_RESET, &changedStart, &changedEnd);
4397 
4398  if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
4399  {
4400  InvalidateRect(Context->Handle, &rect, FALSE);
4401  }
4402 
4403  PhTnpPopTooltip(Context);
4404  }
4405  }
4406  }
4407  }
4408  }
4409  break;
4410  }
4411 
4412  return TRUE;
4413 }
4414 
4416  _In_ PPH_TREENEW_CONTEXT Context,
4417  _In_ ULONG Character
4418  )
4419 {
4420  LONG messageTime;
4421  BOOLEAN newSearch;
4422  PH_TREENEW_SEARCH_EVENT searchEvent;
4423  PPH_TREENEW_NODE foundNode;
4424  ULONG changedStart;
4425  ULONG changedEnd;
4426  RECT rect;
4427 
4428  if (Context->FlatList->Count == 0)
4429  return;
4430 
4431  messageTime = GetMessageTime();
4432  newSearch = FALSE;
4433 
4434  // Check if the search timed out.
4435  if (messageTime - Context->SearchMessageTime > PH_TREENEW_SEARCH_TIMEOUT)
4436  {
4437  Context->SearchStringCount = 0;
4438  Context->SearchFailed = FALSE;
4439  newSearch = TRUE;
4440  Context->SearchSingleCharMode = TRUE;
4441  }
4442 
4443  Context->SearchMessageTime = messageTime;
4444 
4445  // Append the character to the search buffer.
4446 
4447  if (!Context->SearchString)
4448  {
4449  Context->AllocatedSearchString = 32;
4450  Context->SearchString = PhAllocate(Context->AllocatedSearchString * sizeof(WCHAR));
4451  newSearch = TRUE;
4452  Context->SearchSingleCharMode = TRUE;
4453  }
4454 
4455  if (Context->SearchStringCount > PH_TREENEW_SEARCH_MAXIMUM_LENGTH)
4456  {
4457  // The search string has become too long. Fail the search.
4458  if (!Context->SearchFailed)
4459  {
4460  MessageBeep(0);
4461  Context->SearchFailed = TRUE;
4462  return;
4463  }
4464  }
4465  else if (Context->SearchStringCount == Context->AllocatedSearchString)
4466  {
4467  Context->AllocatedSearchString *= 2;
4468  Context->SearchString = PhReAllocate(Context->SearchString, Context->AllocatedSearchString * sizeof(WCHAR));
4469  }
4470 
4471  Context->SearchString[Context->SearchStringCount++] = (WCHAR)Character;
4472 
4473  if (Context->SearchString[Context->SearchStringCount - 1] != Context->SearchString[0])
4474  {
4475  // The user has stopped typing the same character (or never started). Turn single-character search
4476  // off.
4477  Context->SearchSingleCharMode = FALSE;
4478  }
4479 
4480  searchEvent.FoundIndex = -1;
4481 
4482  if (Context->FocusNode)
4483  {
4484  searchEvent.StartIndex = Context->FocusNode->Index;
4485 
4486  if (newSearch || Context->SearchSingleCharMode)
4487  {
4488  // If it's a new search, start at the next item so the user doesn't find the same item again.
4489  searchEvent.StartIndex++;
4490 
4491  if (searchEvent.StartIndex == Context->FlatList->Count)
4492  searchEvent.StartIndex = 0;
4493  }
4494  }
4495  else
4496  {
4497  searchEvent.StartIndex = 0;
4498  }
4499 
4500  searchEvent.String.Buffer = Context->SearchString;
4501 
4502  if (!Context->SearchSingleCharMode)
4503  searchEvent.String.Length = Context->SearchStringCount * sizeof(WCHAR);
4504  else
4505  searchEvent.String.Length = sizeof(WCHAR);
4506 
4507  // Give the user a chance to modify how the search is performed.
4508  if (!Context->Callback(Context->Handle, TreeNewIncrementalSearch, &searchEvent, NULL, Context->CallbackContext))
4509  {
4510  // Use the default search function.
4511  if (!PhTnpDefaultIncrementalSearch(Context, &searchEvent, TRUE, TRUE))
4512  {
4513  return;
4514  }
4515  }
4516 
4517  if (searchEvent.FoundIndex == -1 && !Context->SearchFailed)
4518  {
4519  // No search result. Beep to indicate an error, and set the flag so we don't beep again.
4520  // But don't beep if the first character was a space, because that's used for other purposes
4521  // elsewhere (see PhTnpProcessNodeKey).
4522  if (searchEvent.String.Buffer[0] != ' ')
4523  {
4524  MessageBeep(0);
4525  }
4526 
4527  Context->SearchFailed = TRUE;
4528  return;
4529  }
4530 
4531  if (searchEvent.FoundIndex < 0 || searchEvent.FoundIndex >= (LONG)Context->FlatList->Count)
4532  return;
4533 
4534  foundNode = Context->FlatList->Items[searchEvent.FoundIndex];
4535  Context->FocusNode = foundNode;
4536  PhTnpEnsureVisibleNode(Context, searchEvent.FoundIndex);
4537  PhTnpSetHotNode(Context, foundNode, FALSE);
4538  PhTnpSelectRange(Context, searchEvent.FoundIndex, searchEvent.FoundIndex, TN_SELECT_RESET, &changedStart, &changedEnd);
4539 
4540  if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
4541  {
4542  InvalidateRect(Context->Handle, &rect, FALSE);
4543  }
4544 
4545  PhTnpPopTooltip(Context);
4546 }
4547 
4549  _In_ PPH_TREENEW_CONTEXT Context,
4550  _Inout_ PPH_TREENEW_SEARCH_EVENT SearchEvent,
4551  _In_ BOOLEAN Partial,
4552  _In_ BOOLEAN Wrap
4553  )
4554 {
4555  LONG startIndex;
4556  LONG currentIndex;
4557  LONG foundIndex;
4558  BOOLEAN firstTime;
4559 
4560  if (Context->FlatList->Count == 0)
4561  return FALSE;
4562  if (!Context->FirstColumn)
4563  return FALSE;
4564 
4565  startIndex = SearchEvent->StartIndex;
4566  currentIndex = startIndex;
4567  foundIndex = -1;
4568  firstTime = TRUE;
4569 
4570  while (TRUE)
4571  {
4572  PH_STRINGREF text;
4573 
4574  if (currentIndex >= (LONG)Context->FlatList->Count)
4575  {
4576  if (Wrap)
4577  currentIndex = 0;
4578  else
4579  break;
4580  }
4581 
4582  // We use the firstTime variable instead of a simpler check because
4583  // we want to include the current item in the search. E.g. the
4584  // current item is the only item beginning with "Z". If the user
4585  // searches for "Z", we want to return the current item as being
4586  // found.
4587  if (!firstTime && currentIndex == startIndex)
4588  break;
4589 
4590  if (PhTnpGetCellText(Context, Context->FlatList->Items[currentIndex], Context->FirstColumn->Id, &text))
4591  {
4592  if (Partial)
4593  {
4594  if (PhStartsWithStringRef(&text, &SearchEvent->String, TRUE))
4595  {
4596  foundIndex = currentIndex;
4597  break;
4598  }
4599  }
4600  else
4601  {
4602  if (PhEqualStringRef(&text, &SearchEvent->String, TRUE))
4603  {
4604  foundIndex = currentIndex;
4605  break;
4606  }
4607  }
4608  }
4609 
4610  currentIndex++;
4611  firstTime = FALSE;
4612  }
4613 
4614  SearchEvent->FoundIndex = foundIndex;
4615 
4616  return TRUE;
4617 }
4618 
4620  _In_ PPH_TREENEW_CONTEXT Context
4621  )
4622 {
4623  RECT clientRect;
4624  LONG width;
4625  LONG height;
4626  LONG contentWidth;
4627  LONG contentHeight;
4628  SCROLLINFO scrollInfo;
4629  LONG oldPosition;
4630  LONG deltaRows;
4631  LONG deltaX;
4632  LOGICAL oldHScrollVisible;
4633  RECT rect;
4634 
4635  clientRect = Context->ClientRect;
4636  width = clientRect.right - Context->FixedWidth;
4637  height = clientRect.bottom - Context->HeaderHeight;
4638 
4639  contentWidth = Context->TotalViewX;
4640  contentHeight = (LONG)Context->FlatList->Count * Context->RowHeight;
4641 
4642  if (contentHeight > height)
4643  {
4644  // We need a vertical scrollbar, so we can't use that area of the screen for content.
4645  width -= Context->VScrollWidth;
4646  }
4647 
4648  if (contentWidth > width)
4649  {
4650  height -= Context->HScrollHeight;
4651  }
4652 
4653  deltaRows = 0;
4654  deltaX = 0;
4655 
4656  // Vertical scroll bar
4657 
4658  scrollInfo.cbSize = sizeof(SCROLLINFO);
4659  scrollInfo.fMask = SIF_POS;
4660  GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo);
4661  oldPosition = scrollInfo.nPos;
4662 
4663  scrollInfo.fMask = SIF_RANGE | SIF_PAGE;
4664  scrollInfo.nMin = 0;
4665  scrollInfo.nMax = Context->FlatList->Count != 0 ? Context->FlatList->Count - 1 : 0;
4666  scrollInfo.nPage = height / Context->RowHeight;
4667  SetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo, TRUE);
4668 
4669  // The scroll position may have changed due to the modified scroll range.
4670  scrollInfo.fMask = SIF_POS;
4671  GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo);
4672  deltaRows = scrollInfo.nPos - oldPosition;
4673  Context->VScrollPosition = scrollInfo.nPos;
4674 
4675  if (contentHeight > height && contentHeight != 0)
4676  {
4677  ShowWindow(Context->VScrollHandle, SW_SHOW);
4678  Context->VScrollVisible = TRUE;
4679  }
4680  else
4681  {
4682  ShowWindow(Context->VScrollHandle, SW_HIDE);
4683  Context->VScrollVisible = FALSE;
4684  }
4685 
4686  // Horizontal scroll bar
4687 
4688  scrollInfo.cbSize = sizeof(SCROLLINFO);
4689  scrollInfo.fMask = SIF_POS;
4690  GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo);
4691  oldPosition = scrollInfo.nPos;
4692 
4693  scrollInfo.fMask = SIF_RANGE | SIF_PAGE;
4694  scrollInfo.nMin = 0;
4695  scrollInfo.nMax = contentWidth != 0 ? contentWidth - 1 : 0;
4696  scrollInfo.nPage = width;
4697  SetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo, TRUE);
4698 
4699  scrollInfo.fMask = SIF_POS;
4700  GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo);
4701  deltaX = scrollInfo.nPos - oldPosition;
4702  Context->HScrollPosition = scrollInfo.nPos;
4703 
4704  oldHScrollVisible = Context->HScrollVisible;
4705 
4706  if (contentWidth > width && contentWidth != 0)
4707  {
4708  ShowWindow(Context->HScrollHandle, SW_SHOW);
4709  Context->HScrollVisible = TRUE;
4710  }
4711  else
4712  {
4713  ShowWindow(Context->HScrollHandle, SW_HIDE);
4714  Context->HScrollVisible = FALSE;
4715  }
4716 
4717  if ((Context->HScrollVisible != oldHScrollVisible) && Context->FixedDividerVisible && Context->AnimateDivider)
4718  {
4719  rect.left = Context->FixedWidth;
4720  rect.top = Context->HeaderHeight;
4721  rect.right = Context->FixedWidth + 1;
4722  rect.bottom = Context->ClientRect.bottom;
4723  InvalidateRect(Context->Handle, &rect, FALSE);
4724  }
4725 
4726  if (deltaRows != 0 || deltaX != 0)
4727  PhTnpProcessScroll(Context, deltaRows, deltaX);
4728 
4729  ShowWindow(Context->FillerBoxHandle, (Context->VScrollVisible && Context->HScrollVisible) ? SW_SHOW : SW_HIDE);
4730 }
4731 
4733  _In_ PPH_TREENEW_CONTEXT Context,
4734  _In_ LONG DeltaRows,
4735  _In_ LONG DeltaX
4736  )
4737 {
4738  SCROLLINFO scrollInfo;
4739  LONG oldPosition;
4740  LONG deltaRows;
4741  LONG deltaX;
4742 
4743  deltaRows = 0;
4744  deltaX = 0;
4745 
4746  scrollInfo.cbSize = sizeof(SCROLLINFO);
4747  scrollInfo.fMask = SIF_POS;
4748 
4749  if (DeltaRows != 0 && Context->VScrollVisible)
4750  {
4751  GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo);
4752  oldPosition = scrollInfo.nPos;
4753 
4754  if (DeltaRows == MINLONG)
4755  scrollInfo.nPos = 0;
4756  else if (DeltaRows == MAXLONG)
4757  scrollInfo.nPos = Context->FlatList->Count - 1;
4758  else
4759  scrollInfo.nPos += DeltaRows;
4760 
4761  SetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo, TRUE);
4762  GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo);
4763  Context->VScrollPosition = scrollInfo.nPos;
4764  deltaRows = scrollInfo.nPos - oldPosition;
4765  }
4766 
4767  if (DeltaX != 0 && Context->HScrollVisible)
4768  {
4769  GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo);
4770  oldPosition = scrollInfo.nPos;
4771 
4772  if (DeltaX == MINLONG)
4773  scrollInfo.nPos = 0;
4774  else if (DeltaX == MAXLONG)
4775  scrollInfo.nPos = Context->TotalViewX;
4776  else
4777  scrollInfo.nPos += DeltaX;
4778 
4779  SetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo, TRUE);
4780  GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo);
4781  Context->HScrollPosition = scrollInfo.nPos;
4782  deltaX = scrollInfo.nPos - oldPosition;
4783  }
4784 
4785  if (deltaRows != 0 || deltaX != 0)
4786  PhTnpProcessScroll(Context, deltaRows, deltaX);
4787 }
4788 
4790  _In_ PPH_TREENEW_CONTEXT Context,
4791  _In_ LONG DeltaRows,
4792  _In_ LONG DeltaX
4793  )
4794 {
4795  RECT rect;
4796  LONG deltaY;
4797 
4798  rect.top = Context->HeaderHeight;
4799  rect.bottom = Context->ClientRect.bottom;
4800 
4801  if (DeltaX == 0)
4802  {
4803  rect.left = 0;
4804  rect.right = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0);
4805  ScrollWindowEx(
4806  Context->Handle,
4807  0,
4808  -DeltaRows * Context->RowHeight,
4809  &rect,
4810  NULL,
4811  NULL,
4812  NULL,
4813  SW_INVALIDATE
4814  );
4815  }
4816  else
4817  {
4818  // Don't scroll if there are no rows. This is especially important if the user wants us to display empty text.
4819  if (Context->FlatList->Count != 0)
4820  {
4821  deltaY = DeltaRows * Context->RowHeight;
4822 
4823  // If we're scrolling vertically as well, we need to scroll the fixed part and the normal part
4824  // separately.
4825 
4826  if (DeltaRows != 0)
4827  {
4828  rect.left = 0;
4829  rect.right = Context->NormalLeft;
4830  ScrollWindowEx(
4831  Context->Handle,
4832  0,
4833  -deltaY,
4834  &rect,
4835  &rect,
4836  NULL,
4837  NULL,
4838  SW_INVALIDATE
4839  );
4840  }
4841 
4842  rect.left = Context->NormalLeft;
4843  rect.right = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0);
4844  ScrollWindowEx(
4845  Context->Handle,
4846  -DeltaX,
4847  -deltaY,
4848  &rect,
4849  &rect,
4850  NULL,
4851  NULL,
4852  SW_INVALIDATE
4853  );
4854  }
4855 
4856  PhTnpLayoutHeader(Context);
4857  }
4858 }
4859 
4861  _In_ PPH_TREENEW_CONTEXT Context,
4862  _In_ BOOLEAN Horizontal,
4863  _In_ BOOLEAN Positive
4864  )
4865 {
4866  SCROLLINFO scrollInfo;
4867 
4868  scrollInfo.cbSize = sizeof(SCROLLINFO);
4869  scrollInfo.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
4870 
4871  if (!Horizontal)
4872  GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo);
4873  else
4874  GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo);
4875 
4876  if (Positive)
4877  {
4878  if (scrollInfo.nPage != 0)
4879  scrollInfo.nMax -= scrollInfo.nPage - 1;
4880 
4881  return scrollInfo.nPos < scrollInfo.nMax;
4882  }
4883  else
4884  {
4885  return scrollInfo.nPos > scrollInfo.nMin;
4886  }
4887 }
4888 
4890  _In_ HWND hwnd,
4891  _In_ PPH_TREENEW_CONTEXT Context,
4892  _In_ HDC hdc,
4893  _In_ PRECT PaintRect
4894  )
4895 {
4896  RECT viewRect;
4897  LONG vScrollPosition;
4898  LONG hScrollPosition;
4899  LONG firstRowToUpdate;
4900  LONG lastRowToUpdate;
4901  LONG i;
4902  LONG j;
4903  PPH_TREENEW_NODE node;
4904  PPH_TREENEW_COLUMN column;
4905  RECT rowRect;
4906  LONG x;
4907  BOOLEAN fixedUpdate;
4908  LONG normalUpdateLeftX;
4909  LONG normalUpdateRightX;
4910  LONG normalUpdateLeftIndex;
4911  LONG normalUpdateRightIndex;
4912  LONG normalTotalX;
4913  RECT cellRect;
4914  HBRUSH backBrush;
4915  HRGN oldClipRegion;
4916 
4917  PhTnpInitializeThemeData(Context);
4918 
4919  viewRect = Context->ClientRect;
4920 
4921  if (Context->VScrollVisible)
4922  viewRect.right -= Context->VScrollWidth;
4923 
4924  vScrollPosition = Context->VScrollPosition;
4925  hScrollPosition = Context->HScrollPosition;
4926 
4927  // Calculate the indicies of the first and last rows that need painting. These indicies are relative to the top of the view area.
4928 
4929  firstRowToUpdate = (PaintRect->top - Context->HeaderHeight) / Context->RowHeight;
4930  lastRowToUpdate = (PaintRect->bottom - 1 - Context->HeaderHeight) / Context->RowHeight; // minus one since bottom is exclusive
4931 
4932  if (firstRowToUpdate < 0)
4933  firstRowToUpdate = 0;
4934 
4935  rowRect.left = 0;
4936  rowRect.top = Context->HeaderHeight + firstRowToUpdate * Context->RowHeight;
4937  rowRect.right = Context->NormalLeft + Context->TotalViewX - Context->HScrollPosition;
4938  rowRect.bottom = rowRect.top + Context->RowHeight;
4939 
4940  // Change the indicies to absolute row indicies.
4941 
4942  firstRowToUpdate += vScrollPosition;
4943  lastRowToUpdate += vScrollPosition;
4944 
4945  if (lastRowToUpdate >= (LONG)Context->FlatList->Count)
4946  lastRowToUpdate = Context->FlatList->Count - 1; // becomes -1 when there are no items, handled correctly by loop below
4947 
4948  // Determine whether the fixed column needs painting, and which normal columns need painting.
4949 
4950  fixedUpdate = FALSE;
4951 
4952  if (Context->FixedColumnVisible && PaintRect->left < Context->FixedWidth)
4953  fixedUpdate = TRUE;
4954 
4955  x = Context->NormalLeft - hScrollPosition;
4956  normalUpdateLeftX = viewRect.right;
4957  normalUpdateLeftIndex = 0;
4958  normalUpdateRightX = 0;
4959  normalUpdateRightIndex = -1;
4960 
4961  for (j = 0; j < (LONG)Context->NumberOfColumnsByDisplay; j++)
4962  {
4963  column = Context->ColumnsByDisplay[j];
4964 
4965  if (x + column->Width >= Context->NormalLeft && x + column->Width > PaintRect->left && x < PaintRect->right)
4966  {
4967  if (normalUpdateLeftX > x)
4968  {
4969  normalUpdateLeftX = x;
4970  normalUpdateLeftIndex = j;
4971  }
4972 
4973  if (normalUpdateRightX < x + column->Width)
4974  {
4975  normalUpdateRightX = x + column->Width;
4976  normalUpdateRightIndex = j;
4977  }
4978  }
4979 
4980  x += column->Width;
4981  }
4982 
4983  normalTotalX = x;
4984 
4985  if (normalUpdateRightIndex >= (LONG)Context->NumberOfColumnsByDisplay)
4986  normalUpdateRightIndex = Context->NumberOfColumnsByDisplay - 1;
4987 
4988  // Paint the rows.
4989 
4990  SelectObject(hdc, Context->Font);
4991  SetBkMode(hdc, TRANSPARENT);
4992 
4993  for (i = firstRowToUpdate; i <= lastRowToUpdate; i++)
4994  {
4995  node = Context->FlatList->Items[i];
4996 
4997  // Prepare the row for drawing.
4998 
4999  PhTnpPrepareRowForDraw(Context, hdc, node);
5000 
5001  if (node->Selected && !Context->ThemeHasItemBackground)
5002  {
5003  // Non-themed background
5004  if (Context->HasFocus)
5005  {
5006  SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
5007  backBrush = GetSysColorBrush(COLOR_HIGHLIGHT);
5008  }
5009  else
5010  {
5011  SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT));
5012  backBrush = GetSysColorBrush(COLOR_BTNFACE);
5013  }
5014  }
5015  else
5016  {
5017  SetTextColor(hdc, node->s.DrawForeColor);
5018  SetDCBrushColor(hdc, node->s.DrawBackColor);
5019  backBrush = GetStockObject(DC_BRUSH);
5020  }
5021 
5022  FillRect(hdc, &rowRect, backBrush);
5023 
5024  if (Context->ThemeHasItemBackground)
5025  {
5026  INT stateId;
5027 
5028  // Themed background
5029 
5030  if (node->Selected)
5031  {
5032  if (i == Context->HotNodeIndex)
5033  stateId = TREIS_HOTSELECTED;
5034  else if (!Context->HasFocus)
5035  stateId = TREIS_SELECTEDNOTFOCUS;
5036  else
5037  stateId = TREIS_SELECTED;
5038  }
5039  else
5040  {
5041  if (i == Context->HotNodeIndex)
5042  stateId = TREIS_HOT;
5043  else
5044  stateId = -1;
5045  }
5046 
5047  if (stateId != -1)
5048  {
5049  if (!Context->FixedColumnVisible)
5050  {
5051  rowRect.left = Context->NormalLeft - hScrollPosition;
5052  }
5053 
5055  Context->ThemeData,
5056  hdc,
5057  TVP_TREEITEM,
5058  stateId,
5059  &rowRect,
5060  PaintRect
5061  );
5062  }
5063  }
5064 
5065  // Paint the fixed column.
5066 
5067  cellRect.top = rowRect.top;
5068  cellRect.bottom = rowRect.bottom;
5069 
5070  if (fixedUpdate)
5071  {
5072  cellRect.left = 0;
5073  cellRect.right = Context->FixedWidth;
5074  PhTnpDrawCell(Context, hdc, &cellRect, node, Context->FixedColumn, i, -1);
5075  }
5076 
5077  // Paint the normal columns.
5078 
5079  if (normalUpdateLeftX < normalUpdateRightX)
5080  {
5081  cellRect.left = normalUpdateLeftX;
5082  cellRect.right = cellRect.left;
5083 
5084  oldClipRegion = CreateRectRgn(0, 0, 0, 0);
5085 
5086  if (GetClipRgn(hdc, oldClipRegion) != 1)
5087  {
5088  DeleteObject(oldClipRegion);
5089  oldClipRegion = NULL;
5090  }
5091 
5092  IntersectClipRect(hdc, Context->NormalLeft, cellRect.top, viewRect.right, cellRect.bottom);
5093 
5094  for (j = normalUpdateLeftIndex; j <= normalUpdateRightIndex; j++)
5095  {
5096  column = Context->ColumnsByDisplay[j];
5097 
5098  cellRect.left = cellRect.right;
5099  cellRect.right = cellRect.left + column->Width;
5100  PhTnpDrawCell(Context, hdc, &cellRect, node, column, i, j);
5101  }
5102 
5103  SelectClipRgn(hdc, oldClipRegion);
5104 
5105  if (oldClipRegion)
5106  {
5107  DeleteObject(oldClipRegion);
5108  }
5109  }
5110 
5111  rowRect.top += Context->RowHeight;
5112  rowRect.bottom += Context->RowHeight;
5113  }
5114 
5115  if (lastRowToUpdate == Context->FlatList->Count - 1) // works even if there are no items
5116  {
5117  // Fill the rest of the space on the bottom with the window color.
5118  rowRect.bottom = viewRect.bottom;
5119  FillRect(hdc, &rowRect, GetSysColorBrush(COLOR_WINDOW));
5120  }
5121 
5122  if (normalTotalX < viewRect.right && viewRect.right > PaintRect->left && normalTotalX < PaintRect->right)
5123  {
5124  // Fill the rest of the space on the right with the window color.
5125  rowRect.left = normalTotalX;
5126  rowRect.top = Context->HeaderHeight;
5127  rowRect.right = viewRect.right;
5128  rowRect.bottom = viewRect.bottom;
5129  FillRect(hdc, &rowRect, GetSysColorBrush(COLOR_WINDOW));
5130  }
5131 
5132  if (Context->FlatList->Count == 0 && Context->EmptyText.Length != 0)
5133  {
5134  RECT textRect;
5135 
5136  textRect.left = 20;
5137  textRect.top = Context->HeaderHeight + 10;
5138  textRect.right = viewRect.right - 20;
5139  textRect.bottom = viewRect.bottom - 5;
5140 
5141  SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT));
5142  DrawText(
5143  hdc,
5144  Context->EmptyText.Buffer,
5145  (ULONG)Context->EmptyText.Length / 2,
5146  &textRect,
5147  DT_NOPREFIX | DT_CENTER | DT_END_ELLIPSIS
5148  );
5149  }
5150 
5151  if (Context->FixedDividerVisible && Context->FixedWidth >= PaintRect->left && Context->FixedWidth < PaintRect->right)
5152  {
5153  PhTnpDrawDivider(Context, hdc);
5154  }
5155 
5156  if (Context->DragSelectionActive)
5157  {
5158  PhTnpDrawSelectionRectangle(Context, hdc, &Context->DragRect);
5159  }
5160 }
5161 
5163  _In_ PPH_TREENEW_CONTEXT Context,
5164  _In_ HDC hdc,
5165  _Inout_ PPH_TREENEW_NODE Node
5166  )
5167 {
5168  if (!Node->s.CachedColorValid)
5169  {
5170  PH_TREENEW_GET_NODE_COLOR getNodeColor;
5171 
5172  getNodeColor.Flags = 0;
5173  getNodeColor.Node = Node;
5174  getNodeColor.BackColor = RGB(0xff, 0xff, 0xff);
5175  getNodeColor.ForeColor = RGB(0x00, 0x00, 0x00);
5176 
5177  if (Context->Callback(
5178  Context->Handle,
5180  &getNodeColor,
5181  NULL,
5182  Context->CallbackContext
5183  ))
5184  {
5185  Node->BackColor = getNodeColor.BackColor;
5186  Node->ForeColor = getNodeColor.ForeColor;
5187  Node->UseAutoForeColor = !!(getNodeColor.Flags & TN_AUTO_FORECOLOR);
5188 
5189  if (getNodeColor.Flags & TN_CACHE)
5190  Node->s.CachedColorValid = TRUE;
5191  }
5192  else
5193  {
5194  Node->BackColor = getNodeColor.BackColor;
5195  Node->ForeColor = getNodeColor.ForeColor;
5196  }
5197  }
5198 
5199  Node->s.DrawForeColor = Node->ForeColor;
5200 
5201  if (Node->UseTempBackColor)
5202  Node->s.DrawBackColor = Node->TempBackColor;
5203  else
5204  Node->s.DrawBackColor = Node->BackColor;
5205 
5206  if (!Node->s.CachedFontValid)
5207  {
5208  PH_TREENEW_GET_NODE_FONT getNodeFont;
5209 
5210  getNodeFont.Flags = 0;
5211  getNodeFont.Node = Node;
5212  getNodeFont.Font = NULL;
5213 
5214  if (Context->Callback(
5215  Context->Handle,
5217  &getNodeFont,
5218  NULL,
5219  Context->CallbackContext
5220  ))
5221  {
5222  Node->Font = getNodeFont.Font;
5223 
5224  if (getNodeFont.Flags & TN_CACHE)
5225  Node->s.CachedFontValid = TRUE;
5226  }
5227  else
5228  {
5229  Node->Font = NULL;
5230  }
5231  }
5232 
5233  if (!Node->s.CachedIconValid)
5234  {
5235  PH_TREENEW_GET_NODE_ICON getNodeIcon;
5236 
5237  getNodeIcon.Flags = 0;
5238  getNodeIcon.Node = Node;
5239  getNodeIcon.Icon = NULL;
5240 
5241  if (Context->Callback(
5242  Context->Handle,
5244  &getNodeIcon,
5245  NULL,
5246  Context->CallbackContext
5247  ))
5248  {
5249  Node->Icon = getNodeIcon.Icon;
5250 
5251  if (getNodeIcon.Flags & TN_CACHE)
5252  Node->s.CachedIconValid = TRUE;
5253  }
5254  else
5255  {
5256  Node->Icon = NULL;
5257  }
5258  }
5259 
5260  if (Node->UseAutoForeColor || Node->UseTempBackColor)
5261  {
5262  if (PhGetColorBrightness(Node->s.DrawBackColor) > 100) // slightly less than half
5263  Node->s.DrawForeColor = RGB(0x00, 0x00, 0x00);
5264  else
5265  Node->s.DrawForeColor = RGB(0xff, 0xff, 0xff);
5266  }
5267 }
5268 
5270  _In_ PPH_TREENEW_CONTEXT Context,
5271  _In_ HDC hdc,
5272  _In_ PRECT CellRect,
5273  _In_ PPH_TREENEW_NODE Node,
5274  _In_ PPH_TREENEW_COLUMN Column,
5275  _In_ LONG RowIndex,
5276  _In_ LONG ColumnIndex
5277  )
5278 {
5279  HFONT font; // font to use
5280  HFONT oldFont;
5281  PH_STRINGREF text; // text to draw
5282  RECT textRect; // working rectangle, modified as needed
5283  ULONG textFlags; // DT_* flags
5284  LONG iconVerticalMargin; // top/bottom margin for icons (determined using height of small icon)
5285 
5286  font = Node->Font;
5287  textFlags = Column->TextFlags;
5288 
5289  textRect = *CellRect;
5290 
5291  // Initial margins used by default list view
5292  textRect.left += TNP_CELL_LEFT_MARGIN;
5293  textRect.right -= TNP_CELL_RIGHT_MARGIN;
5294 
5295  // icon margin = (height of row - height of small icon) / 2
5296  iconVerticalMargin = ((textRect.bottom - textRect.top) - SmallIconHeight) / 2;
5297 
5298  textRect.top += iconVerticalMargin;
5299  textRect.bottom -= iconVerticalMargin;
5300 
5301  if (Column == Context->FirstColumn)
5302  {
5303  BOOLEAN needsClip;
5304  HRGN oldClipRegion;
5305 
5306  textRect.left += Node->Level * SmallIconWidth;
5307 
5308  // The icon may need to be clipped if the column is too small.
5309  needsClip = Column->Width < textRect.left + (Context->CanAnyExpand ? SmallIconWidth : 0) + (Node->Icon ? SmallIconWidth : 0);
5310 
5311  if (needsClip)
5312  {
5313  oldClipRegion = CreateRectRgn(0, 0, 0, 0);
5314 
5315  if (GetClipRgn(hdc, oldClipRegion) != 1)
5316  {
5317  DeleteObject(oldClipRegion);
5318  oldClipRegion = NULL;
5319  }
5320 
5321  // Clip contents to the column.
5322  IntersectClipRect(hdc, CellRect->left, textRect.top, CellRect->right, textRect.bottom);
5323  }
5324 
5325  if (Context->CanAnyExpand) // flag is used so we can avoid indenting when it's a flat list
5326  {
5327  BOOLEAN drewUsingTheme = FALSE;
5328  RECT themeRect;
5329 
5330  if (!Node->s.IsLeaf)
5331  {
5332  // Draw the plus/minus glyph.
5333 
5334  themeRect.left = textRect.left;
5335  themeRect.right = themeRect.left + SmallIconWidth;
5336  themeRect.top = textRect.top;
5337  themeRect.bottom = themeRect.top + SmallIconHeight;
5338 
5339  if (Context->ThemeHasGlyph)
5340  {
5341  INT partId;
5342  INT stateId;
5343 
5344  partId = (RowIndex == Context->HotNodeIndex && Node->s.PlusMinusHot && Context->ThemeHasHotGlyph) ? TVP_HOTGLYPH : TVP_GLYPH;
5345  stateId = Node->Expanded ? GLPS_OPENED : GLPS_CLOSED;
5346 
5347  if (SUCCEEDED(DrawThemeBackground_I(
5348  Context->ThemeData,
5349  hdc,
5350  partId,
5351  stateId,
5352  &themeRect,
5353  NULL
5354  )))
5355  drewUsingTheme = TRUE;
5356  }
5357 
5358  if (!drewUsingTheme)
5359  {
5360  ULONG glyphWidth;
5361  ULONG glyphHeight;
5362  RECT glyphRect;
5363 
5364  glyphWidth = SmallIconWidth / 2;
5365  glyphHeight = SmallIconHeight / 2;
5366 
5367  glyphRect.left = textRect.left + (SmallIconWidth - glyphWidth) / 2;
5368  glyphRect.right = glyphRect.left + glyphWidth;
5369  glyphRect.top = textRect.top + (SmallIconHeight - glyphHeight) / 2;
5370  glyphRect.bottom = glyphRect.top + glyphHeight;
5371 
5372  PhTnpDrawPlusMinusGlyph(hdc, &glyphRect, !Node->Expanded);
5373  }
5374  }
5375 
5376  textRect.left += SmallIconWidth;
5377  }
5378 
5379  // Draw the icon.
5380  if (Node->Icon)
5381  {
5382  DrawIconEx(
5383  hdc,
5384  textRect.left,
5385  textRect.top,
5386  Node->Icon,
5387  SmallIconWidth,
5388  SmallIconHeight,
5389  0,
5390  NULL,
5391  DI_NORMAL
5392  );
5393 
5394  textRect.left += SmallIconWidth + TNP_ICON_RIGHT_PADDING;
5395  }
5396 
5397  if (needsClip)
5398  {
5399  SelectClipRgn(hdc, oldClipRegion);
5400 
5401  if (oldClipRegion)
5402  DeleteObject(oldClipRegion);
5403  }
5404 
5405  if (textRect.left > textRect.right)
5406  textRect.left = textRect.right;
5407  }
5408 
5409  if (Column->CustomDraw)
5410  {
5411  BOOLEAN result;
5412  PH_TREENEW_CUSTOM_DRAW customDraw;
5413  INT savedDc;
5414 
5415  customDraw.Node = Node;
5416  customDraw.Column = Column;
5417  customDraw.Dc = hdc;
5418  customDraw.CellRect = *CellRect;
5419  customDraw.TextRect = textRect;
5420 
5421  // Fix up the rectangles before giving them to the user.
5422  if (customDraw.CellRect.left > customDraw.CellRect.right)
5423  customDraw.CellRect.left = customDraw.CellRect.right;
5424  if (customDraw.TextRect.left > customDraw.TextRect.right)
5425  customDraw.TextRect.left = customDraw.TextRect.right;
5426 
5427  savedDc = SaveDC(hdc);
5428  result = Context->Callback(Context->Handle, TreeNewCustomDraw, &customDraw, NULL, Context->CallbackContext);
5429  RestoreDC(hdc, savedDc);
5430 
5431  if (result)
5432  return;
5433  }
5434 
5435  if (PhTnpGetCellText(Context, Node, Column->Id, &text))
5436  {
5437  if (!(textFlags & (DT_PATH_ELLIPSIS | DT_WORD_ELLIPSIS)))
5438  textFlags |= DT_END_ELLIPSIS;
5439 
5440  textFlags |= DT_NOPREFIX | DT_VCENTER | DT_SINGLELINE;
5441 
5442  textRect.top = CellRect->top;
5443  textRect.bottom = CellRect->bottom;
5444 
5445  if (font)
5446  oldFont = SelectObject(hdc, font);
5447 
5448  DrawText(
5449  hdc,
5450  text.Buffer,
5451  (ULONG)text.Length / 2,
5452  &textRect,
5453  textFlags
5454  );
5455 
5456  if (font)
5457  SelectObject(hdc, oldFont);
5458  }
5459 }
5460 
5462  _In_ PPH_TREENEW_CONTEXT Context,
5463  _In_ HDC hdc
5464  )
5465 {
5466  POINT points[2];
5467 
5468  if (Context->AnimateDivider)
5469  {
5470  if (Context->DividerHot == 0 && !Context->HScrollVisible)
5471  return; // divider is invisible
5472 
5473  if (Context->DividerHot < 100)
5474  {
5475  BLENDFUNCTION blendFunction;
5476 
5477  // We need to draw and alpha blend the divider.
5478  // We can use the extra column allocated in the buffered context
5479  // to initially draw the divider.
5480 
5481  points[0].x = Context->ClientRect.right;
5482  points[0].y = Context->HeaderHeight;
5483  points[1].x = Context->ClientRect.right;
5484  points[1].y = Context->ClientRect.bottom;
5485  SetDCPenColor(Context->BufferedContext, RGB(0x77, 0x77, 0x77));
5486  SelectObject(Context->BufferedContext, GetStockObject(DC_PEN));
5487  Polyline(Context->BufferedContext, points, 2);
5488 
5489  blendFunction.BlendOp = AC_SRC_OVER;
5490  blendFunction.BlendFlags = 0;
5491  blendFunction.AlphaFormat = 0;
5492 
5493  // If the horizontal scroll bar is visible, we need to display a line even if
5494  // the divider is not hot. In this case we increase the base alpha value.
5495  if (!Context->HScrollVisible)
5496  blendFunction.SourceConstantAlpha = (UCHAR)(Context->DividerHot * 255 / 100);
5497  else
5498  blendFunction.SourceConstantAlpha = 55 + (UCHAR)(Context->DividerHot * 2);
5499 
5500  GdiAlphaBlend(
5501  hdc,
5502  Context->FixedWidth,
5503  Context->HeaderHeight,
5504  1,
5505  Context->ClientRect.bottom - Context->HeaderHeight,
5506  Context->BufferedContext,
5507  Context->ClientRect.right,
5508  Context->HeaderHeight,
5509  1,
5510  Context->ClientRect.bottom - Context->HeaderHeight,
5511  blendFunction
5512  );
5513 
5514  return;
5515  }
5516  }
5517 
5518  points[0].x = Context->FixedWidth;
5519  points[0].y = Context->HeaderHeight;
5520  points[1].x = Context->FixedWidth;
5521  points[1].y = Context->ClientRect.bottom;
5522  SetDCPenColor(hdc, RGB(0x77, 0x77, 0x77));
5523  SelectObject(hdc, GetStockObject(DC_PEN));
5524  Polyline(hdc, points, 2);
5525 }
5526 
5528  _In_ HDC hdc,
5529  _In_ PRECT Rect,
5530  _In_ BOOLEAN Plus
5531  )
5532 {
5533  INT savedDc;
5534  ULONG width;
5535  ULONG height;
5536  POINT points[2];
5537 
5538  savedDc = SaveDC(hdc);
5539 
5540  SelectObject(hdc, GetStockObject(DC_PEN));
5541  SetDCPenColor(hdc, RGB(0x55, 0x55, 0x55));
5542  SelectObject(hdc, GetStockObject(DC_BRUSH));
5543  SetDCBrushColor(hdc, RGB(0xff, 0xff, 0xff));
5544 
5545  width = Rect->right - Rect->left;
5546  height = Rect->bottom - Rect->top;
5547 
5548  // Draw the rectangle.
5549  Rectangle(hdc, Rect->left, Rect->top, Rect->right + 1, Rect->bottom + 1);
5550 
5551  SetDCPenColor(hdc, RGB(0x00, 0x00, 0x00));
5552 
5553  // Draw the horizontal line.
5554  points[0].x = Rect->left + 2;
5555  points[0].y = Rect->top + height / 2;
5556  points[1].x = Rect->right - 2 + 1;
5557  points[1].y = points[0].y;
5558  Polyline(hdc, points, 2);
5559 
5560  if (Plus)
5561  {
5562  // Draw the vertical line.
5563  points[0].x = Rect->left + width / 2;
5564  points[0].y = Rect->top + 2;
5565  points[1].x = points[0].x;
5566  points[1].y = Rect->bottom - 2 + 1;
5567  Polyline(hdc, points, 2);
5568  }
5569 
5570  RestoreDC(hdc, savedDc);
5571 }
5572 
5574  _In_ PPH_TREENEW_CONTEXT Context,
5575  _In_ HDC hdc,
5576  _In_ PRECT Rect
5577  )
5578 {
5579  RECT rect;
5580  BOOLEAN drewWithAlpha;
5581 
5582  rect = *Rect;
5583 
5584  // MSDN says FrameRect/DrawFocusRect doesn't draw anything if bottom <= top or right <= left.
5585  // That's complete rubbish. (And thanks for making me waste a whole hour on this redraw problem.)
5586  if (rect.right - rect.left == 0 || rect.bottom - rect.top == 0)
5587  return;
5588 
5589  drewWithAlpha = FALSE;
5590 
5591  if (Context->SelectionRectangleAlpha)
5592  {
5593  HDC tempDc;
5594  BITMAPINFOHEADER header;
5595  HBITMAP bitmap;
5596  HBITMAP oldBitmap;
5597  PVOID bits;
5598  RECT tempRect;
5599  BLENDFUNCTION blendFunction;
5600 
5601  tempDc = CreateCompatibleDC(hdc);
5602 
5603  if (tempDc)
5604  {
5605  memset(&header, 0, sizeof(BITMAPINFOHEADER));
5606  header.biSize = sizeof(BITMAPINFOHEADER);
5607  header.biWidth = 1;
5608  header.biHeight = 1;
5609  header.biPlanes = 1;
5610  header.biBitCount = 24;
5611  bitmap = CreateDIBSection(tempDc, (BITMAPINFO *)&header, DIB_RGB_COLORS, &bits, NULL, 0);
5612 
5613  if (bitmap)
5614  {
5615  // Draw the outline of the selection rectangle.
5616  FrameRect(hdc, &rect, GetSysColorBrush(COLOR_HIGHLIGHT));
5617 
5618  // Fill in the selection rectangle.
5619 
5620  oldBitmap = SelectObject(tempDc, bitmap);
5621  tempRect.left = 0;
5622  tempRect.top = 0;
5623  tempRect.right = 1;
5624  tempRect.bottom = 1;
5625  FillRect(tempDc, &tempRect, GetSysColorBrush(COLOR_HOTLIGHT));
5626 
5627  blendFunction.BlendOp = AC_SRC_OVER;
5628  blendFunction.BlendFlags = 0;
5629  blendFunction.SourceConstantAlpha = 70;
5630  blendFunction.AlphaFormat = 0;
5631 
5632  GdiAlphaBlend(
5633  hdc,
5634  rect.left,
5635  rect.top,
5636  rect.right - rect.left,
5637  rect.bottom - rect.top,
5638  tempDc,
5639  0,
5640  0,
5641  1,
5642  1,
5643  blendFunction
5644  );
5645 
5646  drewWithAlpha = TRUE;
5647 
5648  SelectObject(tempDc, oldBitmap);
5649  DeleteObject(bitmap);
5650  }
5651 
5652  DeleteDC(tempDc);
5653  }
5654  }
5655 
5656  if (!drewWithAlpha)
5657  {
5658  DrawFocusRect(hdc, &rect);
5659  }
5660 }
5661 
5663  _In_ PPH_TREENEW_CONTEXT Context,
5664  _In_ HDC hdc
5665  )
5666 {
5667  RECT windowRect;
5668  RECT clientRect;
5669  LONG sizingBorderWidth;
5670  LONG borderX;
5671  LONG borderY;
5672 
5673  GetWindowRect(Context->Handle, &windowRect);
5674  windowRect.right -= windowRect.left;
5675  windowRect.bottom -= windowRect.top;
5676  windowRect.left = 0;
5677  windowRect.top = 0;
5678 
5679  clientRect.left = windowRect.left + Context->SystemEdgeX;
5680  clientRect.top = windowRect.top + Context->SystemEdgeY;
5681  clientRect.right = windowRect.right - Context->SystemEdgeX;
5682  clientRect.bottom = windowRect.bottom - Context->SystemEdgeY;
5683 
5684  // Make sure we don't paint in the client area.
5685  ExcludeClipRect(hdc, clientRect.left, clientRect.top, clientRect.right, clientRect.bottom);
5686 
5687  // Draw the themed border.
5688  DrawThemeBackground_I(Context->ThemeData, hdc, 0, 0, &windowRect, NULL);
5689 
5690  // Calculate the size of the border we just drew, and fill in the rest of the space if we didn't
5691  // fully paint the region.
5692 
5693  if (SUCCEEDED(GetThemeInt_I(Context->ThemeData, 0, 0, TMT_SIZINGBORDERWIDTH, &sizingBorderWidth)))
5694  {
5695  borderX = sizingBorderWidth;
5696  borderY = sizingBorderWidth;
5697  }
5698  else
5699  {
5700  borderX = Context->SystemBorderX;
5701  borderY = Context->SystemBorderY;
5702  }
5703 
5704  if (borderX < Context->SystemEdgeX || borderY < Context->SystemEdgeY)
5705  {
5706  windowRect.left += Context->SystemEdgeX - borderX;
5707  windowRect.top += Context->SystemEdgeY - borderY;
5708  windowRect.right -= Context->SystemEdgeX - borderX;
5709  windowRect.bottom -= Context->SystemEdgeY - borderY;
5710  FillRect(hdc, &windowRect, GetSysColorBrush(COLOR_WINDOW));
5711  }
5712 }
5713 
5715  _In_ PPH_TREENEW_CONTEXT Context
5716  )
5717 {
5718  TOOLINFO toolInfo;
5719 
5720  Context->TooltipsHandle = CreateWindowEx(
5721  WS_EX_TRANSPARENT, // solves double-click problem
5722  TOOLTIPS_CLASS,
5723  NULL,
5724  WS_POPUP | TTS_NOPREFIX,
5725  0,
5726  0,
5727  0,
5728  0,
5729  NULL,
5730  NULL,
5731  Context->InstanceHandle,
5732  NULL
5733  );
5734 
5735  if (!Context->TooltipsHandle)
5736  return;
5737 
5738  // Item tooltips
5739  memset(&toolInfo, 0, sizeof(TOOLINFO));
5740  toolInfo.cbSize = sizeof(TOOLINFO);
5741  toolInfo.uFlags = TTF_TRANSPARENT;
5742  toolInfo.hwnd = Context->Handle;
5743  toolInfo.uId = TNP_TOOLTIPS_ITEM;
5744  toolInfo.lpszText = LPSTR_TEXTCALLBACK;
5745  toolInfo.lParam = TNP_TOOLTIPS_ITEM;
5746  SendMessage(Context->TooltipsHandle, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);
5747 
5748  // Fixed column tooltips
5749  toolInfo.uFlags = 0;
5750  toolInfo.hwnd = Context->FixedHeaderHandle;
5751  toolInfo.uId = TNP_TOOLTIPS_FIXED_HEADER;
5752  toolInfo.lpszText = LPSTR_TEXTCALLBACK;
5753  toolInfo.lParam = TNP_TOOLTIPS_FIXED_HEADER;
5754  SendMessage(Context->TooltipsHandle, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);
5755 
5756  // Normal column tooltips
5757  toolInfo.uFlags = 0;
5758  toolInfo.hwnd = Context->HeaderHandle;
5759  toolInfo.uId = TNP_TOOLTIPS_HEADER;
5760  toolInfo.lpszText = LPSTR_TEXTCALLBACK;
5761  toolInfo.lParam = TNP_TOOLTIPS_HEADER;
5762  SendMessage(Context->TooltipsHandle, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);
5763 
5764  // Hook the header control window procedures so we can forward mouse messages
5765  // to the tooltip control.
5766  Context->FixedHeaderOldWndProc = (WNDPROC)GetWindowLongPtr(Context->FixedHeaderHandle, GWLP_WNDPROC);
5767  SetProp(Context->FixedHeaderHandle, PhTnpMakeContextAtom(), (HANDLE)Context);
5768  SetWindowLongPtr(Context->FixedHeaderHandle, GWLP_WNDPROC, (LONG_PTR)PhTnpHeaderHookWndProc);
5769  Context->HeaderOldWndProc = (WNDPROC)GetWindowLongPtr(Context->HeaderHandle, GWLP_WNDPROC);
5770  SetProp(Context->HeaderHandle, PhTnpMakeContextAtom(), (HANDLE)Context);
5771  SetWindowLongPtr(Context->HeaderHandle, GWLP_WNDPROC, (LONG_PTR)PhTnpHeaderHookWndProc);
5772 
5773  SendMessage(Context->TooltipsHandle, TTM_SETMAXTIPWIDTH, 0, MAXSHORT); // no limit
5774  SendMessage(Context->TooltipsHandle, WM_SETFONT, (WPARAM)Context->Font, FALSE);
5775  Context->TooltipFont = Context->Font;
5776 }
5777 
5779  _In_ PPH_TREENEW_CONTEXT Context,
5780  _In_ PPOINT Point,
5781  _Out_ PWSTR *Text
5782  )
5783 {
5784  PH_TREENEW_HIT_TEST hitTest;
5785  BOOLEAN unfoldingTooltip;
5786  BOOLEAN unfoldingTooltipFromViewCancelled;
5787  PH_TREENEW_CELL_PARTS parts;
5788  LONG viewRight;
5789  PH_TREENEW_GET_CELL_TOOLTIP getCellTooltip;
5790 
5791  hitTest.Point = *Point;
5793  PhTnpHitTest(Context, &hitTest);
5794 
5795  if (Context->DragSelectionActive)
5796  return;
5797  if (!(hitTest.Flags & TN_HIT_ITEM))
5798  return;
5799  if (hitTest.Flags & TN_HIT_DIVIDER)
5800  return;
5801  if (!hitTest.Column)
5802  return;
5803 
5804  if (Context->TooltipIndex != hitTest.Node->Index || Context->TooltipId != hitTest.Column->Id)
5805  {
5806  Context->TooltipIndex = hitTest.Node->Index;
5807  Context->TooltipId = hitTest.Column->Id;
5808 
5809  getCellTooltip.Flags = 0;
5810  getCellTooltip.Node = hitTest.Node;
5811  getCellTooltip.Column = hitTest.Column;
5812  getCellTooltip.Unfolding = FALSE;
5813  PhInitializeEmptyStringRef(&getCellTooltip.Text);
5814  getCellTooltip.Font = Context->Font;
5815  getCellTooltip.MaximumWidth = -1;
5816 
5817  unfoldingTooltip = FALSE;
5818  unfoldingTooltipFromViewCancelled = FALSE;
5819 
5820  if (!(Context->ExtendedFlags & TN_FLAG_NO_UNFOLDING_TOOLTIPS) &&
5821  PhTnpGetCellParts(Context, hitTest.Node->Index, hitTest.Column, TN_MEASURE_TEXT, &parts) &&
5822  (parts.Flags & TN_PART_CONTENT) && (parts.Flags & TN_PART_TEXT))
5823  {
5824  viewRight = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0);
5825 
5826  // Use an unfolding tooltip if the text was truncated within the column, or the text
5827  // extends beyond the view area in either direction.
5828 
5829  if (parts.TextRect.left < parts.ContentRect.left || parts.TextRect.right > parts.ContentRect.right)
5830  {
5831  unfoldingTooltip = TRUE;
5832  }
5833  else if ((!hitTest.Column->Fixed && parts.TextRect.left < Context->NormalLeft) || parts.TextRect.right > viewRight)
5834  {
5835  // Only show view-based unfolding tooltips if the mouse is over the text itself.
5836  if (Point->x >= parts.TextRect.left && Point->x < parts.TextRect.right)
5837  unfoldingTooltip = TRUE;
5838  else
5839  unfoldingTooltipFromViewCancelled = TRUE;
5840  }
5841 
5842  if (unfoldingTooltip)
5843  {
5844  getCellTooltip.Unfolding = TRUE;
5845  getCellTooltip.Text = parts.Text;
5846  getCellTooltip.Font = parts.Font; // try to use the same font as the cell
5847 
5848  Context->TooltipRect = parts.TextRect;
5849  }
5850  }
5851 
5852  Context->Callback(Context->Handle, TreeNewGetCellTooltip, &getCellTooltip, NULL, Context->CallbackContext);
5853 
5854  Context->TooltipUnfolding = getCellTooltip.Unfolding;
5855 
5856  if (getCellTooltip.Text.Buffer && getCellTooltip.Text.Length != 0)
5857  {
5858  PhMoveReference(&Context->TooltipText, PhCreateString2(&getCellTooltip.Text));
5859  }
5860  else
5861  {
5862  PhClearReference(&Context->TooltipText);
5863 
5864  if (unfoldingTooltipFromViewCancelled)
5865  {
5866  // We may need to show the view-based unfolding tooltip if the mouse moves over the text in the future.
5867  // Reset the index and ID to make sure we keep checking.
5868  Context->TooltipIndex = -1;
5869  Context->TooltipId = -1;
5870  }
5871  }
5872 
5873  Context->NewTooltipFont = getCellTooltip.Font;
5874 
5875  if (!Context->NewTooltipFont)
5876  Context->NewTooltipFont = Context->Font;
5877 
5878  if (getCellTooltip.MaximumWidth <= MAXSHORT) // seems to be the maximum value that the tooltip control supports
5879  SendMessage(Context->TooltipsHandle, TTM_SETMAXTIPWIDTH, 0, getCellTooltip.MaximumWidth);
5880  else
5881  SendMessage(Context->TooltipsHandle, TTM_SETMAXTIPWIDTH, 0, MAXSHORT);
5882  }
5883 
5884  if (Context->TooltipText)
5885  *Text = Context->TooltipText->Buffer;
5886 }
5887 
5889  _In_ PPH_TREENEW_CONTEXT Context
5890  )
5891 {
5892  RECT rect;
5893 
5894  if (Context->TooltipFont != Context->NewTooltipFont)
5895  {
5896  Context->TooltipFont = Context->NewTooltipFont;
5897  SendMessage(Context->TooltipsHandle, WM_SETFONT, (WPARAM)Context->TooltipFont, FALSE);
5898  }
5899 
5900  if (!Context->TooltipUnfolding)
5901  {
5902  SetWindowPos(
5903  Context->TooltipsHandle,
5904  HWND_TOPMOST,
5905  0,
5906  0,
5907  0,
5908  0,
5909  SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_HIDEWINDOW
5910  );
5911 
5912  return FALSE;
5913  }
5914 
5915  rect = Context->TooltipRect;
5916  SendMessage(Context->TooltipsHandle, TTM_ADJUSTRECT, TRUE, (LPARAM)&rect);
5917  MapWindowPoints(Context->Handle, NULL, (POINT *)&rect, 2);
5918  SetWindowPos(
5919  Context->TooltipsHandle,
5920  HWND_TOPMOST,
5921  rect.left,
5922  rect.top,
5923  0,
5924  0,
5925  SWP_NOSIZE | SWP_NOACTIVATE | SWP_HIDEWINDOW
5926  );
5927 
5928  return TRUE;
5929 }
5930 
5932  _In_ PPH_TREENEW_CONTEXT Context
5933  )
5934 {
5935  Context->TooltipIndex = -1;
5936  Context->TooltipId = -1;
5937  Context->TooltipColumnId = -1;
5938 }
5939 
5941  _In_ PPH_TREENEW_CONTEXT Context
5942  )
5943 {
5944  if (Context->TooltipsHandle)
5945  {
5946  SendMessage(Context->TooltipsHandle, TTM_POP, 0, 0);
5947  PhTnpPrepareTooltipPop(Context);
5948  }
5949 }
5950 
5952  _In_ PPH_TREENEW_CONTEXT Context,
5953  _In_ BOOLEAN Fixed,
5954  _In_ PPOINT Point,
5955  _Out_opt_ PRECT ItemRect
5956  )
5957 {
5958  PPH_TREENEW_COLUMN column;
5959  RECT itemRect;
5960 
5961  if (Fixed)
5962  {
5963  if (!Context->FixedColumnVisible)
5964  return NULL;
5965 
5966  column = Context->FixedColumn;
5967 
5968  if (!Header_GetItemRect(Context->FixedHeaderHandle, 0, &itemRect))
5969  return NULL;
5970  }
5971  else
5972  {
5973  HDHITTESTINFO hitTestInfo;
5974 
5975  hitTestInfo.pt = *Point;
5976  hitTestInfo.flags = 0;
5977  hitTestInfo.iItem = -1;
5978 
5979  if (SendMessage(Context->HeaderHandle, HDM_HITTEST, 0, (LPARAM)&hitTestInfo) != -1 && hitTestInfo.iItem != -1)
5980  {
5981  HDITEM item;
5982 
5983  item.mask = HDI_LPARAM;
5984 
5985  if (!Header_GetItem(Context->HeaderHandle, hitTestInfo.iItem, &item))
5986  return NULL;
5987 
5988  column = (PPH_TREENEW_COLUMN)item.lParam;
5989 
5990  if (!Header_GetItemRect(Context->HeaderHandle, hitTestInfo.iItem, &itemRect))
5991  return NULL;
5992  }
5993  else
5994  {
5995  return NULL;
5996  }
5997  }
5998 
5999  if (ItemRect)
6000  *ItemRect = itemRect;
6001 
6002  return column;
6003 }
6004 
6006  _In_ PPH_TREENEW_CONTEXT Context,
6007  _In_ BOOLEAN Fixed,
6008  _In_ PPOINT Point,
6009  _Out_ PWSTR *Text
6010  )
6011 {
6012  LOGICAL result;
6013  PPH_TREENEW_COLUMN column;
6014  RECT itemRect;
6015  PWSTR text;
6016  SIZE_T textCount;
6017  HDC hdc;
6018  SIZE textSize;
6019 
6020  column = PhTnpHitTestHeader(Context, Fixed, Point, &itemRect);
6021 
6022  if (!column)
6023  return;
6024 
6025  if (Context->TooltipColumnId != column->Id)
6026  {
6027  // Determine if the tooltip needs to be shown.
6028 
6029  text = column->Text;
6030  textCount = PhCountStringZ(text);
6031 
6032  if (!(hdc = GetDC(Context->Handle)))
6033  return;
6034 
6035  SelectObject(hdc, Context->Font);
6036 
6037  result = GetTextExtentPoint32(hdc, text, (ULONG)textCount, &textSize);
6038  ReleaseDC(Context->Handle, hdc);
6039 
6040  if (!result)
6041  return;
6042 
6043  if (textSize.cx + 6 + 6 <= itemRect.right - itemRect.left) // HACK: Magic values (same as our cell margins?)
6044  return;
6045 
6046  Context->TooltipColumnId = column->Id;
6047  PhMoveReference(&Context->TooltipText, PhCreateStringEx(text, textCount * sizeof(WCHAR)));
6048  }
6049 
6050  *Text = Context->TooltipText->Buffer;
6051 
6052  // Always use the default parameters for column header tooltips.
6053  Context->NewTooltipFont = Context->Font;
6054  Context->TooltipUnfolding = FALSE;
6055  SendMessage(Context->TooltipsHandle, TTM_SETMAXTIPWIDTH, 0, TNP_TOOLTIPS_DEFAULT_MAXIMUM_WIDTH);
6056 }
6057 
6059  VOID
6060  )
6061 {
6062  PH_DEFINE_MAKE_ATOM(L"PhLib_TreeNewContext");
6063 }
6064 
6065 LRESULT CALLBACK PhTnpHeaderHookWndProc(
6066  _In_ HWND hwnd,
6067  _In_ UINT uMsg,
6068  _In_ WPARAM wParam,
6069  _In_ LPARAM lParam
6070  )
6071 {
6072  PPH_TREENEW_CONTEXT context;
6073  WNDPROC oldWndProc;
6074 
6075  context = (PPH_TREENEW_CONTEXT)GetProp(hwnd, PhTnpMakeContextAtom());
6076 
6077  if (hwnd == context->FixedHeaderHandle)
6078  oldWndProc = context->FixedHeaderOldWndProc;
6079  else
6080  oldWndProc = context->HeaderOldWndProc;
6081 
6082  switch (uMsg)
6083  {
6084  case WM_DESTROY:
6085  {
6086  SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc);
6087 
6088  RemoveProp(hwnd, PhTnpMakeContextAtom());
6089  }
6090  break;
6091  case WM_MOUSEMOVE:
6092  {
6093  POINT point;
6094  PPH_TREENEW_COLUMN column;
6095  ULONG id;
6096 
6097  point.x = GET_X_LPARAM(lParam);
6098  point.y = GET_Y_LPARAM(lParam);
6099  column = PhTnpHitTestHeader(context, hwnd == context->FixedHeaderHandle, &point, NULL);
6100 
6101  if (column)
6102  id = column->Id;
6103  else
6104  id = -1;
6105 
6106  if (context->TooltipColumnId != id)
6107  {
6108  PhTnpPopTooltip(context);
6109  }
6110  }
6111  break;
6112  case WM_NOTIFY:
6113  {
6114  NMHDR *header = (NMHDR *)lParam;
6115 
6116  switch (header->code)
6117  {
6118  case TTN_GETDISPINFO:
6119  {
6120  if (header->hwndFrom == context->TooltipsHandle)
6121  {
6122  NMTTDISPINFO *info = (NMTTDISPINFO *)header;
6123  POINT point;
6124 
6125  PhTnpGetMessagePos(hwnd, &point);
6126  PhTnpGetHeaderTooltipText(context, info->lParam == TNP_TOOLTIPS_FIXED_HEADER, &point, &info->lpszText);
6127  }
6128  }
6129  break;
6130  case TTN_SHOW:
6131  {
6132  if (header->hwndFrom == context->TooltipsHandle)
6133  {
6134  return PhTnpPrepareTooltipShow(context);
6135  }
6136  }
6137  break;
6138  case TTN_POP:
6139  {
6140  if (header->hwndFrom == context->TooltipsHandle)
6141  {
6142  PhTnpPrepareTooltipPop(context);
6143  }
6144  }
6145  break;
6146  }
6147  }
6148  break;
6149  }
6150 
6151  switch (uMsg)
6152  {
6153  case WM_MOUSEMOVE:
6154  case WM_LBUTTONDOWN:
6155  case WM_LBUTTONUP:
6156  case WM_RBUTTONDOWN:
6157  case WM_RBUTTONUP:
6158  case WM_MBUTTONDOWN:
6159  case WM_MBUTTONUP:
6160  {
6161  if (context->TooltipsHandle)
6162  {
6163  MSG message;
6164 
6165  message.hwnd = hwnd;
6166  message.message = uMsg;
6167  message.wParam = wParam;
6168  message.lParam = lParam;
6169  SendMessage(context->TooltipsHandle, TTM_RELAYEVENT, 0, (LPARAM)&message);
6170  }
6171  }
6172  break;
6173  }
6174 
6175  return CallWindowProc(oldWndProc, hwnd, uMsg, wParam, lParam);
6176 }
6177 
6179  _In_ PPH_TREENEW_CONTEXT Context,
6180  _In_ LONG CursorX,
6181  _In_ LONG CursorY,
6182  _In_ BOOLEAN DispatchMessages,
6183  _Out_opt_ PULONG CancelledByMessage
6184  )
6185 {
6186  RECT dragRect;
6187  MSG msg;
6188 
6189  // Capture mouse input and see if the user moves the mouse beyond the drag rectangle.
6190 
6191  dragRect.left = CursorX - Context->SystemDragX;
6192  dragRect.top = CursorY - Context->SystemDragY;
6193  dragRect.right = CursorX + Context->SystemDragX;
6194  dragRect.bottom = CursorY + Context->SystemDragY;
6195  MapWindowPoints(Context->Handle, NULL, (POINT *)&dragRect, 2);
6196 
6197  SetCapture(Context->Handle);
6198 
6199  if (CancelledByMessage)
6200  *CancelledByMessage = 0;
6201 
6202  do
6203  {
6204  // It seems that GetMessage dispatches nonqueued messages directly from kernel-mode, so
6205  // we have to use PeekMessage and WaitMessage in order to process WM_CAPTURECHANGED messages.
6206  if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
6207  {
6208  switch (msg.message)
6209  {
6210  case WM_LBUTTONDOWN:
6211  case WM_LBUTTONUP:
6212  case WM_RBUTTONDOWN:
6213  case WM_RBUTTONUP:
6214  ReleaseCapture();
6215 
6216  if (CancelledByMessage)
6217  *CancelledByMessage = msg.message;
6218 
6219  break;
6220  case WM_MOUSEMOVE:
6221  if (msg.pt.x < dragRect.left || msg.pt.x >= dragRect.right ||
6222  msg.pt.y < dragRect.top || msg.pt.y >= dragRect.bottom)
6223  {
6224  if (IsWindow(Context->Handle))
6225  return TRUE;
6226  else
6227  return FALSE;
6228  }
6229  break;
6230  default:
6231  if (DispatchMessages)
6232  {
6233  TranslateMessage(&msg);
6234  DispatchMessage(&msg);
6235  }
6236  break;
6237  }
6238  }
6239  else
6240  {
6241  WaitMessage();
6242  }
6243  } while (IsWindow(Context->Handle) && GetCapture() == Context->Handle);
6244 
6245  return FALSE;
6246 }
6247 
6249  _In_ PPH_TREENEW_CONTEXT Context,
6250  _In_ LONG CursorX,
6251  _In_ LONG CursorY
6252  )
6253 {
6254  MSG msg;
6255  LONG cursorX;
6256  LONG cursorY;
6257  BOOLEAN originFixed;
6258  RECT dragRect;
6259  RECT oldDragRect;
6260  RECT windowRect;
6261  POINT cursorPoint;
6262  BOOLEAN showContextMenu;
6263 
6264  cursorX = CursorX;
6265  cursorY = CursorY;
6266  originFixed = cursorX < Context->FixedWidth;
6267 
6268  dragRect.left = cursorX;
6269  dragRect.top = cursorY;
6270  dragRect.right = cursorX;
6271  dragRect.bottom = cursorY;
6272  oldDragRect = dragRect;
6273  Context->DragRect = dragRect;
6274  Context->DragSelectionActive = TRUE;
6275 
6276  if (Context->DoubleBuffered)
6277  Context->SelectionRectangleAlpha = TRUE;
6278  // TODO: Make sure the monitor's color depth is sufficient for alpha-blended selection rectangles.
6279 
6280  GetWindowRect(Context->Handle, &windowRect);
6281 
6282  cursorPoint.x = windowRect.left + cursorX;
6283  cursorPoint.y = windowRect.top + cursorY;
6284 
6285  showContextMenu = FALSE;
6286 
6287  SetCapture(Context->Handle);
6288 
6289  while (TRUE)
6290  {
6291  if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
6292  {
6293  BOOLEAN leftOrRight;
6294  BOOLEAN aboveOrBelow;
6295 
6296  // If the cursor is outside of the window, generate some messages
6297  // so the window keeps scrolling.
6298 
6299  leftOrRight = cursorPoint.x < windowRect.left || cursorPoint.x > windowRect.right;
6300  aboveOrBelow = cursorPoint.y < windowRect.top || cursorPoint.y > windowRect.bottom;
6301 
6302  if ((Context->VScrollVisible && aboveOrBelow && PhTnpCanScroll(Context, FALSE, cursorPoint.y > windowRect.bottom)) ||
6303  (Context->HScrollVisible && leftOrRight && PhTnpCanScroll(Context, TRUE, cursorPoint.x > windowRect.right)))
6304  {
6305  SetCursorPos(cursorPoint.x, cursorPoint.y);
6306  }
6307  else
6308  {
6309  WaitMessage();
6310  }
6311 
6312  goto EndOfLoop;
6313  }
6314 
6315  cursorPoint = msg.pt;
6316 
6317  switch (msg.message)
6318  {
6319  case WM_LBUTTONDOWN:
6320  case WM_LBUTTONUP:
6321  case WM_RBUTTONDOWN:
6322  case WM_MBUTTONDOWN:
6323  case WM_MBUTTONUP:
6324  ReleaseCapture();
6325  goto EndOfLoop;
6326  case WM_RBUTTONUP:
6327  ReleaseCapture();
6328  showContextMenu = TRUE;
6329  goto EndOfLoop;
6330  case WM_MOUSEMOVE:
6331  {
6332  LONG newCursorX;
6333  LONG newCursorY;
6334  LONG deltaRows;
6335  LONG deltaX;
6336  LONG oldVScrollPosition;
6337  LONG oldHScrollPosition;
6338  LONG newDeltaX;
6339  LONG newDeltaY;
6340  LONG viewLeft;
6341  LONG viewTop;
6342  LONG viewRight;
6343  LONG viewBottom;
6344  LONG temp;
6345  RECT totalRect;
6346 
6347  newCursorX = GET_X_LPARAM(msg.lParam);
6348  newCursorY = GET_Y_LPARAM(msg.lParam);
6349 
6350  // Scroll the window if the cursor is outside of it.
6351 
6352  deltaRows = 0;
6353  deltaX = 0;
6354 
6355  if (Context->VScrollVisible)
6356  {
6357  if (cursorPoint.y < windowRect.top)
6358  deltaRows = -(windowRect.top - cursorPoint.y + Context->RowHeight - 1) / Context->RowHeight; // scroll up
6359  else if (cursorPoint.y >= windowRect.bottom)
6360  deltaRows = (cursorPoint.y - windowRect.bottom + Context->RowHeight - 1) / Context->RowHeight; // scroll down
6361  }
6362 
6363  if (Context->HScrollVisible)
6364  {
6365  if (cursorPoint.x < windowRect.left)
6366  deltaX = -(windowRect.left - cursorPoint.x); // scroll left
6367  else if (cursorPoint.x >= windowRect.right)
6368  deltaX = cursorPoint.x - windowRect.right; // scroll right
6369  }
6370 
6371  oldVScrollPosition = Context->VScrollPosition;
6372  oldHScrollPosition = Context->HScrollPosition;
6373 
6374  if (deltaRows != 0 || deltaX != 0)
6375  PhTnpScroll(Context, deltaRows, deltaX);
6376 
6377  newDeltaX = oldHScrollPosition - Context->HScrollPosition;
6378  newDeltaY = (oldVScrollPosition - Context->VScrollPosition) * Context->RowHeight;
6379 
6380  // Adjust our original drag point for the scrolling.
6381  if (!originFixed)
6382  cursorX += newDeltaX;
6383  cursorY += newDeltaY;
6384 
6385  // Adjust the old drag rectangle for the scrolling.
6386  if (!originFixed)
6387  oldDragRect.left += newDeltaX;
6388  oldDragRect.top += newDeltaY;
6389  if (!originFixed)
6390  oldDragRect.right += newDeltaX;
6391  oldDragRect.bottom += newDeltaY;
6392 
6393  // Ensure that the new cursor position is within the content area.
6394 
6395  viewLeft = Context->FixedColumnVisible ? 0 : -Context->HScrollPosition;
6396  viewTop = Context->HeaderHeight - Context->VScrollPosition;
6397  viewRight = Context->NormalLeft + Context->TotalViewX - Context->HScrollPosition;
6398  viewBottom = Context->HeaderHeight + ((LONG)Context->FlatList->Count - Context->VScrollPosition) * Context->RowHeight;
6399 
6400  temp = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0);
6401  viewRight = max(viewRight, temp);
6402  temp = Context->ClientRect.bottom - ((!Context->FixedColumnVisible && Context->HScrollVisible) ? Context->HScrollHeight : 0);
6403  viewBottom = max(viewBottom, temp);
6404 
6405  if (newCursorX < viewLeft)
6406  newCursorX = viewLeft;
6407  if (newCursorX > viewRight)
6408  newCursorX = viewRight;
6409  if (newCursorY < viewTop)
6410  newCursorY = viewTop;
6411  if (newCursorY > viewBottom)
6412  newCursorY = viewBottom;
6413 
6414  // Create the new drag rectangle.
6415 
6416  if (cursorX < newCursorX)
6417  {
6418  dragRect.left = cursorX;
6419  dragRect.right = newCursorX;
6420  }
6421  else
6422  {
6423  dragRect.left = newCursorX;
6424  dragRect.right = cursorX;
6425  }
6426 
6427  if (cursorY < newCursorY)
6428  {
6429  dragRect.top = cursorY;
6430  dragRect.bottom = newCursorY;
6431  }
6432  else
6433  {
6434  dragRect.top = newCursorY;
6435  dragRect.bottom = cursorY;
6436  }
6437 
6438  // Has anything changed from before?
6439  if (dragRect.left == oldDragRect.left && dragRect.top == oldDragRect.top &&
6440  dragRect.right == oldDragRect.right && dragRect.bottom == oldDragRect.bottom)
6441  {
6442  break;
6443  }
6444 
6445  Context->DragRect = dragRect;
6446 
6447  // Process the selection.
6448  totalRect.left = min(dragRect.left, oldDragRect.left);
6449  totalRect.top = min(dragRect.top, oldDragRect.top);
6450  totalRect.right = max(dragRect.right, oldDragRect.right);
6451  totalRect.bottom = max(dragRect.bottom, oldDragRect.bottom);
6452  PhTnpProcessDragSelect(Context, (ULONG)msg.wParam, &oldDragRect, &dragRect, &totalRect);
6453 
6454  // Redraw the drag rectangle.
6455  RedrawWindow(Context->Handle, &totalRect, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
6456 
6457  oldDragRect = dragRect;
6458  }
6459  break;
6460  case WM_MOUSELEAVE:
6461  break; // don't process
6462  case WM_MOUSEWHEEL:
6463  break; // don't process
6464  case WM_KEYDOWN:
6465  if (msg.wParam == VK_ESCAPE)
6466  {
6467  ULONG changedStart;
6468  ULONG changedEnd;
6469  RECT rect;
6470 
6471  PhTnpSelectRange(Context, -1, -1, TN_SELECT_RESET, &changedStart, &changedEnd);
6472 
6473  if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
6474  {
6475  InvalidateRect(Context->Handle, &rect, FALSE);
6476  }
6477 
6478  ReleaseCapture();
6479  }
6480  break; // don't process
6481  case WM_CHAR:
6482  break; // don't process
6483  default:
6484  TranslateMessage(&msg);
6485  DispatchMessage(&msg);
6486  break;
6487  }
6488 
6489 EndOfLoop:
6490  if (GetCapture() != Context->Handle)
6491  break;
6492  }
6493 
6494  Context->DragSelectionActive = FALSE;
6495  RedrawWindow(Context->Handle, &dragRect, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
6496 
6497  if (showContextMenu)
6498  {
6499  // Display a context menu at the original drag point.
6500  SendMessage(Context->Handle, WM_CONTEXTMENU, (WPARAM)Context->Handle, MAKELPARAM(windowRect.left + CursorX, windowRect.top + CursorY));
6501  }
6502 }
6503 
6505  _In_ PPH_TREENEW_CONTEXT Context,
6506  _In_ ULONG VirtualKeys,
6507  _In_ PRECT OldRect,
6508  _In_ PRECT NewRect,
6509  _In_ PRECT TotalRect
6510  )
6511 {
6512  LONG firstRow;
6513  LONG lastRow;
6514  RECT rowRect;
6515  LONG i;
6516  PPH_TREENEW_NODE node;
6517  LONG changedStart;
6518  LONG changedEnd;
6519  RECT rect;
6520 
6521  // Determine which rows we need to test. The divisions below must be done on positive integers to ensure correct rounding.
6522 
6523  firstRow = (TotalRect->top - Context->HeaderHeight + Context->VScrollPosition * Context->RowHeight) / Context->RowHeight;
6524  lastRow = (TotalRect->bottom - 1 - Context->HeaderHeight + Context->VScrollPosition * Context->RowHeight) / Context->RowHeight;
6525 
6526  if (firstRow < 0)
6527  firstRow = 0;
6528  if (lastRow >= (LONG)Context->FlatList->Count)
6529  lastRow = Context->FlatList->Count - 1;
6530 
6531  rowRect.left = 0;
6532  rowRect.top = Context->HeaderHeight + (firstRow - Context->VScrollPosition) * Context->RowHeight;
6533  rowRect.right = Context->NormalLeft + Context->TotalViewX - Context->HScrollPosition;
6534  rowRect.bottom = rowRect.top + Context->RowHeight;
6535 
6536  changedStart = lastRow;
6537  changedEnd = firstRow;
6538 
6539  // Process the rows.
6540  for (i = firstRow; i <= lastRow; i++)
6541  {
6542  BOOLEAN inOldRect;
6543  BOOLEAN inNewRect;
6544 
6545  node = Context->FlatList->Items[i];
6546 
6547  inOldRect = rowRect.top < OldRect->bottom && rowRect.bottom > OldRect->top &&
6548  rowRect.left < OldRect->right && rowRect.right > OldRect->left;
6549  inNewRect = rowRect.top < NewRect->bottom && rowRect.bottom > NewRect->top &&
6550  rowRect.left < NewRect->right && rowRect.right > NewRect->left;
6551 
6552  if (VirtualKeys & MK_CONTROL)
6553  {
6554  if (!node->Unselectable && inOldRect != inNewRect)
6555  {
6556  node->Selected = !node->Selected;
6557 
6558  if (changedStart > i)
6559  changedStart = i;
6560  if (changedEnd < i)
6561  changedEnd = i;
6562  }
6563  }
6564  else
6565  {
6566  if (!node->Unselectable && inOldRect != inNewRect)
6567  {
6568  node->Selected = inNewRect;
6569 
6570  if (changedStart > i)
6571  changedStart = i;
6572  if (changedEnd < i)
6573  changedEnd = i;
6574  }
6575  }
6576 
6577  rowRect.top = rowRect.bottom;
6578  rowRect.bottom += Context->RowHeight;
6579  }
6580 
6581  if (changedStart <= changedEnd)
6582  {
6583  Context->Callback(Context->Handle, TreeNewSelectionChanged, NULL, NULL, Context->CallbackContext);
6584  }
6585 
6586  if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect))
6587  {
6588  InvalidateRect(Context->Handle, &rect, FALSE);
6589  }
6590 }
6591 
6593  _In_ PPH_TREENEW_CONTEXT Context
6594  )
6595 {
6596  HDC hdc;
6597 
6598  if (hdc = GetDC(Context->Handle))
6599  {
6600  Context->BufferedContext = CreateCompatibleDC(hdc);
6601 
6602  if (!Context->BufferedContext)
6603  return;
6604 
6605  Context->BufferedContextRect = Context->ClientRect;
6606  Context->BufferedBitmap = CreateCompatibleBitmap(
6607  hdc,
6608  Context->BufferedContextRect.right + 1, // leave one extra pixel for divider animation
6609  Context->BufferedContextRect.bottom
6610  );
6611 
6612  if (!Context->BufferedBitmap)
6613  {
6614  DeleteDC(Context->BufferedContext);
6615  Context->BufferedContext = NULL;
6616  return;
6617  }
6618 
6619  ReleaseDC(Context->Handle, hdc);
6620  Context->BufferedOldBitmap = SelectObject(Context->BufferedContext, Context->BufferedBitmap);
6621  }
6622 }
6623 
6625  _In_ PPH_TREENEW_CONTEXT Context
6626  )
6627 {
6628  // The original bitmap must be selected back into the context, otherwise
6629  // the bitmap can't be deleted.
6630  SelectObject(Context->BufferedContext, Context->BufferedOldBitmap);
6631  DeleteObject(Context->BufferedBitmap);
6632  DeleteDC(Context->BufferedContext);
6633 
6634  Context->BufferedContext = NULL;
6635  Context->BufferedBitmap = NULL;
6636 }
6637 
6639  _In_ HWND hwnd,
6640  _Out_ PPOINT ClientPoint
6641  )
6642 {
6643  ULONG position;
6644  POINT point;
6645 
6646  position = GetMessagePos();
6647  point.x = GET_X_LPARAM(position);
6648  point.y = GET_Y_LPARAM(position);
6649  ScreenToClient(hwnd, &point);
6650 
6651  *ClientPoint = point;
6652 }