regedit: Ignore case when sorting values.
[wine.git] / programs / regedit / listview.c
blob1091d8d3cf9c78be0340a589130a51d7b2a7fbde
1 /*
2 * Regedit listviews
4 * Copyright (C) 2002 Robert Dickenson <robd@reactos.org>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #include <windows.h>
22 #include <commctrl.h>
23 #include <stdlib.h>
24 #include <tchar.h>
25 #include <stdio.h>
27 #include "main.h"
28 #include "regproc.h"
30 #include "wine/unicode.h"
31 static INT Image_String;
32 static INT Image_Binary;
34 typedef struct tagLINE_INFO
36 DWORD dwValType;
37 LPWSTR name;
38 void* val;
39 size_t val_len;
40 } LINE_INFO;
42 /*******************************************************************************
43 * Global and Local Variables:
46 static WNDPROC g_orgListWndProc;
47 static DWORD g_columnToSort = ~0U;
48 static BOOL g_invertSort = FALSE;
49 static LPTSTR g_valueName;
50 static LPTSTR g_currentPath;
51 static HKEY g_currentRootKey;
52 static WCHAR g_szValueNotSetW[64];
53 static TCHAR g_szValueNotSet[64];
55 #define MAX_LIST_COLUMNS (IDS_LIST_COLUMN_LAST - IDS_LIST_COLUMN_FIRST + 1)
56 static int default_column_widths[MAX_LIST_COLUMNS] = { 200, 175, 400 };
57 static int column_alignment[MAX_LIST_COLUMNS] = { LVCFMT_LEFT, LVCFMT_LEFT, LVCFMT_LEFT };
59 LPTSTR GetItemText(HWND hwndLV, UINT item)
61 LPTSTR newStr, curStr;
62 unsigned int maxLen = 128;
64 curStr = HeapAlloc(GetProcessHeap(), 0, maxLen);
65 if (!curStr) return NULL;
66 if (item == 0) { /* first item is ALWAYS a default */
67 HeapFree(GetProcessHeap(), 0, curStr);
68 return NULL;
70 do {
71 ListView_GetItemText(hwndLV, item, 0, curStr, maxLen);
72 if (_tcslen(curStr) < maxLen - 1) return curStr;
73 newStr = HeapReAlloc(GetProcessHeap(), 0, curStr, maxLen * 2);
74 if (!newStr) break;
75 curStr = newStr;
76 maxLen *= 2;
77 } while (TRUE);
78 HeapFree(GetProcessHeap(), 0, curStr);
79 return NULL;
82 LPCTSTR GetValueName(HWND hwndLV)
84 INT item;
86 if (g_valueName != LPSTR_TEXTCALLBACK)
87 HeapFree(GetProcessHeap(), 0, g_valueName);
88 g_valueName = NULL;
90 item = ListView_GetNextItem(hwndLV, -1, LVNI_FOCUSED);
91 if (item == -1) return NULL;
93 g_valueName = GetItemText(hwndLV, item);
95 return g_valueName;
98 /* convert '\0' separated string list into ',' separated string list */
99 static void MakeMULTISZDisplayable(LPWSTR multi)
103 for (; *multi; multi++)
105 if (*(multi+1))
107 *multi = ',';
108 multi++;
110 } while (*multi);
113 /*******************************************************************************
114 * Local module support methods
116 static void AddEntryToList(HWND hwndLV, LPWSTR Name, DWORD dwValType,
117 void* ValBuf, DWORD dwCount, BOOL bHighlight)
119 LINE_INFO* linfo;
120 LVITEMW item;
121 int index;
123 linfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(LINE_INFO) + dwCount);
124 linfo->dwValType = dwValType;
125 linfo->val_len = dwCount;
126 CopyMemory(&linfo[1], ValBuf, dwCount);
128 if (Name)
130 linfo->name = HeapAlloc(GetProcessHeap(), 0, (lstrlenW(Name) + 1) * sizeof(WCHAR));
131 lstrcpyW(linfo->name, Name);
132 } else
134 linfo->name = NULL;
137 item.mask = LVIF_TEXT | LVIF_PARAM | LVIF_STATE | LVIF_IMAGE;
138 item.iItem = ListView_GetItemCount(hwndLV);/*idx; */
139 item.iSubItem = 0;
140 item.state = 0;
141 item.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
142 item.pszText = Name ? Name : LPSTR_TEXTCALLBACKW;
143 item.cchTextMax = Name ? lstrlenW(item.pszText) : 0;
144 if (bHighlight) {
145 item.stateMask = item.state = LVIS_FOCUSED | LVIS_SELECTED;
147 switch (dwValType)
149 case REG_SZ:
150 case REG_EXPAND_SZ:
151 case REG_MULTI_SZ:
152 item.iImage = Image_String;
153 break;
154 default:
155 item.iImage = Image_Binary;
156 break;
158 item.lParam = (LPARAM)linfo;
160 #if (_WIN32_IE >= 0x0300)
161 item.iIndent = 0;
162 #endif
164 index = ListView_InsertItemW(hwndLV, &item);
165 if (index != -1) {
166 switch (dwValType) {
167 case REG_SZ:
168 case REG_EXPAND_SZ:
169 if (ValBuf) {
170 ListView_SetItemTextW(hwndLV, index, 2, ValBuf);
171 } else {
172 ListView_SetItemTextW(hwndLV, index, 2, g_szValueNotSetW);
174 break;
175 case REG_DWORD: {
176 WCHAR buf[64];
177 WCHAR format[] = {'0','x','%','0','8','x',' ','(','%','u',')',0};
178 wsprintfW(buf, format, *(DWORD*)ValBuf, *(DWORD*)ValBuf);
179 ListView_SetItemTextW(hwndLV, index, 2, buf);
181 break;
182 case REG_BINARY: {
183 unsigned int i;
184 LPBYTE pData = (LPBYTE)ValBuf;
185 LPWSTR strBinary = HeapAlloc(GetProcessHeap(), 0, dwCount * sizeof(WCHAR) * 3 + sizeof(WCHAR));
186 WCHAR format[] = {'%','0','2','X',' ',0};
187 for (i = 0; i < dwCount; i++)
188 wsprintfW( strBinary + i*3, format, pData[i] );
189 strBinary[dwCount * 3] = 0;
190 ListView_SetItemTextW(hwndLV, index, 2, strBinary);
191 HeapFree(GetProcessHeap(), 0, strBinary);
193 break;
194 case REG_MULTI_SZ:
195 MakeMULTISZDisplayable(ValBuf);
196 ListView_SetItemTextW(hwndLV, index, 2, ValBuf);
197 break;
198 default:
200 WCHAR szText[128];
201 LoadStringW(hInst, IDS_REGISTRY_VALUE_CANT_DISPLAY, szText, COUNT_OF(szText));
202 ListView_SetItemTextW(hwndLV, index, 2, szText);
203 break;
209 static BOOL InitListViewImageList(HWND hWndListView)
211 HIMAGELIST himl;
212 HICON hicon;
213 INT cx = GetSystemMetrics(SM_CXSMICON);
214 INT cy = GetSystemMetrics(SM_CYSMICON);
216 himl = ImageList_Create(cx, cy, ILC_MASK, 0, 2);
217 if (!himl)
218 return FALSE;
220 hicon = LoadImage(hInst, MAKEINTRESOURCE(IDI_STRING),
221 IMAGE_ICON, cx, cy, LR_DEFAULTCOLOR);
222 Image_String = ImageList_AddIcon(himl, hicon);
224 hicon = LoadImage(hInst, MAKEINTRESOURCE(IDI_BIN),
225 IMAGE_ICON, cx, cy, LR_DEFAULTCOLOR);
226 Image_Binary = ImageList_AddIcon(himl, hicon);
228 SendMessage( hWndListView, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) himl );
230 /* fail if some of the icons failed to load */
231 if (ImageList_GetImageCount(himl) < 2)
232 return FALSE;
234 return TRUE;
237 static BOOL CreateListColumns(HWND hWndListView)
239 TCHAR szText[50];
240 int index;
241 LV_COLUMN lvC;
243 /* Create columns. */
244 lvC.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
245 lvC.pszText = szText;
247 /* Load the column labels from the resource file. */
248 for (index = 0; index < MAX_LIST_COLUMNS; index++) {
249 lvC.iSubItem = index;
250 lvC.cx = default_column_widths[index];
251 lvC.fmt = column_alignment[index];
252 LoadString(hInst, IDS_LIST_COLUMN_FIRST + index, szText, sizeof(szText)/sizeof(TCHAR));
253 if (ListView_InsertColumn(hWndListView, index, &lvC) == -1) return FALSE;
255 return TRUE;
258 /* OnGetDispInfo - processes the LVN_GETDISPINFO notification message. */
260 static void OnGetDispInfo(NMLVDISPINFO* plvdi)
262 static TCHAR buffer[200];
263 static TCHAR reg_szT[] = {'R','E','G','_','S','Z',0},
264 reg_expand_szT[] = {'R','E','G','_','E','X','P','A','N','D','_','S','Z',0},
265 reg_binaryT[] = {'R','E','G','_','B','I','N','A','R','Y',0},
266 reg_dwordT[] = {'R','E','G','_','D','W','O','R','D',0},
267 reg_dword_big_endianT[] = {'R','E','G','_','D','W','O','R','D','_',
268 'B','I','G','_','E','N','D','I','A','N',0},
269 reg_multi_szT[] = {'R','E','G','_','M','U','L','T','I','_','S','Z',0},
270 reg_linkT[] = {'R','E','G','_','L','I','N','K',0},
271 reg_resource_listT[] = {'R','E','G','_','R','E','S','O','U','R','C','E','_','L','I','S','T',0},
272 reg_noneT[] = {'R','E','G','_','N','O','N','E',0},
273 emptyT[] = {0};
275 plvdi->item.pszText = NULL;
276 plvdi->item.cchTextMax = 0;
278 switch (plvdi->item.iSubItem) {
279 case 0:
280 plvdi->item.pszText = (LPSTR)g_pszDefaultValueName;
281 break;
282 case 1:
283 switch (((LINE_INFO*)plvdi->item.lParam)->dwValType) {
284 case REG_SZ:
285 plvdi->item.pszText = reg_szT;
286 break;
287 case REG_EXPAND_SZ:
288 plvdi->item.pszText = reg_expand_szT;
289 break;
290 case REG_BINARY:
291 plvdi->item.pszText = reg_binaryT;
292 break;
293 case REG_DWORD:
294 plvdi->item.pszText = reg_dwordT;
295 break;
296 case REG_DWORD_BIG_ENDIAN:
297 plvdi->item.pszText = reg_dword_big_endianT;
298 break;
299 case REG_MULTI_SZ:
300 plvdi->item.pszText = reg_multi_szT;
301 break;
302 case REG_LINK:
303 plvdi->item.pszText = reg_linkT;
304 break;
305 case REG_RESOURCE_LIST:
306 plvdi->item.pszText = reg_resource_listT;
307 break;
308 case REG_NONE:
309 plvdi->item.pszText = reg_noneT;
310 break;
311 default:
313 TCHAR szUnknownFmt[64];
314 LoadString(hInst, IDS_REGISTRY_UNKNOWN_TYPE, szUnknownFmt, COUNT_OF(szUnknownFmt));
315 wsprintf(buffer, szUnknownFmt, plvdi->item.lParam);
316 plvdi->item.pszText = buffer;
317 break;
320 break;
321 case 2:
322 plvdi->item.pszText = g_szValueNotSet;
323 break;
324 case 3:
325 plvdi->item.pszText = emptyT;
326 break;
330 static int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
332 LINE_INFO*l, *r;
333 l = (LINE_INFO*)lParam1;
334 r = (LINE_INFO*)lParam2;
335 if (!l->name) return -1;
336 if (!r->name) return +1;
338 if (g_columnToSort == ~0U)
339 g_columnToSort = 0;
341 if (g_columnToSort == 1 && l->dwValType != r->dwValType)
342 return g_invertSort ? (int)r->dwValType - (int)l->dwValType : (int)l->dwValType - (int)r->dwValType;
343 if (g_columnToSort == 2) {
344 /* FIXME: Sort on value */
346 return g_invertSort ? lstrcmpiW(r->name, l->name) : lstrcmpiW(l->name, r->name);
349 HWND StartValueRename(HWND hwndLV)
351 int item;
353 item = ListView_GetNextItem(hwndLV, -1, LVNI_FOCUSED | LVNI_SELECTED);
354 if (item < 1) { /* cannot rename default key */
355 MessageBeep(MB_ICONHAND);
356 return 0;
358 return ListView_EditLabel(hwndLV, item);
361 static BOOL _CmdWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
363 switch (LOWORD(wParam)) {
364 /* case ID_FILE_OPEN: */
365 /* break; */
366 default:
367 return FALSE;
369 return TRUE;
372 static LRESULT CALLBACK ListWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
374 switch (message) {
375 case WM_COMMAND:
376 if (!_CmdWndProc(hWnd, message, wParam, lParam)) {
377 return CallWindowProc(g_orgListWndProc, hWnd, message, wParam, lParam);
379 break;
380 case WM_NOTIFY_REFLECT:
381 switch (((LPNMHDR)lParam)->code) {
383 case LVN_BEGINLABELEDIT:
384 if (!((NMLVDISPINFO *)lParam)->item.iItem)
385 return 1;
386 return 0;
387 case LVN_GETDISPINFO:
388 OnGetDispInfo((NMLVDISPINFO*)lParam);
389 break;
390 case LVN_COLUMNCLICK:
391 if (g_columnToSort == ((LPNMLISTVIEW)lParam)->iSubItem)
392 g_invertSort = !g_invertSort;
393 else {
394 g_columnToSort = ((LPNMLISTVIEW)lParam)->iSubItem;
395 g_invertSort = FALSE;
398 SendMessage(hWnd, LVM_SORTITEMS, (WPARAM)hWnd, (LPARAM)CompareFunc);
399 break;
400 case LVN_ENDLABELEDIT: {
401 LPNMLVDISPINFO dispInfo = (LPNMLVDISPINFO)lParam;
402 LPTSTR valueName = GetItemText(hWnd, dispInfo->item.iItem);
403 LONG ret;
404 if (!valueName) return -1; /* cannot rename a default value */
405 ret = RenameValue(hWnd, g_currentRootKey, g_currentPath, valueName, dispInfo->item.pszText);
406 if (ret)
407 RefreshListView(hWnd, g_currentRootKey, g_currentPath, dispInfo->item.pszText);
408 HeapFree(GetProcessHeap(), 0, valueName);
409 return 0;
411 case NM_RETURN: {
412 int cnt = ListView_GetNextItem(hWnd, -1, LVNI_FOCUSED | LVNI_SELECTED);
413 if (cnt != -1)
414 SendMessage(hFrameWnd, WM_COMMAND, ID_EDIT_MODIFY, 0);
416 break;
417 case NM_DBLCLK: {
418 NMITEMACTIVATE* nmitem = (LPNMITEMACTIVATE)lParam;
419 LVHITTESTINFO info;
421 /* if (nmitem->hdr.hwndFrom != hWnd) break; unnecessary because of WM_NOTIFY_REFLECT */
422 /* if (nmitem->hdr.idFrom != IDW_LISTVIEW) break; */
423 /* if (nmitem->hdr.code != ???) break; */
424 #ifdef _MSC_VER
425 switch (nmitem->uKeyFlags) {
426 case LVKF_ALT: /* The ALT key is pressed. */
427 /* properties dialog box ? */
428 break;
429 case LVKF_CONTROL: /* The CTRL key is pressed. */
430 /* run dialog box for providing parameters... */
431 break;
432 case LVKF_SHIFT: /* The SHIFT key is pressed. */
433 break;
435 #endif
436 info.pt.x = nmitem->ptAction.x;
437 info.pt.y = nmitem->ptAction.y;
438 if (ListView_HitTest(hWnd, &info) != -1) {
439 ListView_SetItemState(hWnd, -1, 0, LVIS_FOCUSED|LVIS_SELECTED);
440 ListView_SetItemState(hWnd, info.iItem, LVIS_FOCUSED|LVIS_SELECTED,
441 LVIS_FOCUSED|LVIS_SELECTED);
442 SendMessage(hFrameWnd, WM_COMMAND, ID_EDIT_MODIFY, 0);
445 break;
447 default:
448 return 0; /* shouldn't call default ! */
450 break;
451 case WM_CONTEXTMENU: {
452 int cnt = ListView_GetNextItem(hWnd, -1, LVNI_SELECTED);
453 TrackPopupMenu(GetSubMenu(hPopupMenus, cnt == -1 ? PM_NEW : PM_MODIFYVALUE),
454 TPM_RIGHTBUTTON, (short)LOWORD(lParam), (short)HIWORD(lParam),
455 0, hFrameWnd, NULL);
456 break;
458 default:
459 return CallWindowProc(g_orgListWndProc, hWnd, message, wParam, lParam);
461 return 0;
465 HWND CreateListView(HWND hwndParent, UINT id)
467 RECT rcClient;
468 HWND hwndLV;
470 /* prepare strings */
471 LoadString(hInst, IDS_REGISTRY_VALUE_NOT_SET, g_szValueNotSet, COUNT_OF(g_szValueNotSet));
472 LoadStringW(hInst, IDS_REGISTRY_VALUE_NOT_SET, g_szValueNotSetW, COUNT_OF(g_szValueNotSetW));
474 /* Get the dimensions of the parent window's client area, and create the list view control. */
475 GetClientRect(hwndParent, &rcClient);
476 hwndLV = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, _T("List View"),
477 WS_VISIBLE | WS_CHILD | WS_TABSTOP | LVS_REPORT | LVS_EDITLABELS,
478 0, 0, rcClient.right, rcClient.bottom,
479 hwndParent, (HMENU)ULongToHandle(id), hInst, NULL);
480 if (!hwndLV) return NULL;
481 SendMessage(hwndLV, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_FULLROWSELECT);
483 /* Initialize the image list */
484 if (!InitListViewImageList(hwndLV)) goto fail;
485 if (!CreateListColumns(hwndLV)) goto fail;
486 g_orgListWndProc = (WNDPROC) SetWindowLongPtr(hwndLV, GWLP_WNDPROC, (LPARAM)ListWndProc);
487 return hwndLV;
488 fail:
489 DestroyWindow(hwndLV);
490 return NULL;
493 BOOL RefreshListView(HWND hwndLV, HKEY hKeyRoot, LPCTSTR keyPath, LPCTSTR highlightValue)
495 BOOL result = FALSE;
496 DWORD max_sub_key_len;
497 DWORD max_val_name_len, valNameLen;
498 DWORD max_val_size, valSize;
499 DWORD val_count, index, valType;
500 WCHAR* valName = 0;
501 BYTE* valBuf = 0;
502 HKEY hKey = 0;
503 LONG errCode;
504 INT count, i;
505 LVITEMW item;
506 WCHAR* keyPathW;
507 WCHAR* highlightValueW;
509 if (!hwndLV) return FALSE;
511 SendMessageW(hwndLV, WM_SETREDRAW, FALSE, 0);
513 keyPathW = GetWideString(keyPath);
514 highlightValueW = GetWideString(highlightValue);
516 errCode = RegOpenKeyExW(hKeyRoot, keyPathW, 0, KEY_READ, &hKey);
517 if (errCode != ERROR_SUCCESS) goto done;
519 count = ListView_GetItemCount(hwndLV);
520 for (i = 0; i < count; i++) {
521 item.mask = LVIF_PARAM;
522 item.iItem = i;
523 SendMessageW( hwndLV, LVM_GETITEM, 0, (LPARAM)&item );
524 HeapFree(GetProcessHeap(), 0, ((LINE_INFO*)item.lParam)->name);
525 HeapFree(GetProcessHeap(), 0, (void*)item.lParam);
527 g_columnToSort = ~0U;
528 SendMessageW( hwndLV, LVM_DELETEALLITEMS, 0, 0L );
530 /* get size information and resize the buffers if necessary */
531 errCode = RegQueryInfoKeyW(hKey, NULL, NULL, NULL, NULL, &max_sub_key_len, NULL,
532 &val_count, &max_val_name_len, &max_val_size, NULL, NULL);
533 if (errCode != ERROR_SUCCESS) goto done;
535 /* account for the terminator char */
536 max_val_name_len++;
537 max_val_size++;
539 valName = HeapAlloc(GetProcessHeap(), 0, max_val_name_len * sizeof(WCHAR));
540 valBuf = HeapAlloc(GetProcessHeap(), 0, max_val_size);
541 if (RegQueryValueExW(hKey, NULL, NULL, &valType, valBuf, &valSize) == ERROR_FILE_NOT_FOUND) {
542 AddEntryToList(hwndLV, NULL, REG_SZ, NULL, 0, !highlightValue);
544 for(index = 0; index < val_count; index++) {
545 BOOL bSelected = (valName == highlightValueW); /* NOT a bug, we check for double NULL here */
546 valNameLen = max_val_name_len;
547 valSize = max_val_size;
548 valType = 0;
549 errCode = RegEnumValueW(hKey, index, valName, &valNameLen, NULL, &valType, valBuf, &valSize);
550 if (errCode != ERROR_SUCCESS) goto done;
551 valBuf[valSize] = 0;
552 if (valName && highlightValueW && lstrcmpW(valName, highlightValueW))
553 bSelected = TRUE;
554 AddEntryToList(hwndLV, valName[0] ? valName : NULL, valType, valBuf, valSize, bSelected);
556 SendMessageW(hwndLV, LVM_SORTITEMS, (WPARAM)hwndLV, (LPARAM)CompareFunc);
558 g_currentRootKey = hKeyRoot;
559 if (keyPath != g_currentPath) {
560 HeapFree(GetProcessHeap(), 0, g_currentPath);
561 g_currentPath = HeapAlloc(GetProcessHeap(), 0, (lstrlen(keyPath) + 1) * sizeof(TCHAR));
562 if (!g_currentPath) goto done;
563 lstrcpy(g_currentPath, keyPath);
566 result = TRUE;
568 done:
569 HeapFree(GetProcessHeap(), 0, valBuf);
570 HeapFree(GetProcessHeap(), 0, valName);
571 HeapFree(GetProcessHeap(), 0, keyPathW);
572 HeapFree(GetProcessHeap(), 0, highlightValueW);
573 SendMessageW(hwndLV, WM_SETREDRAW, TRUE, 0);
574 if (hKey) RegCloseKey(hKey);
576 return result;