1 // FontSub by Katayama Hirofumi MZ
2 //
3 // To the extent possible under law, the person who associated CC0 with
4 // FontSub has waived all copyright and related or neighboring rights
5 // to FontSub.
6 //
7 // You should have received a copy of the CC0 legalcode along with this
8 // work.  If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
9 
10 
11 #include <windows.h>
12 #include <windowsx.h>
13 #include <commctrl.h>
14 #include <tchar.h>
15 #include <vector>       // for std::vector
16 #include <set>          // for std::set
17 #include <string>       // for std::basic_string
18 #include <algorithm>    // for std::sort
19 #include <cstdio>
20 #include <cstring>
21 #include <cassert>
22 #include "resource.h"
23 
24 
25 #define NAME_COLUMN_WIDTH   250
26 #define SUB_COLUMN_WIDTH    250
27 #define MAX_STRING          120
28 
29 #ifndef _countof
30     #define _countof(array)     (sizeof(array) / sizeof(array[0]))
31 #endif
32 
33 typedef std::wstring        STRING;
34 
35 struct ITEM
36 {
37     STRING  m_Name, m_Substitute;
38     BYTE    m_CharSet1, m_CharSet2;
39     ITEM(const STRING& Name, const STRING& Substitute,
40          BYTE CharSet1, BYTE CharSet2)
41         : m_Name(Name), m_Substitute(Substitute),
42           m_CharSet1(CharSet1), m_CharSet2(CharSet2) { }
43 };
44 
45 /* global variables */
46 HINSTANCE   g_hInstance = NULL;
47 HWND        g_hMainWnd = NULL;
48 HICON       g_hIcon = NULL;
49 HWND        g_hListView = NULL;
50 BOOL        g_bModified = FALSE;
51 BOOL        g_bNeedsReboot = FALSE;
52 INT         g_iItem = 0;
53 
54 LPCWSTR     g_pszClassName = L"ReactOS Font Substitutes Editor";
55 LPCWSTR     g_pszFileHeader = L"Windows Registry Editor Version 5.00";
56 
57 WCHAR       g_szTitle[MAX_STRING];
58 WCHAR       g_szNameHead[MAX_STRING];
59 WCHAR       g_szSubstituteHead[MAX_STRING];
60 
61 INT         g_iSortColumn = 0;
62 BOOL        g_bSortAscendant = TRUE;
63 
64 LPCWSTR     g_pszKey =
65     L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes";
66 
67 typedef std::set<STRING>    FONTNAMESET;
68 typedef std::vector<ITEM>   ITEMVECTOR;
69 
70 FONTNAMESET g_Names;
71 ITEMVECTOR  g_Items;
72 STRING      g_strFontName;
73 STRING      g_strSubstitute;
74 BYTE        g_CharSet1 = DEFAULT_CHARSET;
75 BYTE        g_CharSet2 = DEFAULT_CHARSET;
76 
77 typedef struct CHARSET_ENTRY
78 {
79     BYTE        CharSet;
80     LPCWSTR     DisplayName;
81 } CHARSET_ENTRY;
82 
83 CHARSET_ENTRY g_CharSetList[] =
84 {
85     { DEFAULT_CHARSET,      L"DEFAULT_CHARSET (1)" },
86     { ANSI_CHARSET,         L"ANSI_CHARSET (0)" },
87     { SYMBOL_CHARSET,       L"SYMBOL_CHARSET (2)" },
88     { SHIFTJIS_CHARSET,     L"SHIFTJIS_CHARSET (128)" },
89     { HANGUL_CHARSET,       L"HANGUL_CHARSET (129)" },
90     { GB2312_CHARSET,       L"GB2312_CHARSET (134)" },
91     { CHINESEBIG5_CHARSET,  L"CHINESEBIG5_CHARSET (136)" },
92     { OEM_CHARSET,          L"OEM_CHARSET (255)" },
93     { JOHAB_CHARSET,        L"JOHAB_CHARSET (130)" },
94     { HEBREW_CHARSET,       L"HEBREW_CHARSET (177)" },
95     { ARABIC_CHARSET,       L"ARABIC_CHARSET (178)" },
96     { GREEK_CHARSET,        L"GREEK_CHARSET (161)" },
97     { TURKISH_CHARSET,      L"TURKISH_CHARSET (162)" },
98     { VIETNAMESE_CHARSET,   L"VIETNAMESE_CHARSET (163)" },
99     { THAI_CHARSET,         L"THAI_CHARSET (222)" },
100     { EASTEUROPE_CHARSET,   L"EASTEUROPE_CHARSET (238)" },
101     { RUSSIAN_CHARSET,      L"RUSSIAN_CHARSET (204)" },
102     { MAC_CHARSET,          L"MAC_CHARSET (77)" },
103     { BALTIC_CHARSET,       L"BALTIC_CHARSET (186)" }
104 };
105 const WCHAR g_LongestName[] = L"CHINESEBIG5_CHARSET (136)";
106 
107 static void trim(STRING& str)
108 {
109     static const WCHAR Spaces[] = L" \t\r\n";
110     size_t i = str.find_first_not_of(Spaces);
111     size_t j = str.find_last_not_of(Spaces);
112     if (i == STRING::npos || j == STRING::npos)
113     {
114         str.clear();
115     }
116     else
117     {
118         str = str.substr(i, j - i + 1);
119     }
120 }
121 
122 static int CALLBACK
123 EnumFontFamExProc(const ENUMLOGFONTW *pelf,
124                   const NEWTEXTMETRICW *pntm,
125                   int FontType,
126                   LPARAM lParam)
127 {
128     switch (pelf->elfFullName[0])
129     {
130         case UNICODE_NULL: case L'@':
131             break;
132         default:
133             g_Names.insert((const WCHAR *)pelf->elfFullName);
134     }
135     switch (pelf->elfLogFont.lfFaceName[0])
136     {
137         case UNICODE_NULL: case L'@':
138             break;
139         default:
140             g_Names.insert(pelf->elfLogFont.lfFaceName);
141     }
142     return 1;
143 }
144 
145 BOOL DoLoadNames(void)
146 {
147     g_Names.clear();
148 
149     LOGFONTW lf;
150     ZeroMemory(&lf, sizeof(lf));
151     lf.lfCharSet = DEFAULT_CHARSET;
152 
153     HDC hDC = CreateCompatibleDC(NULL);
154     EnumFontFamiliesExW(hDC, &lf, (FONTENUMPROCW)EnumFontFamExProc, 0, 0);
155     DeleteDC(hDC);
156 
157     return !g_Names.empty();
158 }
159 
160 inline bool ItemCompareByNameAscend(const ITEM& Item1, const ITEM& Item2)
161 {
162     return Item1.m_Name < Item2.m_Name;
163 }
164 
165 inline bool ItemCompareByNameDescend(const ITEM& Item1, const ITEM& Item2)
166 {
167     return Item1.m_Name > Item2.m_Name;
168 }
169 
170 inline bool ItemCompareBySubAscend(const ITEM& Item1, const ITEM& Item2)
171 {
172     return Item1.m_Substitute < Item2.m_Substitute;
173 }
174 
175 inline bool ItemCompareBySubDescend(const ITEM& Item1, const ITEM& Item2)
176 {
177     return Item1.m_Substitute > Item2.m_Substitute;
178 }
179 
180 void DoSort(INT iColumn, BOOL bAscendant = TRUE)
181 {
182     LV_COLUMN Column;
183     ZeroMemory(&Column, sizeof(Column));
184     Column.mask = LVCF_IMAGE | LVCF_SUBITEM;
185     Column.iImage = 2;
186     Column.iSubItem = 0;
187     ListView_SetColumn(g_hListView, 0, &Column);
188     Column.iSubItem = 1;
189     ListView_SetColumn(g_hListView, 1, &Column);
190 
191     switch (iColumn)
192     {
193         case 0:
194             Column.iSubItem = 0;
195             if (bAscendant)
196             {
197                 std::sort(g_Items.begin(), g_Items.end(),
198                           ItemCompareByNameAscend);
199                 Column.iImage = 0;
200                 ListView_SetColumn(g_hListView, 0, &Column);
201             }
202             else
203             {
204                 std::sort(g_Items.begin(), g_Items.end(),
205                           ItemCompareByNameDescend);
206                 Column.iImage = 1;
207                 ListView_SetColumn(g_hListView, 0, &Column);
208             }
209             break;
210         case 1:
211             Column.iSubItem = 1;
212             if (bAscendant)
213             {
214                 std::sort(g_Items.begin(), g_Items.end(),
215                           ItemCompareBySubAscend);
216                 Column.iImage = 0;
217                 ListView_SetColumn(g_hListView, 1, &Column);
218             }
219             else
220             {
221                 std::sort(g_Items.begin(), g_Items.end(),
222                           ItemCompareBySubDescend);
223                 Column.iImage = 1;
224                 ListView_SetColumn(g_hListView, 1, &Column);
225             }
226             break;
227     }
228     g_iSortColumn = iColumn;
229     g_bSortAscendant = bAscendant;
230     InvalidateRect(g_hListView, NULL, TRUE);
231 }
232 
233 void LV_AddItems(HWND hwnd)
234 {
235     ListView_DeleteAllItems(hwnd);
236 
237     LV_ITEM Item;
238     ZeroMemory(&Item, sizeof(Item));
239     Item.mask = LVIF_PARAM;
240 
241     const INT Count = INT(g_Items.size());
242     for (INT i = 0; i < Count; ++i)
243     {
244         Item.iItem = i;
245         Item.iSubItem = 0;
246         Item.lParam = i;
247         ListView_InsertItem(hwnd, &Item);
248 
249         Item.iItem = i;
250         Item.iSubItem = 1;
251         Item.lParam = i;
252         ListView_InsertItem(hwnd, &Item);
253     }
254 }
255 
256 BOOL DoLoadItems(void)
257 {
258     ITEMVECTOR Items;
259 
260     HKEY hKey = NULL;
261     RegOpenKeyExW(HKEY_LOCAL_MACHINE, g_pszKey, 0, KEY_READ, &hKey);
262     if (hKey == NULL)
263         return FALSE;
264 
265     WCHAR szName[MAX_STRING], szValue[MAX_STRING];
266     DWORD cbName, cbValue;
267     for (DWORD dwIndex = 0; ; ++dwIndex)
268     {
269         cbName = sizeof(szName);
270         cbValue = sizeof(szValue);
271         LONG Error = RegEnumValueW(hKey, dwIndex, szName, &cbName,
272             NULL, NULL, (LPBYTE)szValue, &cbValue);
273         if (Error != ERROR_SUCCESS)
274             break;
275 
276         BYTE CharSet1 = DEFAULT_CHARSET, CharSet2 = DEFAULT_CHARSET;
277         LPWSTR pch;
278 
279         pch = wcsrchr(szName, L',');
280         if (pch)
281         {
282             *pch = 0;
283             CharSet1 = (BYTE)_wtoi(pch + 1);
284         }
285 
286         pch = wcsrchr(szValue, L',');
287         if (pch)
288         {
289             *pch = 0;
290             CharSet2 = (BYTE)_wtoi(pch + 1);
291         }
292 
293         ITEM Item(szName, szValue, CharSet1, CharSet2);
294         trim(Item.m_Name);
295         trim(Item.m_Substitute);
296         Items.push_back(Item);
297     }
298 
299     RegCloseKey(hKey);
300 
301     g_Items = Items;
302     LV_AddItems(g_hListView);
303     DoSort(0, TRUE);
304     g_bModified = FALSE;
305     g_bNeedsReboot = FALSE;
306 
307     return !g_Items.empty();
308 }
309 
310 BOOL DoLoad(void)
311 {
312     return DoLoadNames() && DoLoadItems();
313 }
314 
315 void LV_InvalidateRow(HWND hwnd, INT iRow = -1)
316 {
317     if (iRow == -1)
318         iRow = ListView_GetNextItem(hwnd, -1, LVNI_SELECTED);
319     if (iRow == -1)
320         return;
321 
322     RECT Rect;
323     LPRECT GccIsWhining = &Rect;
324     ListView_GetItemRect(hwnd, iRow, GccIsWhining, LVIR_BOUNDS);
325     InvalidateRect(hwnd, &Rect, FALSE);
326 }
327 
328 BOOL LV_Init(HWND hwnd)
329 {
330     ListView_SetExtendedListViewStyle(hwnd,
331         LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
332 
333     HIMAGELIST hImageList;
334     hImageList = ImageList_Create(12, 12, ILC_COLOR8 | ILC_MASK, 2, 2);
335 
336     HBITMAP hbm;
337     hbm = (HBITMAP)LoadImage(g_hInstance, MAKEINTRESOURCE(2), IMAGE_BITMAP,
338                              12, 12, LR_CREATEDIBSECTION | LR_LOADMAP3DCOLORS);
339     assert(hbm);
340     ImageList_AddMasked(hImageList, hbm, RGB(192, 192, 192));
341     DeleteObject(hbm);
342 
343     hbm = (HBITMAP)LoadImage(g_hInstance, MAKEINTRESOURCE(3), IMAGE_BITMAP,
344                              12, 12, LR_CREATEDIBSECTION | LR_LOADMAP3DCOLORS);
345     assert(hbm);
346     ImageList_AddMasked(hImageList, hbm, RGB(192, 192, 192));
347     DeleteObject(hbm);
348 
349     hbm = (HBITMAP)LoadImage(g_hInstance, MAKEINTRESOURCE(4), IMAGE_BITMAP,
350                              12, 12, LR_CREATEDIBSECTION | LR_LOADMAP3DCOLORS);
351     assert(hbm);
352     ImageList_AddMasked(hImageList, hbm, RGB(192, 192, 192));
353     DeleteObject(hbm);
354 
355     ListView_SetImageList(hwnd, hImageList, LVSIL_SMALL);
356 
357     LV_COLUMNW Column;
358     ZeroMemory(&Column, sizeof(Column));
359     Column.mask = LVCF_FMT | LVCF_TEXT | LVCF_SUBITEM | LVCF_WIDTH | LVCF_IMAGE;
360     Column.fmt = LVCFMT_LEFT;
361 
362     Column.cx = NAME_COLUMN_WIDTH;
363     Column.pszText = g_szNameHead;
364     Column.iSubItem = 0;
365     Column.iImage = 0;
366     ListView_InsertColumn(hwnd, 0, &Column);
367 
368     Column.cx = SUB_COLUMN_WIDTH;
369     Column.pszText = g_szSubstituteHead;
370     Column.iSubItem = 1;
371     Column.iImage = 2;
372     ListView_InsertColumn(hwnd, 1, &Column);
373 
374     UINT State = LVIS_SELECTED | LVIS_FOCUSED;
375     ListView_SetItemState(hwnd, 0, State, State);
376 
377     return TRUE;
378 }
379 
380 BOOL EditDlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
381 {
382     COMBOBOXEXITEMW Item;
383     ZeroMemory(&Item, sizeof(Item));
384     Item.mask = CBEIF_TEXT;
385 
386     FONTNAMESET::iterator it, end = g_Names.end();
387     for (it = g_Names.begin(); it != end; ++it)
388     {
389         Item.pszText = const_cast<LPWSTR>(it->c_str());
390         Item.iItem = ComboBox_GetCount(GetDlgItem(hwnd, cmb2));
391         SendDlgItemMessageW(hwnd, cmb2, CBEM_INSERTITEM, 0, (LPARAM)&Item);
392     }
393     SetDlgItemTextW(hwnd, edt1, g_strFontName.c_str());
394     SetDlgItemTextW(hwnd, cmb2, g_strSubstitute.c_str());
395 
396     const INT Count = _countof(g_CharSetList);
397     for (INT i = 0; i < Count; ++i)
398     {
399         Item.pszText = const_cast<LPWSTR>(g_CharSetList[i].DisplayName);
400         Item.iItem = ComboBox_GetCount(GetDlgItem(hwnd, cmb3));
401         SendDlgItemMessageW(hwnd, cmb3, CBEM_INSERTITEM, 0, (LPARAM)&Item);
402         Item.iItem = ComboBox_GetCount(GetDlgItem(hwnd, cmb4));
403         SendDlgItemMessageW(hwnd, cmb4, CBEM_INSERTITEM, 0, (LPARAM)&Item);
404     }
405 
406     SendDlgItemMessageW(hwnd, cmb3, CB_SETCURSEL, 0, 0);
407     SendDlgItemMessageW(hwnd, cmb4, CB_SETCURSEL, 0, 0);
408     for (INT i = 0; i < Count; ++i)
409     {
410         if (g_CharSet1 == g_CharSetList[i].CharSet)
411         {
412             SendDlgItemMessageW(hwnd, cmb3, CB_SETCURSEL, i, 0);
413         }
414         if (g_CharSet2 == g_CharSetList[i].CharSet)
415         {
416             SendDlgItemMessageW(hwnd, cmb4, CB_SETCURSEL, i, 0);
417         }
418     }
419 
420     SIZE siz;
421     HDC hDC = CreateCompatibleDC(NULL);
422     SelectObject(hDC, GetStockObject(DEFAULT_GUI_FONT));
423     GetTextExtentPoint32W(hDC, g_LongestName, lstrlenW(g_LongestName), &siz);
424     DeleteDC(hDC);
425 
426     SendDlgItemMessageW(hwnd, cmb3, CB_SETHORIZONTALEXTENT, siz.cx + 16, 0);
427     SendDlgItemMessageW(hwnd, cmb4, CB_SETHORIZONTALEXTENT, siz.cx + 16, 0);
428 
429     EnableWindow(GetDlgItem(hwnd, cmb3), FALSE);
430 
431     return TRUE;
432 }
433 
434 void LV_OnDelete(HWND hwnd, INT iRow = -1)
435 {
436     if (iRow == -1)
437         iRow = ListView_GetNextItem(hwnd, -1, LVNI_SELECTED);
438     if (iRow == -1)
439         return;
440 
441     UINT State = LVIS_SELECTED | LVIS_FOCUSED;
442     ListView_SetItemState(g_hListView, iRow, State, State);
443 
444     WCHAR sz[MAX_STRING];
445     LoadStringW(g_hInstance, IDS_QUERYDELETE, sz, _countof(sz));
446     if (IDYES != MessageBoxW(g_hMainWnd, sz, g_szTitle,
447                              MB_ICONINFORMATION | MB_YESNO))
448     {
449         return;
450     }
451 
452     ListView_DeleteItem(hwnd, iRow);
453     g_Items.erase(g_Items.begin() + iRow);
454     g_bModified = TRUE;
455 
456     ListView_SetItemState(g_hListView, iRow, State, State);
457 
458     InvalidateRect(hwnd, NULL, TRUE);
459 }
460 
461 void EditDlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
462 {
463     WCHAR szValue[MAX_STRING];
464     STRING str;
465     INT i;
466 
467     switch (id)
468     {
469         case IDOK:
470             GetDlgItemTextW(hwnd, cmb2, szValue, _countof(szValue));
471             str = szValue;
472             trim(str);
473             if (str.empty())
474             {
475                 WCHAR sz[MAX_STRING];
476                 SendDlgItemMessageW(hwnd, cmb2, CB_SETEDITSEL, 0, MAKELPARAM(0, -1));
477                 SetFocus(GetDlgItem(hwnd, cmb2));
478                 LoadStringW(g_hInstance, IDS_ENTERNAME2, sz, _countof(sz));
479                 MessageBoxW(hwnd, sz, NULL, MB_ICONERROR);
480                 return;
481             }
482 
483             g_Items[g_iItem].m_CharSet2 = DEFAULT_CHARSET;
484             i = SendDlgItemMessageW(hwnd, cmb4, CB_GETCURSEL, 0, 0);
485             if (i != CB_ERR)
486             {
487                 g_Items[g_iItem].m_CharSet2 = g_CharSetList[i].CharSet;
488             }
489             g_Items[g_iItem].m_Substitute = str;
490 
491             g_bModified = TRUE;
492             EndDialog(hwnd, IDOK);
493             break;
494         case IDCANCEL:
495             EndDialog(hwnd, IDCANCEL);
496             break;
497         case psh1:
498             LV_OnDelete(g_hListView, g_iItem);
499             EndDialog(hwnd, psh1);
500             break;
501     }
502 }
503 
504 INT_PTR CALLBACK
505 EditDlg_DlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
506 {
507     switch (uMsg)
508     {
509         HANDLE_MSG(hwnd, WM_INITDIALOG, EditDlg_OnInitDialog);
510         HANDLE_MSG(hwnd, WM_COMMAND, EditDlg_OnCommand);
511     }
512     return 0;
513 }
514 
515 void LV_OnDblClk(HWND hwnd)
516 {
517     g_iItem = ListView_GetNextItem(hwnd, -1, LVNI_SELECTED);
518     if (g_iItem == -1)
519         return;
520 
521     g_strFontName = g_Items[g_iItem].m_Name;
522     g_strSubstitute = g_Items[g_iItem].m_Substitute;
523     g_CharSet1 = g_Items[g_iItem].m_CharSet1;
524     g_CharSet2 = g_Items[g_iItem].m_CharSet2;
525 
526     DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_EDIT), g_hMainWnd,
527               EditDlg_DlgProc);
528     InvalidateRect(g_hListView, NULL, TRUE);
529 }
530 
531 BOOL MainWnd_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
532 {
533     DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_VSCROLL |
534                     LVS_SINGLESEL | LVS_REPORT | LVS_OWNERDRAWFIXED;
535     DWORD dwExStyle = WS_EX_CLIENTEDGE;
536     g_hListView = CreateWindowEx(dwExStyle, WC_LISTVIEW, NULL, dwStyle,
537                                  0, 0, 0, 0,
538                                  hwnd, (HMENU)1, g_hInstance, NULL);
539     if (g_hListView == NULL)
540         return FALSE;
541 
542     if (!LV_Init(g_hListView))
543         return FALSE;
544 
545     if (!DoLoad())
546         return FALSE;
547 
548     UINT State = LVIS_SELECTED | LVIS_FOCUSED;
549     ListView_SetItemState(g_hListView, 0, State, State);
550     SetFocus(g_hListView);
551     return TRUE;
552 }
553 
554 BOOL AddDlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
555 {
556     COMBOBOXEXITEMW Item;
557     ZeroMemory(&Item, sizeof(Item));
558     Item.iItem = -1;
559     Item.mask = CBEIF_TEXT;
560 
561     FONTNAMESET::iterator it, end = g_Names.end();
562     for (it = g_Names.begin(); it != end; ++it)
563     {
564         Item.pszText = const_cast<LPWSTR>(it->c_str());
565         Item.iItem = ComboBox_GetCount(GetDlgItem(hwnd, cmb1));
566         SendDlgItemMessageW(hwnd, cmb1, CBEM_INSERTITEM, 0, (LPARAM)&Item);
567         Item.iItem = ComboBox_GetCount(GetDlgItem(hwnd, cmb2));
568         SendDlgItemMessageW(hwnd, cmb2, CBEM_INSERTITEM, 0, (LPARAM)&Item);
569     }
570     WCHAR szEnterName[MAX_STRING];
571     LoadStringW(g_hInstance, IDS_ENTERNAME, szEnterName, _countof(szEnterName));
572     SetDlgItemTextW(hwnd, cmb1, szEnterName);
573     SendDlgItemMessageW(hwnd, cmb1, CB_SETEDITSEL, 0, MAKELPARAM(0, -1));
574 
575     const INT Count = _countof(g_CharSetList);
576     for (INT i = 0; i < Count; ++i)
577     {
578         Item.pszText = const_cast<LPWSTR>(g_CharSetList[i].DisplayName);
579         Item.iItem = ComboBox_GetCount(GetDlgItem(hwnd, cmb3));
580         SendDlgItemMessageW(hwnd, cmb3, CBEM_INSERTITEM, 0, (LPARAM)&Item);
581         Item.iItem = ComboBox_GetCount(GetDlgItem(hwnd, cmb4));
582         SendDlgItemMessageW(hwnd, cmb4, CBEM_INSERTITEM, 0, (LPARAM)&Item);
583     }
584 
585     SendDlgItemMessageW(hwnd, cmb3, CB_SETCURSEL, 0, 0);
586     SendDlgItemMessageW(hwnd, cmb4, CB_SETCURSEL, 0, 0);
587     for (INT i = 0; i < Count; ++i)
588     {
589         if (g_CharSet1 == g_CharSetList[i].CharSet)
590         {
591             SendDlgItemMessageW(hwnd, cmb3, CB_SETCURSEL, i, 0);
592         }
593         if (g_CharSet2 == g_CharSetList[i].CharSet)
594         {
595             SendDlgItemMessageW(hwnd, cmb4, CB_SETCURSEL, i, 0);
596         }
597     }
598 
599     SIZE siz;
600     HDC hDC = CreateCompatibleDC(NULL);
601     SelectObject(hDC, GetStockObject(DEFAULT_GUI_FONT));
602     GetTextExtentPoint32W(hDC, g_LongestName, lstrlenW(g_LongestName), &siz);
603     DeleteDC(hDC);
604 
605     SendDlgItemMessageW(hwnd, cmb3, CB_SETHORIZONTALEXTENT, siz.cx + 16, 0);
606     SendDlgItemMessageW(hwnd, cmb4, CB_SETHORIZONTALEXTENT, siz.cx + 16, 0);
607 
608     return TRUE;
609 }
610 
611 void AddDlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
612 {
613     WCHAR szKey[MAX_STRING], szValue[MAX_STRING], sz[MAX_STRING];
614     INT i, iCharSet1, iCharSet2;
615     BYTE CharSet1, CharSet2;
616     STRING key, value;
617     switch (id)
618     {
619         case IDOK:
620             GetDlgItemTextW(hwnd, cmb1, szKey, _countof(szKey));
621             key = szKey;
622             trim(key);
623             LoadStringW(g_hInstance, IDS_ENTERNAME, sz, _countof(sz));
624             if (key.empty() || key == sz)
625             {
626                 SendDlgItemMessageW(hwnd, cmb1, CB_SETEDITSEL, 0, MAKELPARAM(0, -1));
627                 SetFocus(GetDlgItem(hwnd, cmb1));
628                 LoadStringW(g_hInstance, IDS_ENTERNAME2, sz, _countof(sz));
629                 MessageBoxW(hwnd, sz, NULL, MB_ICONERROR);
630                 return;
631             }
632 
633             GetDlgItemTextW(hwnd, cmb2, szValue, _countof(szValue));
634             value = szValue;
635             trim(value);
636             if (value.empty())
637             {
638                 SendDlgItemMessageW(hwnd, cmb2, CB_SETEDITSEL, 0, MAKELPARAM(0, -1));
639                 SetFocus(GetDlgItem(hwnd, cmb2));
640                 LoadStringW(g_hInstance, IDS_ENTERNAME2, sz, _countof(sz));
641                 MessageBoxW(hwnd, sz, NULL, MB_ICONERROR);
642                 return;
643             }
644 
645             iCharSet1 = SendDlgItemMessageW(hwnd, cmb3, CB_GETCURSEL, 0, 0);
646             if (iCharSet1 == CB_ERR)
647                 iCharSet1 = 0;
648             iCharSet2 = SendDlgItemMessageW(hwnd, cmb4, CB_GETCURSEL, 0, 0);
649             if (iCharSet2 == CB_ERR)
650                 iCharSet2 = 0;
651 
652             CharSet1 = g_CharSetList[iCharSet1].CharSet;
653             CharSet2 = g_CharSetList[iCharSet2].CharSet;
654 
655             for (i = 0; i < (INT)g_Items.size(); ++i)
656             {
657                 if (g_Items[i].m_Name == key &&
658                     g_Items[i].m_CharSet1 == CharSet1)
659                 {
660                     WCHAR sz[MAX_STRING];
661                     SendDlgItemMessageW(hwnd, cmb1, CB_SETEDITSEL, 0, MAKELPARAM(0, -1));
662                     SetFocus(GetDlgItem(hwnd, cmb1));
663                     LoadStringW(g_hInstance, IDS_ALREADYEXISTS, sz, _countof(sz));
664                     MessageBoxW(hwnd, sz, NULL, MB_ICONERROR);
665                     return;
666                 }
667             }
668             {
669                 ITEM Item(key, value, CharSet1, CharSet2);
670                 g_Items.push_back(Item);
671                 g_bModified = TRUE;
672 
673                 i = (INT)g_Items.size();
674                 LV_ITEM LvItem;
675                 ZeroMemory(&LvItem, sizeof(LvItem));
676                 LvItem.mask = LVIF_PARAM;
677                 LvItem.iItem = i;
678                 LvItem.lParam = i;
679 
680                 LvItem.iSubItem = 0;
681                 ListView_InsertItem(g_hListView, &LvItem);
682 
683                 LvItem.iSubItem = 1;
684                 ListView_InsertItem(g_hListView, &LvItem);
685             }
686             g_bModified = TRUE;
687             EndDialog(hwnd, IDOK);
688             break;
689         case IDCANCEL:
690             EndDialog(hwnd, IDCANCEL);
691             break;
692     }
693 }
694 
695 INT_PTR CALLBACK
696 AddDlg_DlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
697 {
698     switch (uMsg)
699     {
700         HANDLE_MSG(hwnd, WM_INITDIALOG, AddDlg_OnInitDialog);
701         HANDLE_MSG(hwnd, WM_COMMAND, AddDlg_OnCommand);
702     }
703     return 0;
704 }
705 
706 void MainWnd_OnNew(HWND hwnd)
707 {
708     g_iItem = ListView_GetNextItem(hwnd, -1, LVNI_SELECTED);
709     if (g_iItem == -1)
710         return;
711 
712     g_strFontName = g_Items[g_iItem].m_Name;
713     g_strSubstitute = g_Items[g_iItem].m_Substitute;
714     g_CharSet1 = g_Items[g_iItem].m_CharSet1;
715     g_CharSet2 = g_Items[g_iItem].m_CharSet2;
716 
717     if (IDOK == DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_ADD), g_hMainWnd,
718                           AddDlg_DlgProc))
719     {
720         INT i = ListView_GetItemCount(g_hListView) - 1;
721         UINT State = LVIS_SELECTED | LVIS_FOCUSED;
722         ListView_SetItemState(g_hListView, i, State, State);
723         ListView_EnsureVisible(g_hListView, i, FALSE);
724     }
725 }
726 
727 BOOL MainWnd_OnUpdateRegistry(HWND hwnd)
728 {
729     // open the key
730     HKEY hKey = NULL;
731     RegOpenKeyExW(HKEY_LOCAL_MACHINE, g_pszKey, 0, KEY_ALL_ACCESS, &hKey);
732     if (hKey == NULL)
733         return FALSE;
734 
735     // clear all values
736     WCHAR szName[MAX_STRING], szValue[MAX_STRING];
737     DWORD cbName, cbValue;
738     for (;;)
739     {
740         cbName = sizeof(szName);
741         cbValue = sizeof(szValue);
742         LONG Error = RegEnumValueW(hKey, 0, szName, &cbName,
743                                    NULL, NULL, (LPBYTE)szValue, &cbValue);
744         if (Error != ERROR_SUCCESS)
745             break;
746 
747         RegDeleteValueW(hKey, szName);
748     }
749 
750     // set values
751     size_t Count = g_Items.size();
752     for (size_t i = 0; i < Count; ++i)
753     {
754         DWORD cbData = (g_Items[i].m_Substitute.size() + 1) * sizeof(WCHAR);
755         RegSetValueExW(hKey, g_Items[i].m_Name.c_str(), 0,
756             REG_SZ, (LPBYTE)g_Items[i].m_Substitute.c_str(), cbData);
757     }
758 
759     // close now
760     RegCloseKey(hKey);
761 
762     g_bModified = FALSE;
763     g_bNeedsReboot = TRUE;
764     return TRUE;
765 }
766 
767 LPWSTR SkipSpace(LPCWSTR pch)
768 {
769     while (*pch && wcschr(L" \t\r\n", *pch) != NULL)
770     {
771         ++pch;
772     }
773     return const_cast<LPWSTR>(pch);
774 }
775 
776 LPWSTR SkipQuoted(LPWSTR pch)
777 {
778     ++pch;  // L'"'
779     while (*pch)
780     {
781         if (*pch == L'"')
782         {
783             ++pch;
784             break;
785         }
786         if (*pch == L'\\')
787         {
788             ++pch;
789         }
790         ++pch;
791     }
792     return pch;
793 }
794 
795 void UnescapeHex(const STRING& str, size_t& i, STRING& Ret, BOOL Unicode)
796 {
797     STRING Num;
798 
799     // hexadecimal
800     if (iswxdigit(str[i]))
801     {
802         Num += str[i];
803         ++i;
804         if (iswxdigit(str[i]))
805         {
806             Num += str[i];
807             ++i;
808             if (Unicode)
809             {
810                 if (iswxdigit(str[i]))
811                 {
812                     Num += str[i];
813                     ++i;
814                     if (iswxdigit(str[i]))
815                     {
816                         Num += str[i];
817                         ++i;
818                     }
819                 }
820             }
821         }
822     }
823     if (!Num.empty())
824     {
825         Ret += (WCHAR)wcstoul(&Num[0], NULL, 16);
826     }
827 }
828 
829 void UnescapeOther(const STRING& str, size_t& i, STRING& Ret)
830 {
831     STRING Num;
832 
833     // check octal
834     if (L'0' <= str[i] && str[i] < L'8')
835     {
836         Num += str[i];
837         ++i;
838         if (L'0' <= str[i] && str[i] < L'8')
839         {
840             Num += str[i];
841             ++i;
842             if (L'0' <= str[i] && str[i] < L'8')
843             {
844                 Num += str[i];
845                 ++i;
846             }
847         }
848     }
849     if (Num.empty())
850     {
851         Ret += str[i];
852         ++i;
853     }
854     else
855     {
856         // octal
857         Ret += (WCHAR)wcstoul(&Num[0], NULL, 8);
858     }
859 }
860 
861 // process escape sequence
862 void UnescapeChar(const STRING& str, size_t& i, STRING& Ret)
863 {
864     if (str[i] != L'\\')
865     {
866         Ret += str[i];
867         ++i;
868         return;
869     }
870 
871     ++i;
872     switch (str[i])
873     {
874         case L'a': Ret += L'\a'; ++i; break;
875         case L'b': Ret += L'\b'; ++i; break;
876         case L'f': Ret += L'\f'; ++i; break;
877         case L'n': Ret += L'\n'; ++i; break;
878         case L'r': Ret += L'\r'; ++i; break;
879         case L't': Ret += L'\t'; ++i; break;
880         case L'v': Ret += L'\v'; ++i; break;
881         case L'x':
882             // hexidemical
883             ++i;
884             UnescapeHex(str, i, Ret, FALSE);
885             break;
886         case L'u':
887             // Unicode hexidemical
888             ++i;
889             UnescapeHex(str, i, Ret, TRUE);
890             break;
891         default:
892             // other case
893             UnescapeOther(str, i, Ret);
894             break;
895     }
896 }
897 
898 STRING Unquote(const STRING& str)
899 {
900     if (str[0] != L'"')
901         return str;
902 
903     STRING Ret;
904     size_t i = 1;
905     while (i < str.size())
906     {
907         if (str[i] == L'"' || str[i] == UNICODE_NULL)
908             break;
909 
910         UnescapeChar(str, i, Ret);
911     }
912     return Ret;
913 }
914 
915 BOOL DoParseFile(LPVOID pvContents, DWORD dwSize)
916 {
917     ITEMVECTOR  Items;
918 
919     LPWSTR pch, pchSep, pchStart = (LPWSTR)pvContents;
920 
921     pchStart[dwSize / sizeof(WCHAR)] = UNICODE_NULL;
922 
923     // check header
924     const DWORD cbHeader = lstrlenW(g_pszFileHeader) * sizeof(WCHAR);
925     if (memcmp(pchStart, g_pszFileHeader, cbHeader) != 0)
926         return FALSE;
927 
928     pchStart += cbHeader / sizeof(WCHAR);
929 
930     // find the key
931     WCHAR szKey[MAX_STRING];
932     wsprintfW(szKey, L"[HKEY_LOCAL_MACHINE\\%s]", g_pszKey);
933     pch = wcsstr(pchStart, szKey);
934     if (pch == NULL)
935         return FALSE;
936 
937     pchStart = pch + lstrlenW(szKey);
938 
939     for (;;)
940     {
941         pchStart = SkipSpace(pchStart);
942         if (*pchStart == UNICODE_NULL || *pchStart == L'[')
943             break;
944 
945         pch = wcschr(pchStart, L'\n');
946         if (pch)
947             *pch = UNICODE_NULL;
948 
949         pchSep = SkipQuoted(pchStart);
950         if (*pchSep == L'=')
951         {
952             *pchSep = UNICODE_NULL;
953 
954             STRING key = pchStart;
955             trim(key);
956             key = Unquote(key);
957 
958             STRING value = pchSep + 1;
959             trim(value);
960             value = Unquote(value);
961 
962             BYTE CharSet1 = DEFAULT_CHARSET, CharSet2 = DEFAULT_CHARSET;
963 
964             size_t pos;
965             pos = key.find(L',');
966             if (pos != STRING::npos)
967             {
968                 CharSet1 = (BYTE)_wtoi(&key[pos + 1]);
969                 key.resize(pos);
970                 trim(key);
971             }
972             pos = value.find(L',');
973             if (pos != STRING::npos)
974             {
975                 CharSet2 = (BYTE)_wtoi(&value[pos + 1]);
976                 value.resize(pos);
977                 trim(value);
978             }
979 
980             ITEM Item(key, value, CharSet1, CharSet2);
981             Items.push_back(Item);
982         }
983 
984         if (pch == NULL)
985             break;
986 
987         pchStart = pch + 1;
988     }
989 
990     g_Items = Items;
991     g_bModified = TRUE;
992 
993     LV_AddItems(g_hListView);
994     return TRUE;
995 }
996 
997 BOOL DoImport(HWND hwnd, LPCWSTR pszFile)
998 {
999     HANDLE hFile = CreateFileW(pszFile, GENERIC_READ,
1000         FILE_SHARE_READ | FILE_SHARE_DELETE, NULL,
1001         OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
1002     if (hFile == INVALID_HANDLE_VALUE)
1003         return FALSE;
1004 
1005     BOOL bSuccess = FALSE;
1006     DWORD dwSize = GetFileSize(hFile, NULL);
1007     if (dwSize != 0xFFFFFFFF)
1008     {
1009         std::vector<BYTE> Contents(dwSize + 2);
1010         DWORD cbRead;
1011         if (ReadFile(hFile, &Contents[0], dwSize, &cbRead, NULL) &&
1012             cbRead == dwSize)
1013         {
1014             /* check BOM */
1015             if (memcmp(&Contents[0], "\xFF\xFE", 2) == 0)
1016             {
1017                 bSuccess = DoParseFile(&Contents[2], dwSize - 2);
1018             }
1019             else
1020             {
1021                 bSuccess = DoParseFile(&Contents[0], dwSize);
1022             }
1023         }
1024     }
1025     CloseHandle(hFile);
1026 
1027     return bSuccess;
1028 }
1029 
1030 STRING Escape(const STRING& str)
1031 {
1032     STRING Ret;
1033     for (size_t i = 0; i < str.size(); ++i)
1034     {
1035         switch (str[i])
1036         {
1037             case L'"': case L'\\':
1038                 Ret += L'\\';
1039                 Ret += str[i];
1040                 break;
1041             default:
1042                 Ret += str[i];
1043         }
1044     }
1045     return Ret;
1046 }
1047 
1048 BOOL DoExport(HWND hwnd, LPCWSTR pszFile)
1049 {
1050     HANDLE hFile = CreateFileW(pszFile, GENERIC_WRITE, FILE_SHARE_READ,
1051         NULL, CREATE_ALWAYS,
1052         FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, NULL);
1053     if (hFile == INVALID_HANDLE_VALUE)
1054         return FALSE;
1055 
1056     BOOL bSuccess;
1057     DWORD dwSize, cbWritten;
1058     WCHAR szCharSet1[MAX_STRING], szCharSet2[MAX_STRING];
1059     WCHAR szLine[MAX_STRING * 2 + 4];
1060 
1061     /* write header */
1062     dwSize = lstrlenW(g_pszFileHeader) * sizeof(WCHAR);
1063     bSuccess =
1064         WriteFile(hFile, "\xFF\xFE", 2, &cbWritten, NULL) &&
1065         WriteFile(hFile, g_pszFileHeader, dwSize, &cbWritten, NULL);
1066     if (bSuccess)
1067     {
1068         wsprintfW(szLine, L"\r\n\r\n[HKEY_LOCAL_MACHINE\\%s]\r\n", g_pszKey);
1069         dwSize = lstrlenW(szLine) * sizeof(WCHAR);
1070         bSuccess = WriteFile(hFile, szLine, dwSize, &cbWritten, NULL);
1071     }
1072     if (bSuccess)
1073     {
1074         size_t i, Count = g_Items.size();
1075         for (i = 0; i < Count; ++i)
1076         {
1077             if (g_Items[i].m_CharSet1 != DEFAULT_CHARSET)
1078                 wsprintfW(szCharSet1, L",%u", g_Items[i].m_CharSet1);
1079             else
1080                 szCharSet1[0] = UNICODE_NULL;
1081 
1082             if (g_Items[i].m_CharSet2 != DEFAULT_CHARSET)
1083                 wsprintfW(szCharSet2, L",%u", g_Items[i].m_CharSet2);
1084             else
1085                 szCharSet2[0] = UNICODE_NULL;
1086 
1087             STRING Name = Escape(g_Items[i].m_Name);
1088             STRING Substitute = Escape(g_Items[i].m_Substitute);
1089             wsprintfW(szLine, L"\"%s%s\"=\"%s%s\"\r\n",
1090                       Name.c_str(), szCharSet1,
1091                       Substitute.c_str(), szCharSet2);
1092 
1093             dwSize = lstrlenW(szLine) * sizeof(WCHAR);
1094             if (!WriteFile(hFile, szLine, dwSize, &cbWritten, NULL))
1095             {
1096                 bSuccess = FALSE;
1097                 break;
1098             }
1099         }
1100         WriteFile(hFile, L"\r\n", 2 * sizeof(WCHAR), &cbWritten, NULL);
1101     }
1102     CloseHandle(hFile);
1103 
1104     if (!bSuccess)
1105     {
1106         DeleteFileW(pszFile);
1107     }
1108 
1109     return bSuccess;
1110 }
1111 
1112 void MakeFilter(LPWSTR pszFilter)
1113 {
1114     while (*pszFilter)
1115     {
1116         if (*pszFilter == L'|')
1117             *pszFilter = 0;
1118 
1119         ++pszFilter;
1120     }
1121 }
1122 
1123 void MainWnd_OnImport(HWND hwnd)
1124 {
1125     OPENFILENAMEW ofn = {0};
1126     WCHAR szFile[MAX_PATH] = L"";
1127     WCHAR szImportTitle[MAX_STRING];
1128     WCHAR szCannotImport[MAX_STRING];
1129     WCHAR szImportFilter[MAX_STRING];
1130     LoadStringW(g_hInstance, IDS_IMPORT, szImportTitle, _countof(szImportTitle));
1131     LoadStringW(g_hInstance, IDS_CANTIMPORT, szCannotImport, _countof(szCannotImport));
1132     LoadStringW(g_hInstance, IDS_INPFILTER, szImportFilter, _countof(szImportFilter));
1133     MakeFilter(szImportFilter);
1134 
1135     ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
1136     ofn.hwndOwner = hwnd;
1137     ofn.lpstrFilter = szImportFilter;
1138     ofn.lpstrFile = szFile;
1139     ofn.nMaxFile = _countof(szFile);
1140     ofn.lpstrTitle = szImportTitle;
1141     ofn.Flags = OFN_DONTADDTORECENT | OFN_ENABLESIZING |
1142                 OFN_EXPLORER | OFN_FILEMUSTEXIST |
1143                 OFN_HIDEREADONLY | OFN_LONGNAMES |
1144                 OFN_PATHMUSTEXIST;
1145     ofn.lpstrDefExt = L"reg";
1146     if (GetOpenFileNameW(&ofn))
1147     {
1148         if (!DoImport(hwnd, szFile))
1149         {
1150             MessageBoxW(hwnd, szCannotImport, g_szTitle, MB_ICONERROR);
1151         }
1152     }
1153 }
1154 
1155 void MainWnd_OnExport(HWND hwnd)
1156 {
1157     OPENFILENAMEW ofn = {0};
1158     WCHAR szFile[MAX_PATH] = L"";
1159     WCHAR szExportTitle[MAX_STRING];
1160     WCHAR szCannotExport[MAX_STRING];
1161     WCHAR szExportFilter[MAX_STRING];
1162     LoadStringW(g_hInstance, IDS_EXPORT, szExportTitle, _countof(szExportTitle));
1163     LoadStringW(g_hInstance, IDS_CANTEXPORT, szCannotExport, _countof(szCannotExport));
1164     LoadStringW(g_hInstance, IDS_OUTFILTER, szExportFilter, _countof(szExportFilter));
1165     MakeFilter(szExportFilter);
1166 
1167     ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
1168     ofn.hwndOwner = hwnd;
1169     ofn.lpstrFilter = szExportFilter;
1170     ofn.lpstrFile = szFile;
1171     ofn.nMaxFile = _countof(szFile);
1172     ofn.lpstrTitle = szExportTitle;
1173     ofn.Flags = OFN_DONTADDTORECENT | OFN_ENABLESIZING |
1174                 OFN_EXPLORER | OFN_HIDEREADONLY | OFN_LONGNAMES |
1175                 OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT;
1176     ofn.lpstrDefExt = L"reg";
1177     if (GetSaveFileNameW(&ofn))
1178     {
1179         if (!DoExport(hwnd, szFile))
1180         {
1181             MessageBoxW(hwnd, szCannotExport, g_szTitle, MB_ICONERROR);
1182         }
1183     }
1184 }
1185 
1186 void MainWnd_OnReload(HWND hwnd)
1187 {
1188     DoLoad();
1189 }
1190 
1191 void MainWnd_OnEdit(HWND hwnd)
1192 {
1193     LV_OnDblClk(g_hListView);
1194 }
1195 
1196 void MainWnd_OnDelete(HWND hwnd)
1197 {
1198     LV_OnDelete(g_hListView);
1199 }
1200 
1201 void MainWnd_OnOpenRegKey(HWND hwnd)
1202 {
1203     static const WCHAR s_szRegeditKey[] =
1204         L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Applets\\Regedit";
1205     WCHAR sz[MAX_STRING];
1206 
1207     // open regedit key
1208     HKEY hKey = NULL;
1209     LSTATUS Result = RegCreateKeyExW(HKEY_CURRENT_USER, s_szRegeditKey, 0,
1210                                      NULL, 0, KEY_WRITE, NULL, &hKey, NULL);
1211     if (Result != ERROR_SUCCESS)
1212     {
1213         LoadStringW(g_hInstance, IDS_CANTOPENKEY, sz, _countof(sz));
1214         MessageBoxW(hwnd, sz, NULL, MB_ICONERROR);
1215         return;
1216     }
1217 
1218     // set LastKey value
1219     wsprintfW(sz, L"HKEY_LOCAL_MACHINE\\%s", g_pszKey);
1220     DWORD dwSize = sizeof(sz);
1221     Result = RegSetValueExW(hKey, L"LastKey", 0, REG_SZ,
1222                                  (LPBYTE)sz, dwSize);
1223 
1224     // close now
1225     RegCloseKey(hKey);
1226 
1227     if (Result != ERROR_SUCCESS)
1228     {
1229         LoadStringW(g_hInstance, IDS_CANTOPENKEY, sz, _countof(sz));
1230         MessageBoxW(hwnd, sz, NULL, MB_ICONERROR);
1231         return;
1232     }
1233 
1234     // open by regedit
1235     ShellExecuteW(hwnd, NULL, L"regedit.exe", NULL, NULL, SW_SHOWNORMAL);
1236 }
1237 
1238 void MainWnd_OnAbout(HWND hwnd)
1239 {
1240     WCHAR szAbout[MAX_PATH];
1241     LoadStringW(g_hInstance, IDS_ABOUT, szAbout, _countof(szAbout));
1242 
1243     MSGBOXPARAMS Params;
1244     ZeroMemory(&Params, sizeof(Params));
1245     Params.cbSize = sizeof(Params);
1246     Params.hwndOwner = hwnd;
1247     Params.hInstance = g_hInstance;
1248     Params.lpszText = szAbout;
1249     Params.lpszCaption = g_szTitle;
1250     Params.dwStyle = MB_OK | MB_USERICON;
1251     Params.lpszIcon = MAKEINTRESOURCEW(1);
1252     Params.dwLanguageId = LANG_USER_DEFAULT;
1253     MessageBoxIndirectW(&Params);
1254 }
1255 
1256 void MainWnd_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
1257 {
1258     switch (id)
1259     {
1260         case ID_NEW:
1261             MainWnd_OnNew(hwnd);
1262             break;
1263         case ID_EDIT:
1264             MainWnd_OnEdit(hwnd);
1265             break;
1266         case ID_EXIT:
1267             PostMessage(hwnd, WM_CLOSE, 0, 0);
1268             break;
1269         case ID_RELOAD:
1270             MainWnd_OnReload(hwnd);
1271             break;
1272         case ID_UPDATE_REGISTRY:
1273             MainWnd_OnUpdateRegistry(hwnd);
1274             break;
1275         case ID_DELETE:
1276             MainWnd_OnDelete(hwnd);
1277             break;
1278         case ID_IMPORT:
1279             MainWnd_OnImport(hwnd);
1280             break;
1281         case ID_EXPORT:
1282             MainWnd_OnExport(hwnd);
1283             break;
1284         case ID_OPEN_REGKEY:
1285             MainWnd_OnOpenRegKey(hwnd);
1286             break;
1287         case ID_ABOUT:
1288             MainWnd_OnAbout(hwnd);
1289             break;
1290     }
1291 }
1292 
1293 void MainWnd_OnDestroy(HWND hwnd)
1294 {
1295     PostQuitMessage(0);
1296 }
1297 
1298 void MainWnd_OnSize(HWND hwnd, UINT state, int cx, int cy)
1299 {
1300     MoveWindow(g_hListView, 0, 0, cx, cy, TRUE);
1301 }
1302 
1303 void MainWnd_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT *lpDrawItem)
1304 {
1305     if (lpDrawItem->CtlType != ODT_LISTVIEW)
1306         return;
1307 
1308     HDC hDC = lpDrawItem->hDC;
1309     SetBkMode(hDC, TRANSPARENT);
1310 
1311     INT iColumn = 0, x, cx;
1312     RECT rcItem, rcSubItem, rcText;
1313     STRING Str;
1314 
1315     x = -GetScrollPos(g_hListView, SB_HORZ);
1316 
1317     rcItem = lpDrawItem->rcItem;
1318     if (lpDrawItem->itemState & ODS_SELECTED)
1319     {
1320         FillRect(hDC, &rcItem, (HBRUSH)(COLOR_HIGHLIGHT + 1));
1321         SetTextColor(hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
1322     }
1323     else
1324     {
1325         FillRect(hDC, &rcItem, (HBRUSH)(COLOR_WINDOW + 1));
1326         SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
1327     }
1328 
1329     cx = ListView_GetColumnWidth(g_hListView, iColumn);
1330     rcSubItem = rcItem;
1331     rcSubItem.left = x;
1332     rcSubItem.right = x + cx;
1333 
1334     WCHAR sz[MAX_STRING];
1335 
1336     rcText = rcSubItem;
1337     InflateRect(&rcText, -1, -1);
1338     Str = g_Items[lpDrawItem->itemID].m_Name;
1339     BYTE CharSet1 = g_Items[lpDrawItem->itemID].m_CharSet1;
1340     if (CharSet1 != DEFAULT_CHARSET)
1341         wsprintfW(sz, L"%s,%u", Str.c_str(), CharSet1);
1342     else
1343         wsprintfW(sz, L"%s", Str.c_str());
1344 
1345     DrawTextW(hDC, sz, lstrlenW(sz), &rcText,
1346               DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS |
1347               DT_NOPREFIX);
1348 
1349     x += cx;
1350     ++iColumn;
1351 
1352     cx = ListView_GetColumnWidth(g_hListView, iColumn);
1353     rcSubItem = rcItem;
1354     rcSubItem.left = x;
1355     rcSubItem.right = x + cx;
1356 
1357     rcText = rcSubItem;
1358     InflateRect(&rcText, -1, -1);
1359     Str = g_Items[lpDrawItem->itemID].m_Substitute;
1360     BYTE CharSet2 = g_Items[lpDrawItem->itemID].m_CharSet2;
1361     if (CharSet2 != DEFAULT_CHARSET)
1362         wsprintfW(sz, L"%s,%u", Str.c_str(), CharSet2);
1363     else
1364         wsprintfW(sz, L"%s", Str.c_str());
1365 
1366     DrawTextW(hDC, sz, lstrlenW(sz), &rcText,
1367               DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS |
1368               DT_NOPREFIX);
1369 }
1370 
1371 void MainWnd_OnMeasureItem(HWND hwnd, MEASUREITEMSTRUCT *lpMeasureItem)
1372 {
1373     if (lpMeasureItem->CtlType != ODT_LISTVIEW)
1374         return;
1375 
1376     TEXTMETRIC tm;
1377     HDC hDC = GetDC(hwnd);
1378     GetTextMetrics(hDC, &tm);
1379     ReleaseDC(hwnd, hDC);
1380 
1381     lpMeasureItem->itemHeight = tm.tmHeight * 4 / 3;
1382 }
1383 
1384 LRESULT MainWnd_OnNotify(HWND hwnd, int idFrom, NMHDR *pnmhdr)
1385 {
1386     NM_LISTVIEW *pNMLV = (NM_LISTVIEW *)pnmhdr;
1387     LV_KEYDOWN *pLVKD = (LV_KEYDOWN *)pnmhdr;
1388 
1389     switch (pnmhdr->code)
1390     {
1391         case LVN_COLUMNCLICK:
1392             if (pNMLV->iSubItem == g_iSortColumn)
1393                 DoSort(pNMLV->iSubItem, !g_bSortAscendant);
1394             else
1395                 DoSort(pNMLV->iSubItem, TRUE);
1396             break;
1397         case NM_DBLCLK:
1398             LV_OnDblClk(g_hListView);
1399             break;
1400         case LVN_KEYDOWN:
1401             if (pLVKD->wVKey == VK_RETURN)  // [Enter] key
1402             {
1403                 LV_OnDblClk(g_hListView);
1404             }
1405             if (pLVKD->wVKey == VK_DELETE)  // [Del] key
1406             {
1407                 LV_OnDelete(g_hListView);
1408             }
1409             break;
1410     }
1411     return 0;
1412 }
1413 
1414 LRESULT MainWnd_OnContextMenu(HWND hwnd, HWND hwndContext, UINT xPos, UINT yPos)
1415 {
1416     POINT pt = {(INT)xPos, (INT)yPos};
1417     ScreenToClient(g_hListView, &pt);
1418     SendMessageW(g_hListView, WM_LBUTTONDOWN, 0, MAKELPARAM(pt.x, pt.y));
1419 
1420     HMENU hMenu = LoadMenu(g_hInstance, MAKEINTRESOURCE(2));
1421     if (hMenu == NULL)
1422         return 0;
1423 
1424     HMENU hSubMenu = GetSubMenu(hMenu, 0);
1425     if (hSubMenu == NULL)
1426         return 0;
1427 
1428     SetForegroundWindow(hwnd);
1429     TrackPopupMenu(hSubMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON,
1430                    xPos, yPos, 0, g_hMainWnd, NULL);
1431     PostMessage(g_hMainWnd, WM_NULL, 0, 0);
1432     return 0;
1433 }
1434 
1435 void MainWnd_OnActivate(HWND hwnd, UINT state, HWND hwndActDeact, BOOL fMinimized)
1436 {
1437     if (state != WA_INACTIVE)
1438     {
1439         SetFocus(g_hListView);
1440     }
1441 }
1442 
1443 BOOL EnableProcessPrivileges(LPCWSTR lpPrivilegeName, BOOL bEnable = TRUE)
1444 {
1445     HANDLE hToken;
1446     LUID luid;
1447     TOKEN_PRIVILEGES tokenPrivileges;
1448     BOOL Ret;
1449 
1450     Ret = ::OpenProcessToken(::GetCurrentProcess(),
1451                              TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
1452                              &hToken);
1453     if (!Ret)
1454         return Ret;     // failure
1455 
1456     Ret = ::LookupPrivilegeValueW(NULL, lpPrivilegeName, &luid);
1457     if (Ret)
1458     {
1459         tokenPrivileges.PrivilegeCount = 1;
1460         tokenPrivileges.Privileges[0].Luid = luid;
1461         tokenPrivileges.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
1462 
1463         Ret = ::AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, 0, 0);
1464     }
1465 
1466     ::CloseHandle(hToken);
1467     return Ret;
1468 }
1469 
1470 void MainWnd_OnClose(HWND hwnd)
1471 {
1472     if (!g_bNeedsReboot && !g_bModified)
1473     {
1474         DestroyWindow(hwnd);
1475         return;
1476     }
1477 
1478     if (g_bModified)
1479     {
1480         WCHAR szUpdateNow[MAX_STRING];
1481         LoadStringW(g_hInstance, IDS_QUERYUPDATE, szUpdateNow, _countof(szUpdateNow));
1482         INT id = MessageBoxW(hwnd, szUpdateNow, g_szTitle,
1483                              MB_ICONINFORMATION | MB_YESNOCANCEL);
1484         switch (id)
1485         {
1486         case IDYES:
1487             MainWnd_OnUpdateRegistry(hwnd);
1488             break;
1489         case IDNO:
1490             break;
1491         case IDCANCEL:
1492             return;
1493         }
1494     }
1495 
1496     if (g_bNeedsReboot)
1497     {
1498         WCHAR szRebootNow[MAX_STRING];
1499         LoadStringW(g_hInstance, IDS_REBOOTNOW, szRebootNow, _countof(szRebootNow));
1500         INT id = MessageBoxW(hwnd, szRebootNow, g_szTitle,
1501                              MB_ICONINFORMATION | MB_YESNOCANCEL);
1502         switch (id)
1503         {
1504         case IDYES:
1505             EnableProcessPrivileges(SE_SHUTDOWN_NAME, TRUE);
1506             ::ExitWindowsEx(EWX_REBOOT | EWX_FORCE, 0);
1507             break;
1508         case IDNO:
1509             break;
1510         case IDCANCEL:
1511             return;
1512         }
1513     }
1514 
1515     ::DestroyWindow(hwnd);
1516 }
1517 
1518 LRESULT CALLBACK
1519 WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1520 {
1521     switch (uMsg)
1522     {
1523         HANDLE_MSG(hwnd, WM_CREATE, MainWnd_OnCreate);
1524         HANDLE_MSG(hwnd, WM_COMMAND, MainWnd_OnCommand);
1525         HANDLE_MSG(hwnd, WM_DESTROY, MainWnd_OnDestroy);
1526         HANDLE_MSG(hwnd, WM_SIZE, MainWnd_OnSize);
1527         HANDLE_MSG(hwnd, WM_DRAWITEM, MainWnd_OnDrawItem);
1528         HANDLE_MSG(hwnd, WM_MEASUREITEM, MainWnd_OnMeasureItem);
1529         HANDLE_MSG(hwnd, WM_NOTIFY, MainWnd_OnNotify);
1530         HANDLE_MSG(hwnd, WM_CONTEXTMENU, MainWnd_OnContextMenu);
1531         HANDLE_MSG(hwnd, WM_ACTIVATE, MainWnd_OnActivate);
1532         HANDLE_MSG(hwnd, WM_CLOSE, MainWnd_OnClose);
1533         default:
1534             return DefWindowProc(hwnd, uMsg, wParam, lParam);
1535     }
1536 }
1537 
1538 INT WINAPI wWinMain(
1539     HINSTANCE   hInstance,
1540     HINSTANCE   hPrevInstance,
1541     LPWSTR       lpCmdLine,
1542     INT         nCmdShow)
1543 {
1544     g_hInstance = hInstance;
1545     InitCommonControls();
1546 
1547     HACCEL hAccel = LoadAcceleratorsW(hInstance, MAKEINTRESOURCEW(1));
1548 
1549     LoadStringW(hInstance, IDS_TITLE, g_szTitle, _countof(g_szTitle));
1550     LoadStringW(hInstance, IDS_FONTNAME, g_szNameHead, _countof(g_szNameHead));
1551     LoadStringW(hInstance, IDS_SUBSTITUTE, g_szSubstituteHead, _countof(g_szSubstituteHead));
1552 
1553     WNDCLASSW wc = {0};
1554     wc.style = 0;
1555     wc.lpfnWndProc = WindowProc;
1556     wc.hInstance = hInstance;
1557     g_hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(1));
1558     wc.hIcon = g_hIcon;
1559     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
1560     wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
1561     wc.lpszMenuName = MAKEINTRESOURCEW(1);
1562     wc.lpszClassName = g_pszClassName;
1563     if (!RegisterClassW(&wc))
1564     {
1565         MessageBoxA(NULL, "ERROR: RegisterClass failed.", NULL, MB_ICONERROR);
1566         return 1;
1567     }
1568 
1569     const DWORD dwStyle = WS_OVERLAPPEDWINDOW;
1570     INT Width = NAME_COLUMN_WIDTH + SUB_COLUMN_WIDTH +
1571                 GetSystemMetrics(SM_CXVSCROLL) +
1572                 GetSystemMetrics(SM_CXSIZEFRAME);
1573     INT Height = 320;
1574 
1575     RECT Rect = { 0, 0, Width, Height };
1576     AdjustWindowRect(&Rect, dwStyle, TRUE);
1577     Width = Rect.right - Rect.left;
1578     Height = Rect.bottom - Rect.top;
1579 
1580     g_hMainWnd = CreateWindowW(g_pszClassName, g_szTitle, dwStyle,
1581         CW_USEDEFAULT, CW_USEDEFAULT, Width, Height,
1582         NULL, NULL, hInstance, NULL);
1583     if (g_hMainWnd == NULL)
1584     {
1585         MessageBoxA(NULL, "ERROR: CreateWindow failed.", NULL, MB_ICONERROR);
1586         return 2;
1587     }
1588 
1589     ShowWindow(g_hMainWnd, nCmdShow);
1590     UpdateWindow(g_hMainWnd);
1591 
1592     MSG msg;
1593     while (GetMessage(&msg, NULL, 0, 0))
1594     {
1595         if (TranslateAccelerator(g_hMainWnd, hAccel, &msg))
1596             continue;
1597 
1598         TranslateMessage(&msg);
1599         DispatchMessage(&msg);
1600     }
1601 
1602     return (INT)msg.wParam;
1603 }
1604