user32/tests: Fix some input test failures.
[wine] / programs / regedit / treeview.c
1 /*
2  * Regedit treeview
3  *
4  * Copyright (C) 2002 Robert Dickenson <robd@reactos.org>
5  * Copyright (C) 2008 Alexander N. Sørnes <alex@thehandofagony.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 St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 #define WIN32_LEAN_AND_MEAN     /* Exclude rarely-used stuff from Windows headers */
23
24 #define NONAMELESSUNION
25 #define NONAMELESSSTRUCT
26 #include <windows.h>
27 #include <commctrl.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <wine/debug.h>
31 #include <shlwapi.h>
32
33 #include "main.h"
34 #include "regproc.h"
35
36 WINE_DEFAULT_DEBUG_CHANNEL(regedit);
37
38 /* Global variables and constants  */
39 /* Image_Open, Image_Closed, and Image_Root - integer variables for indexes of the images.  */
40 /* CX_BITMAP and CY_BITMAP - width and height of an icon.  */
41 /* NUM_BITMAPS - number of bitmaps to add to the image list.  */
42 int Image_Open;
43 int Image_Closed;
44 int Image_Root;
45
46 #define CX_ICON    16
47 #define CY_ICON    16
48 #define NUM_ICONS    3
49
50 static BOOL UpdateExpandingTree(HWND hwndTV, HTREEITEM hItem, int state);
51
52 static BOOL get_item_path(HWND hwndTV, HTREEITEM hItem, HKEY* phKey, LPWSTR* pKeyPath, int* pPathLen, int* pMaxChars)
53 {
54     TVITEMW item;
55     int maxChars, chars;
56     LPWSTR newStr;
57
58     item.mask = TVIF_PARAM;
59     item.hItem = hItem;
60     if (!TreeView_GetItem(hwndTV, &item)) return FALSE;
61
62     if (item.lParam) {
63     /* found root key with valid key value */
64     *phKey = (HKEY)item.lParam;
65     return TRUE;
66     }
67
68     if(!get_item_path(hwndTV, TreeView_GetParent(hwndTV, hItem), phKey, pKeyPath, pPathLen, pMaxChars)) return FALSE;
69     if (*pPathLen) {
70         (*pKeyPath)[*pPathLen] = '\\';
71         ++(*pPathLen);
72     }
73
74     do {
75         item.mask = TVIF_TEXT;
76         item.hItem = hItem;
77         item.pszText = *pKeyPath + *pPathLen;
78         item.cchTextMax = maxChars = *pMaxChars - *pPathLen;
79         if (!TreeView_GetItemW(hwndTV, &item)) return FALSE;
80         chars = lstrlenW(item.pszText);
81     if (chars < maxChars - 1) {
82             *pPathLen += chars;
83             break;
84     }
85     newStr = HeapReAlloc(GetProcessHeap(), 0, *pKeyPath, *pMaxChars * 2);
86     if (!newStr) return FALSE;
87     *pKeyPath = newStr;
88     *pMaxChars *= 2;
89     } while(TRUE);
90
91     return TRUE;
92 }
93
94 LPWSTR GetItemPath(HWND hwndTV, HTREEITEM hItem, HKEY* phRootKey)
95 {
96     int pathLen = 0, maxLen;
97     WCHAR *pathBuffer;
98
99     pathBuffer = HeapAlloc(GetProcessHeap(), 0, 1024*sizeof(WCHAR));
100     if (!pathBuffer) return NULL;
101     *pathBuffer = 0;
102     maxLen = HeapSize(GetProcessHeap(), 0, pathBuffer);
103     if (maxLen == (SIZE_T) - 1) return NULL;
104     maxLen = maxLen / sizeof(WCHAR);
105     if (!hItem) hItem = TreeView_GetSelection(hwndTV);
106     if (!hItem) return NULL;
107     if (!get_item_path(hwndTV, hItem, phRootKey, &pathBuffer, &pathLen, &maxLen)) return NULL;
108     return pathBuffer;
109 }
110
111 static LPWSTR get_path_component(LPCWSTR *lplpKeyName) {
112      LPCWSTR lpPos = *lplpKeyName;
113      LPWSTR lpResult = NULL;
114      int len;
115      if (!lpPos)
116          return NULL;
117      while(*lpPos && *lpPos != '\\')
118          lpPos++;
119      if (*lpPos && lpPos == *lplpKeyName)
120          return NULL;
121      len = lpPos+1-(*lplpKeyName);
122      lpResult = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
123      if (!lpResult) /* that would be very odd */
124          return NULL;
125      lstrcpynW(lpResult, *lplpKeyName, len);
126      *lplpKeyName = *lpPos ? lpPos+1 : NULL;
127      return lpResult;
128 }
129
130 HTREEITEM FindPathInTree(HWND hwndTV, LPCWSTR lpKeyName) {
131     TVITEMEXW tvi;
132     WCHAR buf[261]; /* tree view has 260 character limitation on item name */
133     HTREEITEM hItem, hOldItem;
134
135     buf[260] = '\0';
136     hItem = TreeView_GetRoot(hwndTV);
137     SendMessageW(hwndTV, TVM_EXPAND, TVE_EXPAND, (LPARAM)hItem );
138     hItem = TreeView_GetChild(hwndTV, hItem);
139     hOldItem = hItem;
140     while(1) {
141         LPWSTR lpItemName = get_path_component(&lpKeyName);
142
143         if (lpItemName) {
144             while(hItem) {
145                 tvi.mask = TVIF_TEXT | TVIF_HANDLE;
146                 tvi.hItem = hItem;
147                 tvi.pszText = buf;
148                 tvi.cchTextMax = 260;
149                 SendMessageW(hwndTV, TVM_GETITEMW, 0, (LPARAM) &tvi);
150                 if (!lstrcmpiW(tvi.pszText, lpItemName)) {
151                      SendMessageW(hwndTV, TVM_EXPAND, TVE_EXPAND, (LPARAM)hItem );
152                      if (!lpKeyName)
153                      {
154                          HeapFree(GetProcessHeap(), 0, lpItemName);
155                          return hItem;
156                      }
157                      hOldItem = hItem;
158                      hItem = TreeView_GetChild(hwndTV, hItem);
159                      break;
160                 }
161                 hItem = TreeView_GetNextSibling(hwndTV, hItem);
162             }
163             HeapFree(GetProcessHeap(), 0, lpItemName);
164             if (!hItem)
165                 return hOldItem;
166         }
167         else
168             return hItem;
169     }
170 }
171
172 BOOL DeleteNode(HWND hwndTV, HTREEITEM hItem)
173 {
174     if (!hItem) hItem = TreeView_GetSelection(hwndTV);
175     if (!hItem) return FALSE;
176     return TreeView_DeleteItem(hwndTV, hItem);
177 }
178
179 /* Add an entry to the tree. Only give hKey for root nodes (HKEY_ constants) */
180 static HTREEITEM AddEntryToTree(HWND hwndTV, HTREEITEM hParent, LPWSTR label, HKEY hKey, DWORD dwChildren)
181 {
182     TVINSERTSTRUCTW tvins;
183
184     if (hKey) {
185         if (RegQueryInfoKeyW(hKey, 0, 0, 0, &dwChildren, 0, 0, 0, 0, 0, 0, 0) != ERROR_SUCCESS) {
186             dwChildren = 0;
187         }
188     }
189
190     tvins.u.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN | TVIF_PARAM;
191     tvins.u.item.pszText = label;
192     tvins.u.item.cchTextMax = lstrlenW(label);
193     tvins.u.item.iImage = Image_Closed;
194     tvins.u.item.iSelectedImage = Image_Open;
195     tvins.u.item.cChildren = dwChildren;
196     tvins.u.item.lParam = (LPARAM)hKey;
197     tvins.hInsertAfter = (HTREEITEM)(hKey ? TVI_LAST : TVI_SORT);
198     tvins.hParent = hParent;
199
200     return TreeView_InsertItemW(hwndTV, &tvins);
201 }
202
203 static BOOL match_string(LPCWSTR sstring1, LPCWSTR sstring2, int mode)
204 {
205     if (mode & SEARCH_WHOLE)
206         return !lstrcmpiW(sstring1, sstring2);
207     else
208         return NULL != StrStrIW(sstring1, sstring2);
209 }
210
211 static BOOL match_item(HWND hwndTV, HTREEITEM hItem, LPCWSTR sstring, int mode, int *row)
212 {
213     TVITEMW item;
214     WCHAR keyname[KEY_MAX_LEN];
215     item.mask = TVIF_TEXT;
216     item.hItem = hItem;
217     item.pszText = keyname;
218     item.cchTextMax = KEY_MAX_LEN;
219     if (!TreeView_GetItem(hwndTV, &item)) return FALSE;
220     if ((mode & SEARCH_KEYS) && match_string(keyname, sstring, mode)) {
221         *row = -1;
222         return TRUE;
223     }
224
225     if (mode & (SEARCH_VALUES | SEARCH_CONTENT)) {
226         int i, adjust;
227         WCHAR valName[KEY_MAX_LEN], *KeyPath;
228         HKEY hKey, hRoot;
229         DWORD lenName;
230         
231         KeyPath = GetItemPath(hwndTV, hItem, &hRoot);
232
233         if (!KeyPath || !hRoot)
234              return FALSE;
235
236         if (RegOpenKeyExW(hRoot, KeyPath, 0, KEY_READ, &hKey) != ERROR_SUCCESS) {
237             HeapFree(GetProcessHeap(), 0, KeyPath);
238             return FALSE;
239         }
240
241         HeapFree(GetProcessHeap(), 0, KeyPath);
242         lenName = KEY_MAX_LEN;
243         adjust = 0;
244         /* RegEnumValue won't return empty default value, so fake it when dealing with *row,
245            which corresponds to list view rows, not value ids */
246         if (ERROR_SUCCESS == RegEnumValueW(hKey, 0, valName, &lenName, NULL, NULL, NULL, NULL) && *valName)
247             adjust = 1;
248         
249         i = (*row)-adjust;
250         if (i < 0) i = 0;
251         while(1) {
252             DWORD lenValue = 0, type = 0;
253             lenName = KEY_MAX_LEN;
254             
255             if (ERROR_SUCCESS != RegEnumValueW(hKey,
256                 i, valName, &lenName, NULL, &type, NULL, &lenValue))
257                 break;
258             
259             if (mode & SEARCH_VALUES) {
260                 if (match_string(valName, sstring, mode)) {
261                     RegCloseKey(hKey);
262                     *row = i+adjust;
263                     return TRUE;
264                 }
265             }
266             
267             if ((mode & SEARCH_CONTENT) && (type == REG_EXPAND_SZ || type == REG_SZ)) {
268                 LPWSTR buffer;
269                 buffer = HeapAlloc(GetProcessHeap(), 0, lenValue);
270                 RegEnumValueW(hKey, i, valName, &lenName, NULL, &type, (LPBYTE)buffer, &lenValue);
271                 if (match_string(buffer, sstring, mode)) {
272                     HeapFree(GetProcessHeap(), 0, buffer);
273                     RegCloseKey(hKey);
274                     *row = i+adjust;
275                     return TRUE;
276                 }
277                 HeapFree(GetProcessHeap(), 0, buffer);
278             }
279                             
280             i++;
281         }
282         RegCloseKey(hKey);
283     }        
284     return FALSE;
285 }
286
287 HTREEITEM FindNext(HWND hwndTV, HTREEITEM hItem, LPCWSTR sstring, int mode, int *row)
288 {
289     HTREEITEM hTry, hLast;
290     
291     hLast = hItem;
292     (*row)++;
293     if (match_item(hwndTV, hLast, sstring, mode & ~SEARCH_KEYS, row)) {
294         return hLast;
295     }
296     *row = 0;
297     
298     while(hLast) {
299         /* first look in subtree */
300         /* no children? maybe we haven't loaded them yet? */
301         if (!TreeView_GetChild(hwndTV, hLast)) {
302             UpdateExpandingTree(hwndTV, hLast, TreeView_GetItemState(hwndTV, hLast, -1));
303         }
304         hTry = TreeView_GetChild(hwndTV, hLast);
305         if (hTry) {
306             if (match_item(hwndTV, hTry, sstring, mode, row))
307                 return hTry;
308             hLast = hTry;
309             continue;
310         }
311         /* no more children, maybe there are any siblings? */
312         hTry = TreeView_GetNextSibling(hwndTV, hLast);
313         if (hTry) {
314             if (match_item(hwndTV, hTry, sstring, mode, row))
315                 return hTry;
316             hLast = hTry;
317             continue;
318         }
319         /* no more siblings, look at the next siblings in parent(s) */
320         hLast = TreeView_GetParent(hwndTV, hLast);
321         if (!hLast)
322             return NULL;
323         while (hLast && (hTry = TreeView_GetNextSibling(hwndTV, hLast)) == NULL) {
324             hLast = TreeView_GetParent(hwndTV, hLast);
325         }
326         if (match_item(hwndTV, hTry, sstring, mode, row))
327             return hTry;
328         hLast = hTry;
329     }
330     return NULL;
331 }
332
333 static BOOL RefreshTreeItem(HWND hwndTV, HTREEITEM hItem)
334 {
335     HKEY hRoot, hKey, hSubKey;
336     HTREEITEM childItem;
337     LPWSTR KeyPath;
338     DWORD dwCount, dwIndex, dwMaxSubKeyLen;
339     LPWSTR Name;
340     TVITEMW tvItem;
341     
342     hRoot = NULL;
343     KeyPath = GetItemPath(hwndTV, hItem, &hRoot);
344
345     if (!KeyPath || !hRoot)
346         return FALSE;
347
348     if (*KeyPath) {
349         if (RegOpenKeyExW(hRoot, KeyPath, 0, KEY_READ, &hKey) != ERROR_SUCCESS) {
350             WINE_TRACE("RegOpenKeyEx failed, %s was probably removed.\n", wine_dbgstr_w(KeyPath));
351             return FALSE;
352         }
353     } else {
354         hKey = hRoot;
355     }
356     HeapFree(GetProcessHeap(), 0, KeyPath);
357
358     if (RegQueryInfoKeyW(hKey, 0, 0, 0, &dwCount, &dwMaxSubKeyLen, 0, 0, 0, 0, 0, 0) != ERROR_SUCCESS) {
359         return FALSE;
360     }
361
362     /* Set the number of children again */
363     tvItem.mask = TVIF_CHILDREN;
364     tvItem.hItem = hItem;
365     tvItem.cChildren = dwCount;
366     if (!TreeView_SetItemW(hwndTV, &tvItem)) {
367         return FALSE;
368     }
369
370     /* We don't have to bother with the rest if it's not expanded. */
371     if (TreeView_GetItemState(hwndTV, hItem, TVIS_EXPANDED) == 0) {
372         RegCloseKey(hKey);
373         return TRUE;
374     }
375
376     dwMaxSubKeyLen++; /* account for the \0 terminator */
377     if (!(Name = HeapAlloc(GetProcessHeap(), 0, dwMaxSubKeyLen * sizeof(WCHAR)))) {
378         return FALSE;
379     }
380     tvItem.cchTextMax = dwMaxSubKeyLen;
381     if (!(tvItem.pszText = HeapAlloc(GetProcessHeap(), 0, dwMaxSubKeyLen * sizeof(WCHAR)))) {
382         HeapFree(GetProcessHeap(), 0, Name);
383         return FALSE;
384     }
385
386     /* Now go through all the children in the registry, and check if any have to be added. */
387     for (dwIndex = 0; dwIndex < dwCount; dwIndex++) {
388         DWORD cName = dwMaxSubKeyLen, dwSubCount;
389         BOOL found;
390
391         found = FALSE;
392         if (RegEnumKeyExW(hKey, dwIndex, Name, &cName, 0, 0, 0, NULL) != ERROR_SUCCESS) {
393             continue;
394         }
395
396         /* Find the number of children of the node. */
397         dwSubCount = 0;
398         if (RegOpenKeyExW(hKey, Name, 0, KEY_QUERY_VALUE, &hSubKey) == ERROR_SUCCESS) {
399             if (RegQueryInfoKey(hSubKey, 0, 0, 0, &dwSubCount, 0, 0, 0, 0, 0, 0, 0) != ERROR_SUCCESS) {
400                 dwSubCount = 0;
401             }
402             RegCloseKey(hSubKey);
403         }
404
405         /* Check if the node is already in there. */
406         for (childItem = TreeView_GetChild(hwndTV, hItem); childItem;
407                 childItem = TreeView_GetNextSibling(hwndTV, childItem)) {
408             tvItem.mask = TVIF_TEXT;
409             tvItem.hItem = childItem;
410             if (!TreeView_GetItemW(hwndTV, &tvItem)) {
411                 HeapFree(GetProcessHeap(), 0, Name);
412                 HeapFree(GetProcessHeap(), 0, tvItem.pszText);
413                 return FALSE;
414             }
415
416             if (!lstrcmpiW(tvItem.pszText, Name)) {
417                 found = TRUE;
418                 break;
419             }
420         }
421
422         if (found == FALSE) {
423             WINE_TRACE("New subkey %s\n", wine_dbgstr_w(Name));
424             AddEntryToTree(hwndTV, hItem, Name, NULL, dwSubCount);
425         }
426     }
427     HeapFree(GetProcessHeap(), 0, Name);
428     HeapFree(GetProcessHeap(), 0, tvItem.pszText);
429     RegCloseKey(hKey);
430
431     /* Now go through all the children in the tree, and check if any have to be removed. */
432     childItem = TreeView_GetChild(hwndTV, hItem);
433     while (childItem) {
434         HTREEITEM nextItem = TreeView_GetNextSibling(hwndTV, childItem);
435         if (RefreshTreeItem(hwndTV, childItem) == FALSE) {
436             SendMessageW(hwndTV, TVM_DELETEITEM, 0, (LPARAM)childItem);
437         }
438         childItem = nextItem;
439     }
440
441     return TRUE;
442 }
443
444 BOOL RefreshTreeView(HWND hwndTV)
445 {
446     HTREEITEM hItem;
447     HTREEITEM hSelectedItem;
448     HCURSOR hcursorOld;
449
450     WINE_TRACE("\n");
451     hSelectedItem = TreeView_GetSelection(hwndTV);
452     hcursorOld = SetCursor(LoadCursor(NULL, IDC_WAIT));
453     SendMessageW(hwndTV, WM_SETREDRAW, FALSE, 0);
454
455     hItem = TreeView_GetChild(hwndTV, TreeView_GetRoot(hwndTV));
456     while (hItem) {
457         RefreshTreeItem(hwndTV, hItem);
458         hItem = TreeView_GetNextSibling(hwndTV, hItem);
459     }
460
461     SendMessageW(hwndTV, WM_SETREDRAW, TRUE, 0);
462     InvalidateRect(hwndTV, NULL, FALSE);
463     SetCursor(hcursorOld);
464     
465     /* We reselect the currently selected node, this will prompt a refresh of the listview. */
466     SendMessageW(hwndTV, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hSelectedItem);
467     return TRUE;
468 }
469
470 HTREEITEM InsertNode(HWND hwndTV, HTREEITEM hItem, LPWSTR name)
471 {
472     WCHAR buf[MAX_NEW_KEY_LEN];
473     HTREEITEM hNewItem = 0;
474     TVITEMEXW item;
475
476     if (!hItem) hItem = TreeView_GetSelection(hwndTV);
477     if (!hItem) return FALSE;
478     if (TreeView_GetItemState(hwndTV, hItem, TVIS_EXPANDEDONCE)) {
479         hNewItem = AddEntryToTree(hwndTV, hItem, name, 0, 0);
480     } else {
481         item.mask = TVIF_CHILDREN | TVIF_HANDLE;
482         item.hItem = hItem;
483         if (!TreeView_GetItemW(hwndTV, &item)) return FALSE;
484         item.cChildren = 1;
485         if (!TreeView_SetItemW(hwndTV, &item)) return FALSE;
486     }
487     SendMessageW(hwndTV, TVM_EXPAND, TVE_EXPAND, (LPARAM)hItem );
488     if (!hNewItem) {
489         for(hNewItem = TreeView_GetChild(hwndTV, hItem); hNewItem; hNewItem = TreeView_GetNextSibling(hwndTV, hNewItem)) {
490             item.mask = TVIF_HANDLE | TVIF_TEXT;
491             item.hItem = hNewItem;
492             item.pszText = buf;
493             item.cchTextMax = COUNT_OF(buf);
494             if (!TreeView_GetItemW(hwndTV, &item)) continue;
495             if (lstrcmpW(name, item.pszText) == 0) break;
496         }       
497     }
498     if (hNewItem)
499         SendMessageW(hwndTV, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hNewItem);
500
501     return hNewItem;
502 }
503
504 HWND StartKeyRename(HWND hwndTV)
505 {
506     HTREEITEM hItem;
507
508     if(!(hItem = TreeView_GetSelection(hwndTV))) return 0;
509     return TreeView_EditLabel(hwndTV, hItem);
510 }
511
512 static BOOL InitTreeViewItems(HWND hwndTV, LPWSTR pHostName)
513 {
514     TVINSERTSTRUCTW tvins;
515     HTREEITEM hRoot;
516     static WCHAR hkcr[] = {'H','K','E','Y','_','C','L','A','S','S','E','S','_','R','O','O','T',0},
517                  hkcu[] = {'H','K','E','Y','_','C','U','R','R','E','N','T','_','U','S','E','R',0},
518                  hklm[] = {'H','K','E','Y','_','L','O','C','A','L','_','M','A','C','H','I','N','E',0},
519                  hku[]  = {'H','K','E','Y','_','U','S','E','R','S',0},
520                  hkcc[] = {'H','K','E','Y','_','C','U','R','R','E','N','T','_','C','O','N','F','I','G',0},
521                  hkdd[] = {'H','K','E','Y','_','D','Y','N','_','D','A','T','A',0};
522
523     tvins.u.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN | TVIF_PARAM;
524     /* Set the text of the item.  */
525     tvins.u.item.pszText = pHostName;
526     tvins.u.item.cchTextMax = lstrlenW(pHostName);
527     /* Assume the item is not a parent item, so give it an image.  */
528     tvins.u.item.iImage = Image_Root;
529     tvins.u.item.iSelectedImage = Image_Root;
530     tvins.u.item.cChildren = 5;
531     /* Save the heading level in the item's application-defined data area.  */
532     tvins.u.item.lParam = (LPARAM)NULL;
533     tvins.hInsertAfter = (HTREEITEM)TVI_FIRST;
534     tvins.hParent = TVI_ROOT;
535     /* Add the item to the tree view control.  */
536     if (!(hRoot = TreeView_InsertItemW(hwndTV, &tvins))) return FALSE;
537
538     if (!AddEntryToTree(hwndTV, hRoot, hkcr, HKEY_CLASSES_ROOT, 1)) return FALSE;
539     if (!AddEntryToTree(hwndTV, hRoot, hkcu, HKEY_CURRENT_USER, 1)) return FALSE;
540     if (!AddEntryToTree(hwndTV, hRoot, hklm, HKEY_LOCAL_MACHINE, 1)) return FALSE;
541     if (!AddEntryToTree(hwndTV, hRoot, hku, HKEY_USERS, 1)) return FALSE;
542     if (!AddEntryToTree(hwndTV, hRoot, hkcc, HKEY_CURRENT_CONFIG, 1)) return FALSE;
543     if (!AddEntryToTree(hwndTV, hRoot, hkdd, HKEY_DYN_DATA, 1)) return FALSE;
544
545     /* expand and select host name */
546     SendMessageW(hwndTV, TVM_EXPAND, TVE_EXPAND, (LPARAM)hRoot );
547     SendMessageW(hwndTV, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hRoot);
548     return TRUE;
549 }
550
551
552 /*
553  * InitTreeViewImageLists - creates an image list, adds three bitmaps
554  * to it, and associates the image list with a tree view control.
555  * Returns TRUE if successful, or FALSE otherwise.
556  * hwndTV - handle to the tree view control.
557  */
558 static BOOL InitTreeViewImageLists(HWND hwndTV)
559 {
560     HIMAGELIST himl;  /* handle to image list  */
561     HICON hico;       /* handle to icon  */
562
563     /* Create the image list.  */
564     if ((himl = ImageList_Create(CX_ICON, CY_ICON,
565                                  ILC_MASK, 0, NUM_ICONS)) == NULL)
566         return FALSE;
567
568     /* Add the open file, closed file, and document bitmaps.  */
569     hico = LoadIconW(hInst, MAKEINTRESOURCEW(IDI_OPEN_FILE));
570     Image_Open = ImageList_AddIcon(himl, hico);
571
572     hico = LoadIconW(hInst, MAKEINTRESOURCEW(IDI_CLOSED_FILE));
573     Image_Closed = ImageList_AddIcon(himl, hico);
574
575     hico = LoadIconW(hInst, MAKEINTRESOURCEW(IDI_ROOT));
576     Image_Root = ImageList_AddIcon(himl, hico);
577
578     /* Fail if not all of the images were added.  */
579     if (ImageList_GetImageCount(himl) < NUM_ICONS)
580     {
581       return FALSE;
582     }
583
584     /* Associate the image list with the tree view control.  */
585     SendMessageW(hwndTV, TVM_SETIMAGELIST, TVSIL_NORMAL, (LPARAM)himl);
586
587     return TRUE;
588 }
589
590 BOOL UpdateExpandingTree(HWND hwndTV, HTREEITEM hItem, int state)
591 {
592     DWORD dwCount, dwIndex, dwMaxSubKeyLen;
593     HKEY hRoot, hNewKey, hKey;
594     LPWSTR keyPath;
595     LPWSTR Name;
596     LONG errCode;
597     HCURSOR hcursorOld;
598
599     static int expanding;
600     if (expanding) return FALSE;
601     if (state & TVIS_EXPANDEDONCE ) {
602         return TRUE;
603     }
604     expanding = TRUE;
605     hcursorOld = SetCursor(LoadCursor(NULL, IDC_WAIT));
606     SendMessageW(hwndTV, WM_SETREDRAW, FALSE, 0);
607
608     keyPath = GetItemPath(hwndTV, hItem, &hRoot);
609     if (!keyPath) goto done;
610
611     if (*keyPath) {
612         errCode = RegOpenKeyExW(hRoot, keyPath, 0, KEY_READ, &hNewKey);
613         if (errCode != ERROR_SUCCESS) goto done;
614     } else {
615         hNewKey = hRoot;
616     }
617
618     errCode = RegQueryInfoKeyW(hNewKey, 0, 0, 0, &dwCount, &dwMaxSubKeyLen, 0, 0, 0, 0, 0, 0);
619     if (errCode != ERROR_SUCCESS) goto done;
620     dwMaxSubKeyLen++; /* account for the \0 terminator */
621     Name = HeapAlloc(GetProcessHeap(), 0, dwMaxSubKeyLen * sizeof(WCHAR));
622     if (!Name) goto done;
623
624     for (dwIndex = 0; dwIndex < dwCount; dwIndex++) {
625         DWORD cName = dwMaxSubKeyLen, dwSubCount;
626
627         errCode = RegEnumKeyExW(hNewKey, dwIndex, Name, &cName, 0, 0, 0, 0);
628         if (errCode != ERROR_SUCCESS) continue;
629         errCode = RegOpenKeyExW(hNewKey, Name, 0, KEY_QUERY_VALUE, &hKey);
630         if (errCode == ERROR_SUCCESS) {
631             errCode = RegQueryInfoKeyW(hKey, 0, 0, 0, &dwSubCount, 0, 0, 0, 0, 0, 0, 0);
632             RegCloseKey(hKey);
633         }
634         if (errCode != ERROR_SUCCESS) dwSubCount = 0;
635         AddEntryToTree(hwndTV, hItem, Name, NULL, dwSubCount);
636     }
637     RegCloseKey(hNewKey);
638     HeapFree(GetProcessHeap(), 0, Name);
639
640 done:
641     TreeView_SetItemState(hwndTV, hItem, TVIS_EXPANDEDONCE, TVIS_EXPANDEDONCE);
642     SendMessageW(hwndTV, WM_SETREDRAW, TRUE, 0);
643     SetCursor(hcursorOld);
644     expanding = FALSE;
645     HeapFree(GetProcessHeap(), 0, keyPath);
646
647     return TRUE;
648 }
649
650 BOOL OnTreeExpanding(HWND hwndTV, NMTREEVIEW* pnmtv)
651 {
652     return UpdateExpandingTree(hwndTV, pnmtv->itemNew.hItem, pnmtv->itemNew.state);
653 }
654
655
656 /*
657  * CreateTreeView - creates a tree view control.
658  * Returns the handle to the new control if successful, or NULL otherwise.
659  * hwndParent - handle to the control's parent window.
660  */
661 HWND CreateTreeView(HWND hwndParent, LPWSTR pHostName, UINT id)
662 {
663     RECT rcClient;
664     HWND hwndTV;
665     WCHAR TreeView[] = {'T','r','e','e',' ','V','i','e','w',0};
666
667     /* Get the dimensions of the parent window's client area, and create the tree view control.  */
668     GetClientRect(hwndParent, &rcClient);
669     hwndTV = CreateWindowExW(WS_EX_CLIENTEDGE, WC_TREEVIEWW, TreeView,
670                             WS_VISIBLE | WS_CHILD | WS_TABSTOP | TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT,
671                             0, 0, rcClient.right, rcClient.bottom,
672                             hwndParent, (HMENU)ULongToHandle(id), hInst, NULL);
673     SendMessageW(hwndTV, TVM_SETUNICODEFORMAT, TRUE, 0);
674     /* Initialize the image list, and add items to the control.  */
675     if (!InitTreeViewImageLists(hwndTV) || !InitTreeViewItems(hwndTV, pHostName)) {
676         DestroyWindow(hwndTV);
677         return NULL;
678     }
679     return hwndTV;
680 }