1 /*
2  * Regedit child window
3  *
4  * Copyright (C) 2002 Robert Dickenson <robd@reactos.org>
5  * Copyright (C) 2024 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21 
22 #include "regedit.h"
23 
24 ChildWnd* g_pChildWnd;
25 static int last_split;
26 HBITMAP SizingPattern = 0;
27 HBRUSH  SizingBrush = 0;
28 WCHAR Suggestions[256];
29 
30 extern LPCWSTR get_root_key_name(HKEY hRootKey)
31 {
32     if (hRootKey == HKEY_CLASSES_ROOT) return L"HKEY_CLASSES_ROOT";
33     if (hRootKey == HKEY_CURRENT_USER) return L"HKEY_CURRENT_USER";
34     if (hRootKey == HKEY_LOCAL_MACHINE) return L"HKEY_LOCAL_MACHINE";
35     if (hRootKey == HKEY_USERS) return L"HKEY_USERS";
36     if (hRootKey == HKEY_CURRENT_CONFIG) return L"HKEY_CURRENT_CONFIG";
37     if (hRootKey == HKEY_DYN_DATA) return L"HKEY_DYN_DATA";
38 
39     return L"UNKNOWN HKEY, PLEASE REPORT";
40 }
41 
42 static INT ClampSplitBarX(HWND hWnd, INT x)
43 {
44     RECT rc;
45     GetClientRect(hWnd, &rc);
46     return min(max(x, SPLIT_MIN), rc.right - SPLIT_MIN);
47 }
48 
49 extern void ResizeWnd(int cx, int cy)
50 {
51     HDWP hdwp = BeginDeferWindowPos(4);
52     RECT rt, rs, rb;
53     const int nButtonWidth = 44;
54     const int nButtonHeight = 22;
55     int cyEdge = GetSystemMetrics(SM_CYEDGE);
56     const UINT uFlags = SWP_NOZORDER | SWP_NOACTIVATE;
57     SetRect(&rt, 0, 0, cx, cy);
58     cy = 0;
59     if (hStatusBar != NULL)
60     {
61         GetWindowRect(hStatusBar, &rs);
62         cy = rs.bottom - rs.top;
63     }
64     GetWindowRect(g_pChildWnd->hAddressBtnWnd, &rb);
65 
66     g_pChildWnd->nSplitPos = ClampSplitBarX(g_pChildWnd->hWnd, g_pChildWnd->nSplitPos);
67 
68     cx = g_pChildWnd->nSplitPos + SPLIT_WIDTH / 2;
69     if (hdwp)
70         hdwp = DeferWindowPos(hdwp, g_pChildWnd->hAddressBarWnd, NULL,
71                               rt.left, rt.top,
72                               rt.right - rt.left - nButtonWidth, nButtonHeight,
73                               uFlags);
74     if (hdwp)
75         hdwp = DeferWindowPos(hdwp, g_pChildWnd->hAddressBtnWnd, NULL,
76                               rt.right - nButtonWidth, rt.top,
77                               nButtonWidth, nButtonHeight,
78                               uFlags);
79     if (hdwp)
80         hdwp = DeferWindowPos(hdwp, g_pChildWnd->hTreeWnd, NULL,
81                               rt.left,
82                               rt.top + nButtonHeight + cyEdge,
83                               g_pChildWnd->nSplitPos - SPLIT_WIDTH/2 - rt.left,
84                               rt.bottom - rt.top - cy - 2 * cyEdge,
85                               uFlags);
86     if (hdwp)
87         hdwp = DeferWindowPos(hdwp, g_pChildWnd->hListWnd, NULL,
88                               rt.left + cx,
89                               rt.top + nButtonHeight + cyEdge,
90                               rt.right - cx,
91                               rt.bottom - rt.top - cy - 2 * cyEdge,
92                               uFlags);
93     if (hdwp)
94         EndDeferWindowPos(hdwp);
95 }
96 
97 /*******************************************************************************
98  * Local module support methods
99  */
100 
101 static void draw_splitbar(HWND hWnd, int x)
102 {
103     RECT rt;
104     HGDIOBJ OldObj;
105     HDC hdc = GetDC(hWnd);
106 
107     if(!SizingPattern)
108     {
109         const DWORD Pattern[4] = {0x5555AAAA, 0x5555AAAA, 0x5555AAAA, 0x5555AAAA};
110         SizingPattern = CreateBitmap(8, 8, 1, 1, Pattern);
111     }
112     if(!SizingBrush)
113     {
114         SizingBrush = CreatePatternBrush(SizingPattern);
115     }
116     GetClientRect(hWnd, &rt);
117     rt.left = x - SPLIT_WIDTH/2;
118     rt.right = x + SPLIT_WIDTH/2+1;
119     OldObj = SelectObject(hdc, SizingBrush);
120     PatBlt(hdc, rt.left, rt.top, rt.right - rt.left, rt.bottom - rt.top, PATINVERT);
121     SelectObject(hdc, OldObj);
122     ReleaseDC(hWnd, hdc);
123 }
124 
125 /*******************************************************************************
126  * finish_splitbar [internal]
127  *
128  * make the splitbar invisible and resize the windows
129  * (helper for ChildWndProc)
130  */
131 static void finish_splitbar(HWND hWnd, int x)
132 {
133     RECT rt;
134 
135     draw_splitbar(hWnd, last_split);
136     last_split = -1;
137     GetClientRect(hWnd, &rt);
138     g_pChildWnd->nSplitPos = x;
139     ResizeWnd(rt.right, rt.bottom);
140     InvalidateRect(hWnd, &rt, FALSE); // HACK: See CORE-19576
141     ReleaseCapture();
142 }
143 
144 /*******************************************************************************
145  *
146  *  Key suggestion
147  */
148 
149 #define MIN(a,b)    ((a < b) ? (a) : (b))
150 
151 static void SuggestKeys(HKEY hRootKey, LPCWSTR pszKeyPath, LPWSTR pszSuggestions,
152                         size_t iSuggestionsLength)
153 {
154     WCHAR szBuffer[256];
155     WCHAR szLastFound[256];
156     size_t i;
157     HKEY hOtherKey, hSubKey;
158     BOOL bFound;
159     const REGSAM regsam = KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE;
160 
161     memset(pszSuggestions, 0, iSuggestionsLength * sizeof(*pszSuggestions));
162     iSuggestionsLength--;
163 
164     /* Are we a root key in HKEY_CLASSES_ROOT? */
165     if ((hRootKey == HKEY_CLASSES_ROOT) && pszKeyPath[0] && !wcschr(pszKeyPath, L'\\'))
166     {
167         do
168         {
169             bFound = FALSE;
170 
171             /* Check default key */
172             if (QueryStringValue(hRootKey, pszKeyPath, NULL,
173                                  szBuffer, ARRAY_SIZE(szBuffer)) == ERROR_SUCCESS)
174             {
175                 /* Sanity check this key; it cannot be empty, nor can it be a
176                  * loop back */
177                 if ((szBuffer[0] != L'\0') && _wcsicmp(szBuffer, pszKeyPath))
178                 {
179                     if (RegOpenKeyExW(hRootKey, szBuffer, 0, regsam, &hOtherKey) == ERROR_SUCCESS)
180                     {
181                         lstrcpynW(pszSuggestions, L"HKCR\\", (int) iSuggestionsLength);
182                         i = wcslen(pszSuggestions);
183                         pszSuggestions += i;
184                         iSuggestionsLength -= i;
185 
186                         lstrcpynW(pszSuggestions, szBuffer, (int) iSuggestionsLength);
187                         i = MIN(wcslen(pszSuggestions) + 1, iSuggestionsLength);
188                         pszSuggestions += i;
189                         iSuggestionsLength -= i;
190                         RegCloseKey(hOtherKey);
191 
192                         bFound = TRUE;
193                         StringCbCopyW(szLastFound, sizeof(szLastFound), szBuffer);
194                         pszKeyPath = szLastFound;
195                     }
196                 }
197             }
198         }
199         while(bFound && (iSuggestionsLength > 0));
200 
201         /* Check CLSID key */
202         if (RegOpenKeyExW(hRootKey, pszKeyPath, 0, regsam, &hSubKey) == ERROR_SUCCESS)
203         {
204             if (QueryStringValue(hSubKey, L"CLSID", NULL, szBuffer,
205                                  ARRAY_SIZE(szBuffer)) == ERROR_SUCCESS)
206             {
207                 lstrcpynW(pszSuggestions, L"HKCR\\CLSID\\", (int)iSuggestionsLength);
208                 i = wcslen(pszSuggestions);
209                 pszSuggestions += i;
210                 iSuggestionsLength -= i;
211 
212                 lstrcpynW(pszSuggestions, szBuffer, (int)iSuggestionsLength);
213                 i = MIN(wcslen(pszSuggestions) + 1, iSuggestionsLength);
214                 pszSuggestions += i;
215                 iSuggestionsLength -= i;
216             }
217             RegCloseKey(hSubKey);
218         }
219     }
220     else if ((hRootKey == HKEY_CURRENT_USER || hRootKey == HKEY_LOCAL_MACHINE) && *pszKeyPath)
221     {
222         LPCWSTR rootstr = hRootKey == HKEY_CURRENT_USER ? L"HKLM" : L"HKCU";
223         hOtherKey = hRootKey == HKEY_CURRENT_USER ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
224         if (RegOpenKeyExW(hOtherKey, pszKeyPath, 0, regsam, &hSubKey) == ERROR_SUCCESS)
225         {
226             int cch;
227             RegCloseKey(hSubKey);
228             cch = _snwprintf(pszSuggestions, iSuggestionsLength, L"%s\\%s", rootstr, pszKeyPath);
229             if (cch <= 0 || cch > iSuggestionsLength)
230                 pszSuggestions[0] = UNICODE_NULL;
231         }
232     }
233 }
234 
235 
236 LRESULT CALLBACK AddressBarProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
237 {
238     WNDPROC oldwndproc;
239     static WCHAR s_szNode[256];
240     oldwndproc = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_USERDATA);
241 
242     switch (uMsg)
243     {
244     case WM_KEYUP:
245         if (wParam == VK_RETURN)
246         {
247             GetWindowTextW(hwnd, s_szNode, ARRAY_SIZE(s_szNode));
248             SelectNode(g_pChildWnd->hTreeWnd, s_szNode);
249         }
250         break;
251     default:
252         break;
253     }
254     return CallWindowProcW(oldwndproc, hwnd, uMsg, wParam, lParam);
255 }
256 
257 VOID
258 UpdateAddress(HTREEITEM hItem, HKEY hRootKey, LPCWSTR pszPath, BOOL bSelectNone)
259 {
260     LPCWSTR keyPath, rootName;
261     LPWSTR fullPath;
262     DWORD cbFullPath;
263 
264     /* Wipe the listview, the status bar and the address bar if the root key was selected */
265     if (TreeView_GetParent(g_pChildWnd->hTreeWnd, hItem) == NULL)
266     {
267         ListView_DeleteAllItems(g_pChildWnd->hListWnd);
268         SendMessageW(hStatusBar, SB_SETTEXTW, 0, (LPARAM)NULL);
269         SendMessageW(g_pChildWnd->hAddressBarWnd, WM_SETTEXT, 0, (LPARAM)NULL);
270         return;
271     }
272 
273     if (pszPath == NULL)
274         keyPath = GetItemPath(g_pChildWnd->hTreeWnd, hItem, &hRootKey);
275     else
276         keyPath = pszPath;
277 
278     if (keyPath)
279     {
280         RefreshListView(g_pChildWnd->hListWnd, hRootKey, keyPath, bSelectNone);
281         rootName = get_root_key_name(hRootKey);
282         cbFullPath = (wcslen(rootName) + 1 + wcslen(keyPath) + 1) * sizeof(WCHAR);
283         fullPath = malloc(cbFullPath);
284         if (fullPath)
285         {
286             /* set (correct) the address bar text */
287             if (keyPath[0] != UNICODE_NULL)
288                 StringCbPrintfW(fullPath, cbFullPath, L"%s%s%s", rootName,
289                                 ((keyPath[0] == L'\\') ? L"" : L"\\"), keyPath);
290             else
291                 StringCbCopyW(fullPath, cbFullPath, rootName);
292 
293             SendMessageW(hStatusBar, SB_SETTEXTW, 0, (LPARAM)fullPath);
294             SendMessageW(g_pChildWnd->hAddressBarWnd, WM_SETTEXT, 0, (LPARAM)fullPath);
295             free(fullPath);
296 
297             /* disable hive manipulation items temporarily (enable only if necessary) */
298             EnableMenuItem(hMenuFrame, ID_REGISTRY_LOADHIVE, MF_BYCOMMAND | MF_GRAYED);
299             EnableMenuItem(hMenuFrame, ID_REGISTRY_UNLOADHIVE, MF_BYCOMMAND | MF_GRAYED);
300             /* compare the strings to see if we should enable/disable the "Load Hive" menus accordingly */
301             if (_wcsicmp(rootName, L"HKEY_LOCAL_MACHINE") == 0 ||
302                 _wcsicmp(rootName, L"HKEY_USERS") == 0)
303             {
304                 /*
305                  * enable the unload menu item if at the root, otherwise
306                  * enable the load menu item if there is no slash in
307                  * keyPath (ie. immediate child selected)
308                  */
309                 if (keyPath[0] == UNICODE_NULL)
310                     EnableMenuItem(hMenuFrame, ID_REGISTRY_LOADHIVE, MF_BYCOMMAND | MF_ENABLED);
311                 else if (!wcschr(keyPath, L'\\'))
312                     EnableMenuItem(hMenuFrame, ID_REGISTRY_UNLOADHIVE, MF_BYCOMMAND | MF_ENABLED);
313             }
314         }
315     }
316 }
317 
318 /*******************************************************************************
319  *
320  *  FUNCTION: ChildWndProc(HWND, unsigned, WORD, LONG)
321  *
322  *  PURPOSE:  Processes messages for the child windows.
323  *
324  *  WM_COMMAND  - process the application menu
325  *  WM_DESTROY  - post a quit message and return
326  *
327  */
328 LRESULT CALLBACK ChildWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
329 {
330     BOOL Result;
331     RECT rc;
332 
333     switch (message)
334     {
335     case WM_CREATE:
336     {
337         WNDPROC oldproc;
338         HFONT hFont;
339         WCHAR buffer[MAX_PATH];
340         DWORD style;
341 
342         /* Load "My Computer" string */
343         LoadStringW(hInst, IDS_MY_COMPUTER, buffer, ARRAY_SIZE(buffer));
344 
345         g_pChildWnd = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(ChildWnd));
346         if (!g_pChildWnd) return 0;
347 
348         wcsncpy(g_pChildWnd->szPath, buffer, MAX_PATH);
349         g_pChildWnd->nSplitPos = 190;
350         g_pChildWnd->hWnd = hWnd;
351 
352         style = WS_CHILD | WS_VISIBLE | WS_TABSTOP;
353         g_pChildWnd->hAddressBarWnd = CreateWindowExW(WS_EX_CLIENTEDGE, L"Edit", NULL, style,
354                                                       CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
355                                                       hWnd, (HMENU)0, hInst, 0);
356 
357         style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_ICON | BS_CENTER |
358                 BS_VCENTER | BS_FLAT | BS_DEFPUSHBUTTON;
359         g_pChildWnd->hAddressBtnWnd = CreateWindowExW(0, L"Button", L"\x00BB", style,
360                                                       CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
361                                                       hWnd, (HMENU)0, hInst, 0);
362         g_pChildWnd->hArrowIcon = (HICON)LoadImageW(hInst, MAKEINTRESOURCEW(IDI_ARROW),
363                                                     IMAGE_ICON, 12, 12, 0);
364         SendMessageW(g_pChildWnd->hAddressBtnWnd, BM_SETIMAGE, IMAGE_ICON, (LPARAM)g_pChildWnd->hArrowIcon);
365 
366         GetClientRect(hWnd, &rc);
367         g_pChildWnd->hTreeWnd = CreateTreeView(hWnd, g_pChildWnd->szPath, (HMENU) TREE_WINDOW);
368         g_pChildWnd->hListWnd = CreateListView(hWnd, (HMENU) LIST_WINDOW, rc.right - g_pChildWnd->nSplitPos);
369         SetFocus(g_pChildWnd->hTreeWnd);
370 
371         /* set the address bar and button font */
372         if ((g_pChildWnd->hAddressBarWnd) && (g_pChildWnd->hAddressBtnWnd))
373         {
374             hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
375             SendMessageW(g_pChildWnd->hAddressBarWnd,
376                          WM_SETFONT,
377                          (WPARAM)hFont,
378                          0);
379             SendMessageW(g_pChildWnd->hAddressBtnWnd,
380                          WM_SETFONT,
381                          (WPARAM)hFont,
382                          0);
383         }
384         /* Subclass the AddressBar */
385         oldproc = (WNDPROC)GetWindowLongPtr(g_pChildWnd->hAddressBarWnd, GWLP_WNDPROC);
386         SetWindowLongPtr(g_pChildWnd->hAddressBarWnd, GWLP_USERDATA, (DWORD_PTR)oldproc);
387         SetWindowLongPtr(g_pChildWnd->hAddressBarWnd, GWLP_WNDPROC, (DWORD_PTR)AddressBarProc);
388         break;
389     }
390     case WM_COMMAND:
391         if(HIWORD(wParam) == BN_CLICKED)
392         {
393             PostMessageW(g_pChildWnd->hAddressBarWnd, WM_KEYUP, VK_RETURN, 0);
394         }
395         break; //goto def;
396     case WM_SETCURSOR:
397         if (LOWORD(lParam) == HTCLIENT)
398         {
399             POINT pt;
400             GetCursorPos(&pt);
401             ScreenToClient(hWnd, &pt);
402             if (pt.x>=g_pChildWnd->nSplitPos-SPLIT_WIDTH/2 && pt.x<g_pChildWnd->nSplitPos+SPLIT_WIDTH/2+1)
403             {
404                 SetCursor(LoadCursorW(0, IDC_SIZEWE));
405                 return TRUE;
406             }
407         }
408         goto def;
409 
410     case WM_DESTROY:
411         DestroyListView(g_pChildWnd->hListWnd);
412         DestroyTreeView(g_pChildWnd->hTreeWnd);
413         DestroyMainMenu();
414         DestroyIcon(g_pChildWnd->hArrowIcon);
415         HeapFree(GetProcessHeap(), 0, g_pChildWnd);
416         g_pChildWnd = NULL;
417         PostQuitMessage(0);
418         break;
419 
420     case WM_LBUTTONDOWN:
421     {
422         INT x = (SHORT)LOWORD(lParam);
423         if (x >= g_pChildWnd->nSplitPos - SPLIT_WIDTH / 2 &&
424             x <  g_pChildWnd->nSplitPos + SPLIT_WIDTH / 2 + 1)
425         {
426             x = ClampSplitBarX(hWnd, x);
427             draw_splitbar(hWnd, x);
428             last_split = x;
429             SetCapture(hWnd);
430         }
431         break;
432     }
433 
434     case WM_LBUTTONUP:
435     case WM_RBUTTONDOWN:
436         if (GetCapture() == hWnd)
437         {
438             INT x = (SHORT)LOWORD(lParam);
439             x = ClampSplitBarX(hWnd, x);
440             finish_splitbar(hWnd, x);
441         }
442         break;
443 
444     case WM_CAPTURECHANGED:
445         if (GetCapture() == hWnd && last_split >= 0)
446             draw_splitbar(hWnd, last_split);
447         break;
448 
449     case WM_KEYDOWN:
450         if (wParam == VK_ESCAPE)
451             if (GetCapture() == hWnd)
452             {
453                 RECT rt;
454                 draw_splitbar(hWnd, last_split);
455                 GetClientRect(hWnd, &rt);
456                 ResizeWnd(rt.right, rt.bottom);
457                 last_split = -1;
458                 ReleaseCapture();
459                 SetCursor(LoadCursorW(0, IDC_ARROW));
460             }
461         break;
462 
463     case WM_MOUSEMOVE:
464         if (GetCapture() == hWnd)
465         {
466             INT x = (SHORT)LOWORD(lParam);
467             x = ClampSplitBarX(hWnd, x);
468             if (last_split != x)
469             {
470                 draw_splitbar(hWnd, last_split);
471                 last_split = x;
472                 draw_splitbar(hWnd, last_split);
473             }
474         }
475         break;
476 
477     case WM_SETFOCUS:
478         if (g_pChildWnd != NULL)
479         {
480             SetFocus(g_pChildWnd->nFocusPanel? g_pChildWnd->hListWnd: g_pChildWnd->hTreeWnd);
481         }
482         break;
483 
484     case WM_NOTIFY:
485         if (g_pChildWnd == NULL) break;
486 
487         if (((LPNMHDR)lParam)->idFrom == TREE_WINDOW)
488         {
489             if (!TreeWndNotifyProc(g_pChildWnd->hListWnd, wParam, lParam, &Result))
490             {
491                 goto def;
492             }
493 
494             return Result;
495         }
496         else
497         {
498             if (((LPNMHDR)lParam)->idFrom == LIST_WINDOW)
499             {
500                 if (!ListWndNotifyProc(g_pChildWnd->hListWnd, wParam, lParam, &Result))
501                 {
502                     goto def;
503                 }
504 
505                 return Result;
506             }
507             else
508             {
509                 goto def;
510             }
511         }
512         break;
513 
514     case WM_CONTEXTMENU:
515     {
516         POINT pt;
517         if((HWND)wParam == g_pChildWnd->hListWnd)
518         {
519             int i, cnt;
520             BOOL IsDefault;
521             pt.x = (short) LOWORD(lParam);
522             pt.y = (short) HIWORD(lParam);
523             cnt = ListView_GetSelectedCount(g_pChildWnd->hListWnd);
524             i = ListView_GetNextItem(g_pChildWnd->hListWnd, -1, LVNI_FOCUSED | LVNI_SELECTED);
525             if (pt.x == -1 && pt.y == -1)
526             {
527                 RECT rc;
528                 if (i != -1)
529                 {
530                     rc.left = LVIR_BOUNDS;
531                     SendMessageW(g_pChildWnd->hListWnd, LVM_GETITEMRECT, i, (LPARAM) &rc);
532                     pt.x = rc.left + 8;
533                     pt.y = rc.top + 8;
534                 }
535                 else
536                     pt.x = pt.y = 0;
537                 ClientToScreen(g_pChildWnd->hListWnd, &pt);
538             }
539             if(i == -1)
540             {
541                 TrackPopupMenu(GetSubMenu(hPopupMenus, PM_NEW), TPM_RIGHTBUTTON, pt.x, pt.y, 0, hFrameWnd, NULL);
542             }
543             else
544             {
545                 HMENU mnu = GetSubMenu(hPopupMenus, PM_MODIFYVALUE);
546                 SetMenuDefaultItem(mnu, ID_EDIT_MODIFY, MF_BYCOMMAND);
547                 IsDefault = IsDefaultValue(g_pChildWnd->hListWnd, i);
548                 if(cnt == 1)
549                     EnableMenuItem(mnu, ID_EDIT_RENAME, MF_BYCOMMAND | (IsDefault ? MF_DISABLED | MF_GRAYED : MF_ENABLED));
550                 else
551                     EnableMenuItem(mnu, ID_EDIT_RENAME, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
552                 EnableMenuItem(mnu, ID_EDIT_MODIFY, MF_BYCOMMAND | (cnt == 1 ? MF_ENABLED : MF_DISABLED | MF_GRAYED));
553                 EnableMenuItem(mnu, ID_EDIT_MODIFY_BIN, MF_BYCOMMAND | (cnt == 1 ? MF_ENABLED : MF_DISABLED | MF_GRAYED));
554 
555                 TrackPopupMenu(mnu, TPM_RIGHTBUTTON, pt.x, pt.y, 0, hFrameWnd, NULL);
556             }
557         }
558         else if ((HWND)wParam == g_pChildWnd->hTreeWnd)
559         {
560             TVHITTESTINFO hti;
561             HMENU hContextMenu;
562             TVITEMW item;
563             MENUITEMINFOW mii;
564             WCHAR resource[256];
565             WCHAR buffer[256];
566             LPWSTR s;
567             LPCWSTR keyPath;
568             HKEY hRootKey;
569             int iLastPos;
570             WORD wID;
571             BOOL isRoot;
572 
573             pt.x = (short) LOWORD(lParam);
574             pt.y = (short) HIWORD(lParam);
575 
576             if (pt.x == -1 && pt.y == -1)
577             {
578                 RECT rc;
579                 hti.hItem = TreeView_GetSelection(g_pChildWnd->hTreeWnd);
580                 if (hti.hItem != NULL)
581                 {
582                     TreeView_GetItemRect(g_pChildWnd->hTreeWnd, hti.hItem, &rc, TRUE);
583                     pt.x = rc.left + 8;
584                     pt.y = rc.top + 8;
585                     ClientToScreen(g_pChildWnd->hTreeWnd, &pt);
586                     hti.flags = TVHT_ONITEM;
587                 }
588                 else
589                     hti.flags = 0;
590             }
591             else
592             {
593                 hti.pt.x = pt.x;
594                 hti.pt.y = pt.y;
595                 ScreenToClient(g_pChildWnd->hTreeWnd, &hti.pt);
596                 TreeView_HitTest(g_pChildWnd->hTreeWnd, &hti);
597             }
598 
599             if (hti.flags & TVHT_ONITEM)
600             {
601                 TreeView_SelectItem(g_pChildWnd->hTreeWnd, hti.hItem);
602 
603                 isRoot = (TreeView_GetParent(g_pChildWnd->hTreeWnd, hti.hItem) == NULL);
604                 hContextMenu = GetSubMenu(hPopupMenus, isRoot ?  PM_ROOTITEM : PM_TREECONTEXT);
605 
606                 memset(&item, 0, sizeof(item));
607                 item.mask = TVIF_STATE | TVIF_CHILDREN;
608                 item.hItem = hti.hItem;
609                 TreeView_GetItem(g_pChildWnd->hTreeWnd, &item);
610 
611                 /* Set the Expand/Collapse menu item appropriately */
612                 LoadStringW(hInst, (item.state & TVIS_EXPANDED) ? IDS_COLLAPSE : IDS_EXPAND, buffer, ARRAY_SIZE(buffer));
613                 memset(&mii, 0, sizeof(mii));
614                 mii.cbSize = sizeof(mii);
615                 mii.fMask = MIIM_STRING | MIIM_STATE | MIIM_ID;
616                 mii.fState = (item.cChildren > 0) ? MFS_DEFAULT : MFS_GRAYED;
617                 mii.wID = (item.state & TVIS_EXPANDED) ? ID_TREE_COLLAPSEBRANCH : ID_TREE_EXPANDBRANCH;
618                 mii.dwTypeData = (LPWSTR) buffer;
619                 SetMenuItemInfo(hContextMenu, 0, TRUE, &mii);
620 
621                 if (isRoot == FALSE)
622                 {
623                     /* Remove any existing suggestions */
624                     memset(&mii, 0, sizeof(mii));
625                     mii.cbSize = sizeof(mii);
626                     mii.fMask = MIIM_ID;
627                     GetMenuItemInfo(hContextMenu, GetMenuItemCount(hContextMenu) - 1, TRUE, &mii);
628                     if ((mii.wID >= ID_TREE_SUGGESTION_MIN) && (mii.wID <= ID_TREE_SUGGESTION_MAX))
629                     {
630                         do
631                         {
632                             iLastPos = GetMenuItemCount(hContextMenu) - 1;
633                             GetMenuItemInfo(hContextMenu, iLastPos, TRUE, &mii);
634                             RemoveMenu(hContextMenu, iLastPos, MF_BYPOSITION);
635                         }
636                         while((mii.wID >= ID_TREE_SUGGESTION_MIN) && (mii.wID <= ID_TREE_SUGGESTION_MAX));
637                     }
638 
639                     /* Come up with suggestions */
640                     keyPath = GetItemPath(g_pChildWnd->hTreeWnd, NULL, &hRootKey);
641                     SuggestKeys(hRootKey, keyPath, Suggestions, ARRAY_SIZE(Suggestions));
642                     if (Suggestions[0])
643                     {
644                         AppendMenu(hContextMenu, MF_SEPARATOR, 0, NULL);
645 
646                         LoadStringW(hInst, IDS_GOTO_SUGGESTED_KEY, resource, ARRAY_SIZE(resource));
647 
648                         s = Suggestions;
649                         wID = ID_TREE_SUGGESTION_MIN;
650                         while(*s && (wID <= ID_TREE_SUGGESTION_MAX))
651                         {
652                             WCHAR *path = s, buf[MAX_PATH];
653                             if (hRootKey == HKEY_CURRENT_USER || hRootKey == HKEY_LOCAL_MACHINE)
654                             {
655                                 // Windows 10 only displays the root name
656                                 LPCWSTR next = PathFindNextComponentW(s);
657                                 if (next > s)
658                                     lstrcpynW(path = buf, s, min(next - s, _countof(buf)));
659                             }
660                             _snwprintf(buffer, ARRAY_SIZE(buffer), resource, path);
661 
662                             memset(&mii, 0, sizeof(mii));
663                             mii.cbSize = sizeof(mii);
664                             mii.fMask = MIIM_STRING | MIIM_ID;
665                             mii.wID = wID++;
666                             mii.dwTypeData = buffer;
667                             InsertMenuItem(hContextMenu, GetMenuItemCount(hContextMenu), TRUE, &mii);
668 
669                             s += wcslen(s) + 1;
670                         }
671                     }
672                 }
673                 TrackPopupMenu(hContextMenu, TPM_RIGHTBUTTON, pt.x, pt.y, 0, hFrameWnd, NULL);
674             }
675         }
676         break;
677     }
678 
679     case WM_SIZE:
680         if (wParam != SIZE_MINIMIZED && g_pChildWnd != NULL)
681         {
682             ResizeWnd(LOWORD(lParam), HIWORD(lParam));
683         }
684         break;
685 
686     default:
687 def:
688         return DefWindowProcW(hWnd, message, wParam, lParam);
689     }
690     return 0;
691 }
692