1 /*
2 * 'File Types' tab property sheet of Folder Options
3 *
4 * Copyright 2007 Johannes Anderwald <johannes.anderwald@reactos.org>
5 * Copyright 2016-2018 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 */
21
22 #include "precomp.h"
23 #include <atlpath.h>
24
25 WINE_DEFAULT_DEBUG_CHANNEL (fprop);
26
27 // rundll32.exe shell32.dll,Options_RunDLL 0
28
29 /////////////////////////////////////////////////////////////////////////////
30
31 EXTERN_C BOOL PathIsExeW(LPCWSTR lpszPath);
32
33 #define FTA_MODIFYMASK (FTA_OpenIsSafe) // Bits modified by EditTypeDlg
34 #define NOASSOCRESID IDI_SHELL_DOCUMENT
35 #define SUPPORT_EXTENSIONWITHOUTPROGID 1 // NT5 does not support these but NT6 does
36
37 #define ASSOC_CCHMAX (32 + 1) // Extension or protocol (INTERNET_MAX_SCHEME_LENGTH)
38 #define TYPENAME_CCHMAX max(100, RTL_FIELD_SIZE(SHFILEINFOA, szTypeName))
39 #define ICONLOCATION_CCHMAX (MAX_PATH + 1 + 11)
40
41 typedef struct _FILE_TYPE_ENTRY
42 {
43 WCHAR FileExtension[ASSOC_CCHMAX];
44 WCHAR FileDescription[TYPENAME_CCHMAX];
45 WCHAR ClassKey[MAX_PATH];
46 DWORD EditFlags;
47 WCHAR AppName[64];
48 HICON hIconSmall;
49 WCHAR ProgramPath[MAX_PATH];
50 WCHAR IconPath[MAX_PATH];
51 INT nIconIndex;
52
IsExtension_FILE_TYPE_ENTRY53 bool IsExtension() const
54 {
55 return FileExtension[0] == '.';
56 }
GetAssocForDisplay_FILE_TYPE_ENTRY57 LPCWSTR GetAssocForDisplay() const
58 {
59 return FileExtension + (FileExtension[0] == '.');
60 }
InvalidateTypeName_FILE_TYPE_ENTRY61 void InvalidateTypeName()
62 {
63 FileDescription[0] = FileDescription[1] = UNICODE_NULL;
64 }
InvalidateDefaultApp_FILE_TYPE_ENTRY65 void InvalidateDefaultApp()
66 {
67 ProgramPath[0] = ProgramPath[1] = AppName[0] = AppName[1] = UNICODE_NULL;
68 }
Initialize_FILE_TYPE_ENTRY69 void Initialize()
70 {
71 ClassKey[0] = UNICODE_NULL;
72 IconPath[0] = UNICODE_NULL;
73 nIconIndex = 0;
74 InvalidateTypeName();
75 InvalidateDefaultApp();
76 }
DestroyIcons_FILE_TYPE_ENTRY77 void DestroyIcons()
78 {
79 if (hIconSmall)
80 DestroyIcon(hIconSmall);
81 hIconSmall = NULL;
82 }
83 } FILE_TYPE_ENTRY, *PFILE_TYPE_ENTRY;
84
85 typedef struct _FILE_TYPE_GLOBALS
86 {
87 HIMAGELIST himlSmall;
88 UINT IconSize;
89 HICON hDefExtIconSmall;
90 HBITMAP hOpenWithImage;
91 HANDLE hHeap;
92 WCHAR NoneString[42];
93 INT8 SortCol, SortReverse;
94 UINT Restricted;
95 } FILE_TYPE_GLOBALS, *PFILE_TYPE_GLOBALS;
96
97 static DWORD
GetRegDWORD(HKEY hKey,LPCWSTR Name,DWORD & Value,DWORD DefaultValue=0,BOOL Strict=FALSE)98 GetRegDWORD(HKEY hKey, LPCWSTR Name, DWORD &Value, DWORD DefaultValue = 0, BOOL Strict = FALSE)
99 {
100 DWORD cb = sizeof(DWORD), type;
101 LRESULT ec = RegQueryValueExW(hKey, Name, 0, &type, (BYTE*)&Value, &cb);
102 if (ec == ERROR_SUCCESS)
103 {
104 if ((type == REG_DWORD && cb == sizeof(DWORD)) ||
105 (!Strict && type == REG_BINARY && (cb && cb <= sizeof(DWORD))))
106 {
107 Value &= (0xffffffffUL >> (32 - cb * 8));
108 return ec;
109 }
110 }
111 Value = DefaultValue;
112 return ec ? ec : ERROR_BAD_FORMAT;
113 }
114
115 static DWORD
GetRegDWORD(HKEY hKey,LPCWSTR Name,DWORD DefaultValue=0)116 GetRegDWORD(HKEY hKey, LPCWSTR Name, DWORD DefaultValue = 0)
117 {
118 GetRegDWORD(hKey, Name, DefaultValue, DefaultValue, FALSE);
119 return DefaultValue;
120 }
121
122 static HRESULT
GetClassKey(const FILE_TYPE_ENTRY & FTE,LPCWSTR & SubKey)123 GetClassKey(const FILE_TYPE_ENTRY &FTE, LPCWSTR &SubKey)
124 {
125 HRESULT hr = S_OK;
126 LPCWSTR path = FTE.IsExtension() ? FTE.ClassKey : FTE.FileExtension;
127 #if SUPPORT_EXTENSIONWITHOUTPROGID
128 if (!*path && FTE.IsExtension())
129 {
130 path = FTE.FileExtension;
131 hr = S_FALSE;
132 }
133 #endif
134 ASSERT(*path);
135 SubKey = path;
136 return hr;
137 }
138
139 static void
QuoteAppPathForCommand(CStringW & path)140 QuoteAppPathForCommand(CStringW &path)
141 {
142 if (path.Find(' ') >= 0 && path.Find('\"') < 0)
143 path = CStringW(L"\"") + path + L"\"";
144 }
145
146 static BOOL
DeleteExt(HWND hwndDlg,LPCWSTR pszExt)147 DeleteExt(HWND hwndDlg, LPCWSTR pszExt)
148 {
149 if (*pszExt != L'.')
150 return FALSE;
151
152 // open ".ext" key
153 HKEY hKey;
154 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, pszExt, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
155 return FALSE;
156
157 // query "extfile" key name
158 WCHAR ProgId[MAX_PATH] = { 0 };
159 DWORD cb = sizeof(ProgId);
160 RegQueryValueExW(hKey, NULL, NULL, NULL, LPBYTE(ProgId), &cb);
161 RegCloseKey(hKey);
162
163 // FIXME: Should verify that no other extensions are using this ProgId
164 // delete "extfile" key (if any)
165 if (ProgId[0])
166 SHDeleteKeyW(HKEY_CLASSES_ROOT, ProgId);
167
168 // delete ".ext" key
169 BOOL ret = (SHDeleteKeyW(HKEY_CLASSES_ROOT, pszExt) == ERROR_SUCCESS);
170
171 // notify
172 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSHNOWAIT, NULL, NULL);
173
174 return ret;
175 }
176
177 static inline HICON
DoExtractIcon(LPCWSTR IconPath,INT iIndex,UINT cx,UINT cy)178 DoExtractIcon(LPCWSTR IconPath, INT iIndex, UINT cx, UINT cy)
179 {
180 return SHELL32_SHExtractIcon(IconPath, iIndex, cx, cy);
181 }
182
183 static HICON
DoExtractIcon(LPCWSTR IconPath,INT iIndex=0,BOOL bSmall=FALSE)184 DoExtractIcon(LPCWSTR IconPath, INT iIndex = 0, BOOL bSmall = FALSE)
185 {
186 UINT cx = GetSystemMetrics(bSmall ? SM_CXSMICON : SM_CXICON);
187 UINT cy = GetSystemMetrics(bSmall ? SM_CYSMICON : SM_CYICON);
188 return DoExtractIcon(IconPath, iIndex, cx, cy);
189 }
190
191 static BOOL
GetFileTypeIconsEx(PFILE_TYPE_ENTRY Entry,LPCWSTR IconLocation,UINT IconSize)192 GetFileTypeIconsEx(PFILE_TYPE_ENTRY Entry, LPCWSTR IconLocation, UINT IconSize)
193 {
194 Entry->hIconSmall = NULL;
195 if (lstrcmpW(IconLocation, L"%1") == 0)
196 {
197 LPCWSTR ext = Entry->FileExtension;
198 if (!lstrcmpiW(ext, L".exe") || !lstrcmpiW(ext, L".scr"))
199 {
200 Entry->hIconSmall = HICON(LoadImageW(shell32_hInstance, MAKEINTRESOURCEW(IDI_SHELL_EXE),
201 IMAGE_ICON, IconSize, IconSize, 0));
202 Entry->nIconIndex = -IDI_SHELL_EXE;
203 }
204 // Set the icon path to %1 on purpose so PickIconDlg will issue a warning
205 StringCbCopyW(Entry->IconPath, sizeof(Entry->IconPath), IconLocation);
206 }
207 else
208 {
209 // Expand the REG_EXPAND_SZ string by environment variables
210 if (ExpandEnvironmentStringsW(IconLocation, Entry->IconPath, _countof(Entry->IconPath)))
211 {
212 Entry->nIconIndex = PathParseIconLocationW(Entry->IconPath);
213 Entry->hIconSmall = DoExtractIcon(Entry->IconPath, Entry->nIconIndex, IconSize, IconSize);
214 }
215 }
216 return Entry->hIconSmall != NULL;
217 }
218
219 static BOOL
GetFileTypeIconsByKey(HKEY hKey,PFILE_TYPE_ENTRY Entry,UINT IconSize)220 GetFileTypeIconsByKey(HKEY hKey, PFILE_TYPE_ENTRY Entry, UINT IconSize)
221 {
222 Entry->hIconSmall = NULL;
223
224 HKEY hDefIconKey;
225 LONG nResult = RegOpenKeyExW(hKey, L"DefaultIcon", 0, KEY_READ, &hDefIconKey);
226 if (nResult != ERROR_SUCCESS)
227 return FALSE;
228
229 // Get the icon location
230 WCHAR szLocation[ICONLOCATION_CCHMAX];
231 DWORD dwSize = sizeof(szLocation);
232 nResult = RegQueryValueExW(hDefIconKey, NULL, NULL, NULL, LPBYTE(szLocation), &dwSize);
233 szLocation[_countof(szLocation) - 1] = UNICODE_NULL;
234
235 RegCloseKey(hDefIconKey);
236 if (nResult != ERROR_SUCCESS || !szLocation[0])
237 return FALSE;
238
239 return GetFileTypeIconsEx(Entry, szLocation, IconSize);
240 }
241
242 static LPCWSTR
GetProgramPath(PFILE_TYPE_ENTRY Entry)243 GetProgramPath(PFILE_TYPE_ENTRY Entry)
244 {
245 if (!Entry->ProgramPath[1] && !Entry->ProgramPath[0])
246 {
247 DWORD cch = _countof(Entry->ProgramPath);
248 if (FAILED(AssocQueryStringW(ASSOCF_INIT_IGNOREUNKNOWN, ASSOCSTR_EXECUTABLE,
249 Entry->FileExtension, NULL, Entry->ProgramPath, &cch)))
250 {
251 Entry->ProgramPath[0] = UNICODE_NULL;
252 Entry->ProgramPath[1] = TRUE;
253 }
254 }
255 return Entry->ProgramPath;
256 }
257
258 static BOOL
QueryFileDescription(LPCWSTR ProgramPath,LPWSTR pszName,INT cchName)259 QueryFileDescription(LPCWSTR ProgramPath, LPWSTR pszName, INT cchName)
260 {
261 SHFILEINFOW fi;
262 fi.szDisplayName[0] = UNICODE_NULL;
263 if (SHGetFileInfoW(ProgramPath, 0, &fi, sizeof(fi), SHGFI_DISPLAYNAME))
264 {
265 StringCchCopyW(pszName, cchName, fi.szDisplayName);
266 return TRUE;
267 }
268 return !!GetFileTitleW(ProgramPath, pszName, cchName);
269 }
270
271 static LPCWSTR
GetAppName(PFILE_TYPE_ENTRY Entry)272 GetAppName(PFILE_TYPE_ENTRY Entry)
273 {
274 if (!Entry->AppName[1] && !Entry->AppName[0])
275 {
276 LPCWSTR exe = GetProgramPath(Entry);
277 if (!*exe || !QueryFileDescription(exe, Entry->AppName, _countof(Entry->AppName)))
278 {
279 Entry->AppName[0] = UNICODE_NULL;
280 Entry->AppName[1] = TRUE;
281 }
282 }
283 return Entry->AppName;
284 }
285
286 static LPWSTR
GetTypeName(PFILE_TYPE_ENTRY Entry,PFILE_TYPE_GLOBALS pG)287 GetTypeName(PFILE_TYPE_ENTRY Entry, PFILE_TYPE_GLOBALS pG)
288 {
289 if (!Entry->FileDescription[1] && !Entry->FileDescription[0])
290 {
291 Entry->FileDescription[1] = TRUE;
292 if (Entry->IsExtension())
293 {
294 SHFILEINFOW fi;
295 if (SHGetFileInfoW(Entry->FileExtension, 0, &fi, sizeof(fi), SHGFI_TYPENAME |
296 SHGFI_USEFILEATTRIBUTES) && *fi.szTypeName)
297 {
298 StringCchCopyW(Entry->FileDescription, _countof(Entry->FileDescription), fi.szTypeName);
299 }
300 }
301 else
302 {
303 // FIXME: Fix and use ASSOCSTR_FRIENDLYDOCNAME
304 DWORD cb = sizeof(Entry->FileDescription), Fallback = TRUE;
305 LPCWSTR ClassKey;
306 HRESULT hr = GetClassKey(*Entry, ClassKey);
307 HKEY hKey;
308 if (SUCCEEDED(hr) && !RegOpenKeyExW(HKEY_CLASSES_ROOT, ClassKey, 0, KEY_READ, &hKey))
309 {
310 Fallback = RegQueryValueExW(hKey, NULL, 0, NULL, (BYTE*)Entry->FileDescription, &cb) != ERROR_SUCCESS ||
311 !*Entry->FileDescription;
312 RegCloseKey(hKey);
313 }
314 if (Fallback)
315 {
316 StringCchCopyW(Entry->FileDescription, _countof(Entry->FileDescription), Entry->FileExtension);
317 }
318 }
319 }
320 return Entry->FileDescription;
321 }
322
323 static void
InitializeDefaultIcons(PFILE_TYPE_GLOBALS pG)324 InitializeDefaultIcons(PFILE_TYPE_GLOBALS pG)
325 {
326 const INT ResId = NOASSOCRESID;
327 if (!pG->hDefExtIconSmall)
328 {
329 pG->hDefExtIconSmall = HICON(LoadImageW(shell32_hInstance, MAKEINTRESOURCEW(ResId),
330 IMAGE_ICON, pG->IconSize, pG->IconSize, 0));
331 }
332
333 if (!ImageList_GetImageCount(pG->himlSmall))
334 {
335 int idx = ImageList_AddIcon(pG->himlSmall, pG->hDefExtIconSmall);
336 ASSERT(idx == 0);
337 }
338 }
339
340 static BOOL
Normalize(PFILE_TYPE_ENTRY Entry)341 Normalize(PFILE_TYPE_ENTRY Entry)
342 {
343 // We don't need this information until somebody tries to edit the entry
344 if (!Entry->IconPath[0])
345 {
346 StringCbCopyW(Entry->IconPath, sizeof(Entry->IconPath), g_pszShell32);
347 Entry->nIconIndex = NOASSOCRESID > 1 ? -NOASSOCRESID : 0;
348 }
349 return TRUE;
350 }
351
352 /////////////////////////////////////////////////////////////////////////////
353 // EditTypeDlg
354
355 #define LISTBOX_MARGIN 2
356
357 enum EDITTYPEFLAGS { ETF_ALWAYSEXT = 1 << 0, ETF_BROWSESAME = 1 << 1 };
358
359 typedef struct EDITTYPE_DIALOG
360 {
361 HWND hwndLV;
362 PFILE_TYPE_GLOBALS pG;
363 PFILE_TYPE_ENTRY pEntry;
364 CSimpleMap<CStringW, CStringW> CommandLineMap;
365 CAtlList<CStringW> ModifiedVerbs;
366 WCHAR szIconPath[MAX_PATH];
367 INT nIconIndex;
368 bool ChangedIcon;
369 WCHAR szDefaultVerb[VERBKEY_CCHMAX];
370 WCHAR TypeName[TYPENAME_CCHMAX];
371 } EDITTYPE_DIALOG, *PEDITTYPE_DIALOG;
372
373 static void
EditTypeDlg_OnChangeIcon(HWND hwndDlg,PEDITTYPE_DIALOG pEditType)374 EditTypeDlg_OnChangeIcon(HWND hwndDlg, PEDITTYPE_DIALOG pEditType)
375 {
376 WCHAR szPath[MAX_PATH];
377 INT IconIndex = pEditType->nIconIndex;
378 ExpandEnvironmentStringsW(pEditType->szIconPath, szPath, _countof(szPath));
379 if (PickIconDlg(hwndDlg, szPath, _countof(szPath), &IconIndex))
380 {
381 HICON hIconLarge = DoExtractIcon(szPath, IconIndex, FALSE);
382
383 // replace Windows directory with "%SystemRoot%" (for portability)
384 WCHAR szWinDir[MAX_PATH];
385 UINT lenWinDir = GetWindowsDirectoryW(szWinDir, _countof(szWinDir));
386 if (StrStrIW(szPath, szWinDir) == szPath)
387 {
388 CPathW str(L"%SystemRoot%");
389 str.Append(&szPath[lenWinDir]);
390 StringCbCopyW(szPath, sizeof(szPath), str);
391 }
392
393 // update EDITTYPE_DIALOG
394 StringCbCopyW(pEditType->szIconPath, sizeof(pEditType->szIconPath), szPath);
395 pEditType->nIconIndex = IconIndex;
396 pEditType->ChangedIcon = true;
397
398 // set icon to dialog
399 HICON hOld = (HICON)SendDlgItemMessageW(hwndDlg, IDC_EDITTYPE_ICON, STM_SETICON, (WPARAM)hIconLarge, 0);
400 if (hOld)
401 DestroyIcon(hOld);
402 }
403 }
404
405 static BOOL
EditTypeDlg_OnDrawItem(HWND hwndDlg,LPDRAWITEMSTRUCT pDraw,PEDITTYPE_DIALOG pEditType)406 EditTypeDlg_OnDrawItem(HWND hwndDlg, LPDRAWITEMSTRUCT pDraw, PEDITTYPE_DIALOG pEditType)
407 {
408 WCHAR szText[MAX_PATH];
409 HFONT hFont, hFont2;
410
411 if (!pDraw)
412 return FALSE;
413
414 // fill rect and set colors
415 if (pDraw->itemState & ODS_SELECTED)
416 {
417 FillRect(pDraw->hDC, &pDraw->rcItem, HBRUSH(COLOR_HIGHLIGHT + 1));
418 SetTextColor(pDraw->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
419 SetBkColor(pDraw->hDC, GetSysColor(COLOR_HIGHLIGHT));
420 }
421 else
422 {
423 FillRect(pDraw->hDC, &pDraw->rcItem, HBRUSH(COLOR_WINDOW + 1));
424 SetTextColor(pDraw->hDC, GetSysColor(COLOR_WINDOWTEXT));
425 SetBkColor(pDraw->hDC, GetSysColor(COLOR_WINDOW));
426 }
427
428 // get listbox text
429 HWND hwndListBox = GetDlgItem(hwndDlg, IDC_EDITTYPE_LISTBOX);
430 SendMessageW(hwndListBox, LB_GETTEXT, pDraw->itemID, (LPARAM)szText);
431
432 // is it default?
433 hFont = (HFONT)SendMessageW(hwndListBox, WM_GETFONT, 0, 0);
434 if (lstrcmpiW(pEditType->szDefaultVerb, szText) == 0)
435 {
436 // default. set bold
437 LOGFONTW lf;
438 GetObject(hFont, sizeof(lf), &lf);
439 lf.lfWeight = FW_BOLD;
440 hFont2 = CreateFontIndirectW(&lf);
441 if (hFont2)
442 {
443 HGDIOBJ hFontOld = SelectObject(pDraw->hDC, hFont2);
444 InflateRect(&pDraw->rcItem, -LISTBOX_MARGIN, -LISTBOX_MARGIN);
445 DrawTextW(pDraw->hDC, szText, -1, &pDraw->rcItem,
446 DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
447 InflateRect(&pDraw->rcItem, LISTBOX_MARGIN, LISTBOX_MARGIN);
448 SelectObject(pDraw->hDC, hFontOld);
449 DeleteObject(hFont2);
450 }
451 }
452 else
453 {
454 // non-default
455 InflateRect(&pDraw->rcItem, -LISTBOX_MARGIN, -LISTBOX_MARGIN);
456 DrawTextW(pDraw->hDC, szText, -1, &pDraw->rcItem,
457 DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
458 InflateRect(&pDraw->rcItem, LISTBOX_MARGIN, LISTBOX_MARGIN);
459 }
460
461 // draw focus rect
462 if (pDraw->itemState & ODS_FOCUS)
463 {
464 DrawFocusRect(pDraw->hDC, &pDraw->rcItem);
465 }
466 return TRUE;
467 }
468
469 static BOOL
EditTypeDlg_OnMeasureItem(HWND hwndDlg,LPMEASUREITEMSTRUCT pMeasure,PEDITTYPE_DIALOG pEditType)470 EditTypeDlg_OnMeasureItem(HWND hwndDlg, LPMEASUREITEMSTRUCT pMeasure, PEDITTYPE_DIALOG pEditType)
471 {
472 if (!pMeasure)
473 return FALSE;
474
475 HWND hwndLB = GetDlgItem(hwndDlg, IDC_EDITTYPE_LISTBOX);
476
477 HDC hDC = GetDC(hwndLB);
478 if (hDC)
479 {
480 TEXTMETRICW tm;
481 GetTextMetricsW(hDC, &tm);
482 pMeasure->itemHeight = tm.tmHeight + LISTBOX_MARGIN * 2;
483 ReleaseDC(hwndLB, hDC);
484 return TRUE;
485 }
486 return FALSE;
487 }
488
489 /////////////////////////////////////////////////////////////////////////////
490 // NewExtDlg
491
492 typedef struct NEWEXT_DIALOG
493 {
494 HWND hwndLV;
495 RECT rcDlg;
496 BOOL bAdvanced;
497 INT dy;
498 WCHAR szExt[16];
499 WCHAR szFileType[64];
500 } NEWEXT_DIALOG, *PNEWEXT_DIALOG;
501
502 static VOID
NewExtDlg_OnAdvanced(HWND hwndDlg,PNEWEXT_DIALOG pNewExt)503 NewExtDlg_OnAdvanced(HWND hwndDlg, PNEWEXT_DIALOG pNewExt)
504 {
505 // If "Advanced" button was clicked, then we shrink or expand the dialog.
506 RECT rc, rc1, rc2;
507
508 GetWindowRect(hwndDlg, &rc);
509 rc.bottom = rc.top + (pNewExt->rcDlg.bottom - pNewExt->rcDlg.top);
510
511 GetWindowRect(GetDlgItem(hwndDlg, IDOK), &rc1);
512 MapWindowPoints(NULL, hwndDlg, (LPPOINT)&rc1, 2);
513
514 GetWindowRect(GetDlgItem(hwndDlg, IDCANCEL), &rc2);
515 MapWindowPoints(NULL, hwndDlg, (LPPOINT)&rc2, 2);
516
517 HWND hClassCombo = GetDlgItem(hwndDlg, IDC_NEWEXT_COMBOBOX);
518 if (pNewExt->bAdvanced)
519 {
520 rc1.top += pNewExt->dy;
521 rc1.bottom += pNewExt->dy;
522
523 rc2.top += pNewExt->dy;
524 rc2.bottom += pNewExt->dy;
525
526 ShowWindow(GetDlgItem(hwndDlg, IDC_NEWEXT_ASSOC), SW_SHOWNOACTIVATE);
527 ShowWindow(hClassCombo, SW_SHOWNOACTIVATE);
528
529 CStringW strLeft(MAKEINTRESOURCEW(IDS_NEWEXT_ADVANCED_LEFT));
530 SetDlgItemTextW(hwndDlg, IDC_NEWEXT_ADVANCED, strLeft);
531
532 SetFocus(hClassCombo);
533 }
534 else
535 {
536 rc1.top -= pNewExt->dy;
537 rc1.bottom -= pNewExt->dy;
538
539 rc2.top -= pNewExt->dy;
540 rc2.bottom -= pNewExt->dy;
541
542 ShowWindow(GetDlgItem(hwndDlg, IDC_NEWEXT_ASSOC), SW_HIDE);
543 ShowWindow(hClassCombo, SW_HIDE);
544
545 CStringW strRight(MAKEINTRESOURCEW(IDS_NEWEXT_ADVANCED_RIGHT));
546 SetDlgItemTextW(hwndDlg, IDC_NEWEXT_ADVANCED, strRight);
547
548 rc.bottom -= pNewExt->dy;
549
550 SendMessageW(hClassCombo, CB_SETCURSEL, 0, 0); // Reset the combo to the "new class" mode
551 }
552
553 HDWP hDWP = BeginDeferWindowPos(3);
554
555 if (hDWP)
556 hDWP = DeferWindowPos(hDWP, GetDlgItem(hwndDlg, IDOK), NULL,
557 rc1.left, rc1.top, rc1.right - rc1.left, rc1.bottom - rc1.top,
558 SWP_NOACTIVATE | SWP_NOZORDER);
559 if (hDWP)
560 hDWP = DeferWindowPos(hDWP, GetDlgItem(hwndDlg, IDCANCEL), NULL,
561 rc2.left, rc2.top, rc2.right - rc2.left, rc2.bottom - rc2.top,
562 SWP_NOACTIVATE | SWP_NOZORDER);
563 if (hDWP)
564 hDWP = DeferWindowPos(hDWP, hwndDlg, NULL,
565 rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
566 SWP_NOACTIVATE | SWP_NOZORDER);
567
568 if (hDWP)
569 EndDeferWindowPos(hDWP);
570 }
571
572 static BOOL
NewExtDlg_OnInitDialog(HWND hwndDlg,PNEWEXT_DIALOG pNewExt)573 NewExtDlg_OnInitDialog(HWND hwndDlg, PNEWEXT_DIALOG pNewExt)
574 {
575 pNewExt->bAdvanced = FALSE;
576
577 // get window rectangle
578 GetWindowRect(hwndDlg, &pNewExt->rcDlg);
579
580 // get delta Y
581 RECT rc1, rc2;
582 GetWindowRect(GetDlgItem(hwndDlg, IDC_NEWEXT_EDIT), &rc1);
583 GetWindowRect(GetDlgItem(hwndDlg, IDC_NEWEXT_COMBOBOX), &rc2);
584 pNewExt->dy = rc2.top - rc1.top;
585
586 // initialize
587 CStringW strText(MAKEINTRESOURCEW(IDS_NEWEXT_NEW));
588 SendDlgItemMessageW(hwndDlg, IDC_NEWEXT_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)(LPCWSTR)strText);
589 SendDlgItemMessageW(hwndDlg, IDC_NEWEXT_COMBOBOX, CB_SETCURSEL, 0, 0);
590 SendDlgItemMessageW(hwndDlg, IDC_NEWEXT_EDIT, EM_SETLIMITTEXT, _countof(pNewExt->szExt) - 1, 0);
591
592 // shrink it first time
593 NewExtDlg_OnAdvanced(hwndDlg, pNewExt);
594
595 return TRUE;
596 }
597
598 static BOOL
NewExtDlg_OnOK(HWND hwndDlg,PNEWEXT_DIALOG pNewExt)599 NewExtDlg_OnOK(HWND hwndDlg, PNEWEXT_DIALOG pNewExt)
600 {
601 LV_FINDINFO find;
602 INT iItem;
603
604 GetDlgItemTextW(hwndDlg, IDC_NEWEXT_EDIT, pNewExt->szExt, _countof(pNewExt->szExt));
605 StrTrimW(pNewExt->szExt, g_pszSpace);
606 _wcsupr(pNewExt->szExt);
607
608 #if 0
609 // FIXME: Implement the "choose existing class" mode
610 GetDlgItemTextW(hwndDlg, IDC_NEWEXT_COMBOBOX, pNewExt->szFileType, _countof(pNewExt->szFileType));
611 StrTrimW(pNewExt->szFileType, g_pszSpace);
612 #endif
613 pNewExt->szFileType[0] = UNICODE_NULL; // "new class" mode
614
615 if (pNewExt->szExt[0] == 0)
616 {
617 CStringW strText(MAKEINTRESOURCEW(IDS_NEWEXT_SPECIFY_EXT));
618 CStringW strTitle(MAKEINTRESOURCEW(IDS_FILE_TYPES));
619 MessageBoxW(hwndDlg, strText, strTitle, MB_ICONERROR);
620 return FALSE;
621 }
622
623 ZeroMemory(&find, sizeof(find));
624 find.flags = LVFI_STRING;
625 if (pNewExt->szExt[0] == L'.')
626 {
627 find.psz = &pNewExt->szExt[1];
628 }
629 else
630 {
631 find.psz = pNewExt->szExt;
632 }
633
634 iItem = ListView_FindItem(pNewExt->hwndLV, -1, &find);
635 if (iItem >= 0)
636 {
637 // already exists
638
639 // get file type
640 WCHAR szFileType[TYPENAME_CCHMAX];
641 LV_ITEM item;
642 ZeroMemory(&item, sizeof(item));
643 item.mask = LVIF_TEXT;
644 item.pszText = szFileType;
645 item.cchTextMax = _countof(szFileType);
646 item.iItem = iItem;
647 item.iSubItem = 1;
648 ListView_GetItem(pNewExt->hwndLV, &item);
649
650 // get text
651 CStringW strText;
652 strText.Format(IDS_NEWEXT_ALREADY_ASSOC, find.psz, szFileType, find.psz, szFileType);
653
654 // get title
655 CStringW strTitle;
656 strTitle.LoadString(IDS_NEWEXT_EXT_IN_USE);
657
658 if (MessageBoxW(hwndDlg, strText, strTitle, MB_ICONWARNING | MB_YESNO) == IDNO)
659 {
660 return FALSE;
661 }
662
663 // Delete the extension
664 CStringW strExt(L".");
665 strExt += find.psz;
666 strExt.MakeLower();
667 if (DeleteExt(hwndDlg, strExt))
668 ListView_DeleteItem(pNewExt->hwndLV, iItem);
669 }
670
671 EndDialog(hwndDlg, IDOK);
672 return TRUE;
673 }
674
675 // IDD_NEWEXTENSION
676 static INT_PTR CALLBACK
NewExtDlgProc(HWND hwndDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)677 NewExtDlgProc(
678 HWND hwndDlg,
679 UINT uMsg,
680 WPARAM wParam,
681 LPARAM lParam)
682 {
683 static PNEWEXT_DIALOG s_pNewExt = NULL;
684
685 switch (uMsg)
686 {
687 case WM_INITDIALOG:
688 s_pNewExt = (PNEWEXT_DIALOG)lParam;
689 NewExtDlg_OnInitDialog(hwndDlg, s_pNewExt);
690 return TRUE;
691
692 case WM_COMMAND:
693 switch (LOWORD(wParam))
694 {
695 case IDOK:
696 NewExtDlg_OnOK(hwndDlg, s_pNewExt);
697 break;
698
699 case IDCANCEL:
700 EndDialog(hwndDlg, IDCANCEL);
701 break;
702
703 case IDC_NEWEXT_ADVANCED:
704 s_pNewExt->bAdvanced = !s_pNewExt->bAdvanced;
705 NewExtDlg_OnAdvanced(hwndDlg, s_pNewExt);
706 break;
707 }
708 break;
709 }
710 return 0;
711 }
712
713 static PFILE_TYPE_ENTRY
FileTypesDlg_InsertToLV(HWND hListView,LPCWSTR Assoc,INT iItem,PFILE_TYPE_GLOBALS pG)714 FileTypesDlg_InsertToLV(HWND hListView, LPCWSTR Assoc, INT iItem, PFILE_TYPE_GLOBALS pG)
715 {
716 PFILE_TYPE_ENTRY Entry;
717 HKEY hKey, hTemp;
718 LVITEMW lvItem;
719 DWORD dwSize;
720
721 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, Assoc, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
722 {
723 return NULL;
724 }
725 if (Assoc[0] != L'.' && !RegValueExists(hKey, L"URL Protocol"))
726 {
727 RegCloseKey(hKey);
728 return NULL;
729 }
730
731 Entry = (PFILE_TYPE_ENTRY)HeapAlloc(pG->hHeap, 0, sizeof(FILE_TYPE_ENTRY));
732 if (!Entry)
733 {
734 RegCloseKey(hKey);
735 return NULL;
736 }
737 Entry->Initialize();
738
739 if (Assoc[0] == L'.')
740 {
741 if (PathIsExeW(Assoc))
742 {
743 exclude:
744 HeapFree(pG->hHeap, 0, Entry);
745 RegCloseKey(hKey);
746 return NULL;
747 }
748
749 dwSize = sizeof(Entry->ClassKey);
750 if (RegQueryValueExW(hKey, NULL, NULL, NULL, LPBYTE(Entry->ClassKey), &dwSize))
751 {
752 Entry->ClassKey[0] = UNICODE_NULL; // No ProgId
753 }
754 #if SUPPORT_EXTENSIONWITHOUTPROGID
755 if (Entry->ClassKey[0] && !RegOpenKeyExW(HKEY_CLASSES_ROOT, Entry->ClassKey, 0, KEY_READ, &hTemp))
756 {
757 RegCloseKey(hKey);
758 hKey = hTemp;
759 }
760 #else
761 if (!Entry->ClassKey[0])
762 goto exclude;
763 #endif
764 }
765
766 Entry->EditFlags = GetRegDWORD(hKey, L"EditFlags", 0);
767 if (Entry->EditFlags & FTA_Exclude)
768 goto exclude;
769
770 wcscpy(Entry->FileExtension, Assoc);
771
772 // get icon
773 Entry->IconPath[0] = UNICODE_NULL;
774 BOOL defaultIcon = !GetFileTypeIconsByKey(hKey, Entry, pG->IconSize);
775
776 RegCloseKey(hKey);
777
778 // add icon to imagelist
779 INT iSmallImage = 0;
780 if (!defaultIcon && Entry->hIconSmall)
781 {
782 iSmallImage = ImageList_AddIcon(pG->himlSmall, Entry->hIconSmall);
783 }
784
785 lvItem.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE;
786 lvItem.iItem = iItem;
787 lvItem.iSubItem = 0;
788 lvItem.lParam = (LPARAM)Entry;
789 lvItem.iImage = iSmallImage;
790 lvItem.pszText = Assoc[0] == L'.' ? _wcsupr(&Entry->FileExtension[1]) : pG->NoneString;
791 SendMessageW(hListView, LVM_INSERTITEMW, 0, (LPARAM)&lvItem);
792
793 lvItem.mask = LVIF_TEXT;
794 lvItem.iItem = iItem;
795 lvItem.iSubItem = 1;
796 lvItem.pszText = LPSTR_TEXTCALLBACK;
797 ListView_SetItem(hListView, &lvItem);
798
799 return Entry;
800 }
801
802 static BOOL
FileTypesDlg_AddExt(HWND hwndDlg,LPCWSTR pszExt,LPCWSTR pszProgId,PFILE_TYPE_GLOBALS pG)803 FileTypesDlg_AddExt(HWND hwndDlg, LPCWSTR pszExt, LPCWSTR pszProgId, PFILE_TYPE_GLOBALS pG)
804 {
805 DWORD dwValue = 1;
806 HKEY hKey;
807 WCHAR szKey[13]; // max. "ft4294967295" + "\0"
808 LONG nResult;
809
810 if (!*pszProgId)
811 {
812 pszProgId = szKey;
813 // Search the next "ft%06u" key name
814 do
815 {
816 StringCbPrintfW(szKey, sizeof(szKey), L"ft%06u", dwValue);
817
818 nResult = RegOpenKeyEx(HKEY_CLASSES_ROOT, szKey, 0, KEY_READ, &hKey);
819 if (nResult != ERROR_SUCCESS)
820 break;
821
822 RegCloseKey(hKey);
823 ++dwValue;
824 } while (dwValue != 0);
825
826 RegCloseKey(hKey);
827
828 if (dwValue == 0)
829 return FALSE;
830
831 // Create new "ft%06u" key
832 nResult = RegCreateKeyEx(HKEY_CLASSES_ROOT, szKey, 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL);
833 if (ERROR_SUCCESS != nResult)
834 return FALSE;
835 RegCloseKey(hKey);
836 }
837
838 // Create the ".ext" key
839 WCHAR szExt[ASSOC_CCHMAX];
840 if (*pszExt == L'.') // The user is allowed to type the extension with or without the . in the new dialog!
841 ++pszExt;
842 StringCbPrintfW(szExt, sizeof(szExt), L".%s", pszExt);
843 _wcslwr(szExt);
844 nResult = RegCreateKeyEx(HKEY_CLASSES_ROOT, szExt, 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL);
845 _wcsupr(szExt);
846 if (ERROR_SUCCESS != nResult)
847 return FALSE;
848
849 // Set the default value of ".ext" to "ft%06u"
850 RegSetString(hKey, NULL, pszProgId, REG_SZ);
851
852 RegCloseKey(hKey);
853
854 // Insert an item to the listview
855 HWND hListView = GetDlgItem(hwndDlg, IDC_FILETYPES_LISTVIEW);
856 INT iItem = ListView_GetItemCount(hListView);
857 if (!FileTypesDlg_InsertToLV(hListView, szExt, iItem, pG))
858 return FALSE;
859
860 ListView_SetItemState(hListView, iItem, -1, LVIS_FOCUSED | LVIS_SELECTED);
861 ListView_EnsureVisible(hListView, iItem, FALSE);
862 return TRUE;
863 }
864
865 static BOOL
FileTypesDlg_RemoveExt(HWND hwndDlg)866 FileTypesDlg_RemoveExt(HWND hwndDlg)
867 {
868 HWND hListView = GetDlgItem(hwndDlg, IDC_FILETYPES_LISTVIEW);
869
870 INT iItem = ListView_GetNextItem(hListView, -1, LVNI_SELECTED);
871 if (iItem == -1)
872 return FALSE;
873
874 WCHAR szExt[MAX_PATH];
875 szExt[0] = L'.';
876 ListView_GetItemText(hListView, iItem, 0, &szExt[1], _countof(szExt) - 1);
877 _wcslwr(szExt);
878
879 if (DeleteExt(hwndDlg, szExt))
880 {
881 ListView_DeleteItem(hListView, iItem);
882 }
883 return TRUE;
884 }
885
886 /////////////////////////////////////////////////////////////////////////////
887 // common code of NewActionDlg and EditActionDlg
888
889 typedef struct ACTION_DIALOG
890 {
891 HWND hwndLB;
892 PFILE_TYPE_GLOBALS pG;
893 PFILE_TYPE_ENTRY pEntry;
894 WCHAR szAction[VERBKEY_CCHMAX];
895 WCHAR szApp[MAX_PATH];
896 BOOL bUseDDE;
897 } ACTION_DIALOG, *PACTION_DIALOG;
898
899 static void
ActionDlg_OnBrowse(HWND hwndDlg,PACTION_DIALOG pNewAct,BOOL bEdit=FALSE)900 ActionDlg_OnBrowse(HWND hwndDlg, PACTION_DIALOG pNewAct, BOOL bEdit = FALSE)
901 {
902 WCHAR szFile[MAX_PATH];
903 szFile[0] = UNICODE_NULL;
904
905 WCHAR szFilter[MAX_PATH];
906 LoadStringW(shell32_hInstance, IDS_EXE_FILTER, szFilter, _countof(szFilter));
907
908 CStringW strTitle(MAKEINTRESOURCEW(IDS_OPEN_WITH));
909
910 OPENFILENAMEW ofn;
911 ZeroMemory(&ofn, sizeof(ofn));
912 ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400W;
913 ofn.hwndOwner = hwndDlg;
914 ofn.lpstrFilter = szFilter;
915 ofn.lpstrFile = szFile;
916 ofn.nMaxFile = _countof(szFile);
917 ofn.lpstrTitle = strTitle;
918 ofn.Flags = OFN_EXPLORER | OFN_ENABLESIZING | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
919 ofn.lpstrDefExt = L"exe";
920 if (GetOpenFileNameW(&ofn))
921 {
922 if (bEdit)
923 {
924 CStringW str = szFile;
925 QuoteAppPathForCommand(str);
926 str += L" \"%1\"";
927 SetDlgItemTextW(hwndDlg, IDC_ACTION_APP, str);
928 }
929 else
930 {
931 SetDlgItemTextW(hwndDlg, IDC_ACTION_APP, szFile);
932 }
933 }
934 }
935
936 /////////////////////////////////////////////////////////////////////////////
937 // NewActionDlg
938
939 static void
NewActionDlg_OnOK(HWND hwndDlg,PACTION_DIALOG pNewAct)940 NewActionDlg_OnOK(HWND hwndDlg, PACTION_DIALOG pNewAct)
941 {
942 // check action
943 GetDlgItemTextW(hwndDlg, IDC_ACTION_ACTION, pNewAct->szAction, _countof(pNewAct->szAction));
944 StrTrimW(pNewAct->szAction, g_pszSpace);
945 if (pNewAct->szAction[0] == 0)
946 {
947 // action was empty, error
948 HWND hwndCtrl = GetDlgItem(hwndDlg, IDC_ACTION_ACTION);
949 SendMessageW(hwndCtrl, EM_SETSEL, 0, -1);
950 SetFocus(hwndCtrl);
951 CStringW strText(MAKEINTRESOURCEW(IDS_SPECIFY_ACTION));
952 CStringW strTitle(MAKEINTRESOURCEW(IDS_FILE_TYPES));
953 MessageBoxW(hwndDlg, strText, strTitle, MB_ICONERROR);
954 return;
955 }
956
957 // check app
958 GetDlgItemTextW(hwndDlg, IDC_ACTION_APP, pNewAct->szApp, _countof(pNewAct->szApp));
959 StrTrimW(pNewAct->szApp, g_pszSpace);
960 if (pNewAct->szApp[0] == 0 ||
961 GetFileAttributesW(pNewAct->szApp) == INVALID_FILE_ATTRIBUTES)
962 {
963 // app was empty or invalid
964 HWND hwndCtrl = GetDlgItem(hwndDlg, IDC_ACTION_APP);
965 SendMessageW(hwndCtrl, EM_SETSEL, 0, -1);
966 SetFocus(hwndCtrl);
967 CStringW strText(MAKEINTRESOURCEW(IDS_INVALID_PROGRAM));
968 CStringW strTitle(MAKEINTRESOURCEW(IDS_FILE_TYPES));
969 MessageBoxW(hwndDlg, strText, strTitle, MB_ICONERROR);
970 return;
971 }
972
973 EndDialog(hwndDlg, IDOK);
974 }
975
976 // IDD_ACTION
977 static INT_PTR CALLBACK
NewActionDlgProc(HWND hwndDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)978 NewActionDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
979 {
980 static PACTION_DIALOG s_pNewAct = NULL;
981
982 switch (uMsg)
983 {
984 case WM_INITDIALOG:
985 s_pNewAct = (PACTION_DIALOG)lParam;
986 s_pNewAct->bUseDDE = FALSE;
987 EnableWindow(GetDlgItem(hwndDlg, IDC_ACTION_USE_DDE), FALSE);
988 return TRUE;
989
990 case WM_COMMAND:
991 switch (LOWORD(wParam))
992 {
993 case IDOK:
994 NewActionDlg_OnOK(hwndDlg, s_pNewAct);
995 break;
996
997 case IDCANCEL:
998 EndDialog(hwndDlg, IDCANCEL);
999 break;
1000
1001 case IDC_ACTION_BROWSE:
1002 ActionDlg_OnBrowse(hwndDlg, s_pNewAct, FALSE);
1003 break;
1004 }
1005 break;
1006 }
1007 return 0;
1008 }
1009
1010 /////////////////////////////////////////////////////////////////////////////
1011 // EditActionDlg
1012
1013 static void
EditActionDlg_OnOK(HWND hwndDlg,PACTION_DIALOG pEditAct)1014 EditActionDlg_OnOK(HWND hwndDlg, PACTION_DIALOG pEditAct)
1015 {
1016 // check action
1017 GetDlgItemTextW(hwndDlg, IDC_ACTION_ACTION, pEditAct->szAction, _countof(pEditAct->szAction));
1018 StrTrimW(pEditAct->szAction, g_pszSpace);
1019 if (pEditAct->szAction[0] == 0)
1020 {
1021 // action was empty. show error
1022 HWND hwndCtrl = GetDlgItem(hwndDlg, IDC_ACTION_ACTION);
1023 SendMessageW(hwndCtrl, EM_SETSEL, 0, -1);
1024 SetFocus(hwndCtrl);
1025 CStringW strText(MAKEINTRESOURCEW(IDS_SPECIFY_ACTION));
1026 CStringW strTitle(MAKEINTRESOURCEW(IDS_FILE_TYPES));
1027 MessageBoxW(hwndDlg, strText, strTitle, MB_ICONERROR);
1028 }
1029
1030 // check app
1031 GetDlgItemTextW(hwndDlg, IDC_ACTION_APP, pEditAct->szApp, _countof(pEditAct->szApp));
1032 StrTrimW(pEditAct->szApp, g_pszSpace);
1033 if (pEditAct->szApp[0] == 0)
1034 {
1035 // app was empty. show error
1036 HWND hwndCtrl = GetDlgItem(hwndDlg, IDC_ACTION_APP);
1037 SendMessageW(hwndCtrl, EM_SETSEL, 0, -1);
1038 SetFocus(hwndCtrl);
1039 CStringW strText(MAKEINTRESOURCEW(IDS_INVALID_PROGRAM));
1040 CStringW strTitle(MAKEINTRESOURCEW(IDS_FILE_TYPES));
1041 MessageBoxW(hwndDlg, strText, strTitle, MB_ICONERROR);
1042 }
1043
1044 EndDialog(hwndDlg, IDOK);
1045 }
1046
1047 // IDD_ACTION
1048 static INT_PTR CALLBACK
EditActionDlgProc(HWND hwndDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)1049 EditActionDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
1050 {
1051 static PACTION_DIALOG s_pEditAct = NULL;
1052
1053 switch (uMsg)
1054 {
1055 case WM_INITDIALOG:
1056 s_pEditAct = (PACTION_DIALOG)lParam;
1057 s_pEditAct->bUseDDE = FALSE;
1058 SetDlgItemTextW(hwndDlg, IDC_ACTION_ACTION, s_pEditAct->szAction);
1059 SetDlgItemTextW(hwndDlg, IDC_ACTION_APP, s_pEditAct->szApp);
1060 EnableWindow(GetDlgItem(hwndDlg, IDC_ACTION_USE_DDE), FALSE);
1061 EnableWindow(GetDlgItem(hwndDlg, IDC_ACTION_ACTION), FALSE);
1062 {
1063 // set title
1064 CStringW str(MAKEINTRESOURCEW(IDS_EDITING_ACTION));
1065 str += GetTypeName(s_pEditAct->pEntry, s_pEditAct->pG);
1066 SetWindowTextW(hwndDlg, str);
1067 }
1068 return TRUE;
1069
1070 case WM_COMMAND:
1071 switch (LOWORD(wParam))
1072 {
1073 case IDOK:
1074 EditActionDlg_OnOK(hwndDlg, s_pEditAct);
1075 break;
1076
1077 case IDCANCEL:
1078 EndDialog(hwndDlg, IDCANCEL);
1079 break;
1080
1081 case IDC_ACTION_BROWSE:
1082 ActionDlg_OnBrowse(hwndDlg, s_pEditAct, TRUE);
1083 break;
1084 }
1085 break;
1086 }
1087 return 0;
1088 }
1089
1090 /////////////////////////////////////////////////////////////////////////////
1091 // EditTypeDlg
1092
1093 static void
EditTypeDlg_Restrict(HWND hwndDlg,PEDITTYPE_DIALOG pEditType)1094 EditTypeDlg_Restrict(HWND hwndDlg, PEDITTYPE_DIALOG pEditType)
1095 {
1096 PFILE_TYPE_ENTRY pEntry = pEditType->pEntry;
1097 static const WORD map[] = {
1098 FTA_NoEditIcon, IDC_EDITTYPE_CHANGE_ICON,
1099 FTA_NoEditDesc, IDC_EDITTYPE_TEXT,
1100 FTA_NoNewVerb, IDC_EDITTYPE_NEW,
1101 FTA_NoEditVerb, IDC_EDITTYPE_EDIT_BUTTON,
1102 FTA_NoRemoveVerb, IDC_EDITTYPE_REMOVE,
1103 FTA_NoEditDflt, IDC_EDITTYPE_SET_DEFAULT
1104 };
1105 for (SIZE_T i = 0; i < _countof(map); i += 2)
1106 {
1107 if (pEntry->EditFlags & map[i + 0])
1108 EnableWindow(GetDlgItem(hwndDlg, map[i + 1]), FALSE);
1109 }
1110 }
1111
1112 static BOOL
EditTypeDlg_UpdateEntryIcon(HWND hwndDlg,PEDITTYPE_DIALOG pEditType)1113 EditTypeDlg_UpdateEntryIcon(HWND hwndDlg, PEDITTYPE_DIALOG pEditType)
1114 {
1115 PFILE_TYPE_ENTRY pEntry = pEditType->pEntry;
1116 WCHAR buf[MAX_PATH];
1117
1118 pEntry->IconPath[0] = UNICODE_NULL; // I want the default icon
1119 Normalize(pEntry);
1120 pEntry->DestroyIcons();
1121 pEntry->hIconSmall = DoExtractIcon(pEntry->IconPath, pEntry->nIconIndex, TRUE);
1122 if (ExpandEnvironmentStringsW(pEditType->szIconPath, buf, _countof(buf)) && buf[0])
1123 {
1124 HICON hIco = DoExtractIcon(buf, pEditType->nIconIndex, TRUE);
1125 if (hIco)
1126 {
1127 pEntry->DestroyIcons();
1128 pEntry->hIconSmall = hIco;
1129 }
1130 }
1131 StringCbCopyW(pEntry->IconPath, sizeof(pEntry->IconPath), pEditType->szIconPath);
1132 pEntry->nIconIndex = pEditType->nIconIndex;
1133
1134 HWND hListView = pEditType->hwndLV;
1135 InitializeDefaultIcons(pEditType->pG);
1136 HIMAGELIST himlSmall = pEditType->pG->himlSmall;
1137 INT iSmallImage = ImageList_AddIcon(himlSmall, pEntry->hIconSmall);
1138
1139 INT iItem = ListView_GetNextItem(hListView, -1, LVNI_SELECTED);
1140 if (iItem != -1)
1141 {
1142 LV_ITEMW Item = { LVIF_IMAGE, iItem };
1143 Item.iImage = iSmallImage;
1144 ListView_SetItem(hListView, &Item);
1145 }
1146 return TRUE;
1147 }
1148
1149 static BOOL
EditTypeDlg_WriteClass(HWND hwndDlg,PEDITTYPE_DIALOG pEditType,LPCWSTR TypeName,EDITTYPEFLAGS Etf)1150 EditTypeDlg_WriteClass(HWND hwndDlg, PEDITTYPE_DIALOG pEditType,
1151 LPCWSTR TypeName, EDITTYPEFLAGS Etf)
1152 {
1153 PFILE_TYPE_ENTRY pEntry = pEditType->pEntry;
1154 LPCWSTR ClassKey;
1155 HRESULT hr = GetClassKey(*pEntry, ClassKey);
1156 BOOL OnlyExt = hr != S_OK;
1157 HKEY hClassKey;
1158 if (FAILED(hr) || RegCreateKeyExW(HKEY_CLASSES_ROOT, ClassKey, 0, NULL, 0,
1159 KEY_QUERY_VALUE | KEY_WRITE, NULL,
1160 &hClassKey, NULL) != ERROR_SUCCESS)
1161 {
1162 return FALSE;
1163 }
1164
1165 // Refresh the EditFlags
1166 DWORD dw;
1167 if (GetRegDWORD(hClassKey, L"EditFlags", dw, 0, FALSE) == ERROR_SUCCESS)
1168 pEntry->EditFlags = (dw & ~FTA_MODIFYMASK) | (pEntry->EditFlags & FTA_MODIFYMASK);
1169
1170 if (!OnlyExt)
1171 {
1172 // Set class properties
1173 RegSetOrDelete(hClassKey, L"EditFlags", REG_DWORD, pEntry->EditFlags ? &pEntry->EditFlags : NULL, 4);
1174 if (pEntry->IsExtension())
1175 {
1176 RegSetOrDelete(hClassKey, L"AlwaysShowExt", REG_SZ, (Etf & ETF_ALWAYSEXT) ? L"" : NULL, 0);
1177 }
1178 if (RegKeyExists(hClassKey, L"DocObject"))
1179 {
1180 LRESULT ec = GetRegDWORD(hClassKey, L"BrowserFlags", dw, 0, TRUE);
1181 if (ec == ERROR_SUCCESS || ec == ERROR_FILE_NOT_FOUND)
1182 {
1183 dw = (dw & ~8) | ((Etf & ETF_BROWSESAME) ? 0 : 8); // Note: 8 means NOT
1184 RegSetOrDelete(hClassKey, L"BrowserFlags", REG_DWORD, dw ? &dw : NULL, sizeof(dw));
1185 }
1186 }
1187 }
1188 if (!(pEntry->EditFlags & FTA_NoEditDesc))
1189 {
1190 if (!OnlyExt)
1191 RegDeleteValueW(hClassKey, NULL); // Legacy name (in ProgId only)
1192
1193 // Deleting this value is always the correct thing to do but Windows does not do this.
1194 // This means the user cannot override the translated known type names set by the OS.
1195 if (OnlyExt)
1196 RegDeleteValueW(hClassKey, L"FriendlyTypeName"); // MUI name (extension or ProgId)
1197
1198 if (TypeName[0])
1199 RegSetString(hClassKey, OnlyExt ? L"FriendlyTypeName" : NULL, TypeName, REG_SZ);
1200 pEntry->InvalidateTypeName();
1201 }
1202
1203 if (pEntry->IconPath[0] && !(pEntry->EditFlags & FTA_NoEditIcon) && pEditType->ChangedIcon)
1204 {
1205 HKEY hDefaultIconKey;
1206 if (RegCreateKeyExW(hClassKey, L"DefaultIcon", 0, NULL, 0, KEY_WRITE,
1207 NULL, &hDefaultIconKey, NULL) == ERROR_SUCCESS)
1208 {
1209 DWORD type = REG_SZ;
1210 WCHAR buf[ICONLOCATION_CCHMAX];
1211 LPCWSTR fmt = L"%s,%d";
1212 if (!lstrcmpW(pEntry->IconPath, L"%1"))
1213 {
1214 fmt = L"%s"; // No icon index for "%1"
1215 }
1216 else if (StrChrW(pEntry->IconPath, L'%'))
1217 {
1218 type = REG_EXPAND_SZ;
1219 }
1220 StringCbPrintfW(buf, sizeof(buf), fmt, pEntry->IconPath, pEntry->nIconIndex);
1221
1222 RegSetString(hDefaultIconKey, NULL, buf, type);
1223 RegCloseKey(hDefaultIconKey);
1224 }
1225 }
1226
1227 HKEY hShellKey;
1228 if (RegCreateKeyExW(hClassKey, L"shell", 0, NULL, 0, KEY_READ | KEY_WRITE, NULL,
1229 &hShellKey, NULL) != ERROR_SUCCESS)
1230 {
1231 RegCloseKey(hClassKey);
1232 return FALSE;
1233 }
1234
1235 // set default action
1236 if (!(pEntry->EditFlags & FTA_NoEditDflt))
1237 {
1238 if (pEditType->szDefaultVerb[0])
1239 RegSetString(hShellKey, NULL, pEditType->szDefaultVerb, REG_SZ);
1240 else
1241 RegDeleteValueW(hShellKey, NULL);
1242 }
1243
1244 // delete shell commands
1245 WCHAR szVerbName[VERBKEY_CCHMAX];
1246 DWORD dwIndex = 0;
1247 while (RegEnumKeyW(hShellKey, dwIndex, szVerbName, _countof(szVerbName)) == ERROR_SUCCESS)
1248 {
1249 if (pEditType->CommandLineMap.FindKey(szVerbName) == -1)
1250 {
1251 // doesn't exist in CommandLineMap, then delete it
1252 if (SHDeleteKeyW(hShellKey, szVerbName) == ERROR_SUCCESS)
1253 {
1254 --dwIndex;
1255 }
1256 }
1257 ++dwIndex;
1258 }
1259
1260 // write shell commands
1261 const INT nCount = pEditType->CommandLineMap.GetSize();
1262 for (INT i = 0; i < nCount; ++i)
1263 {
1264 const CStringW& key = pEditType->CommandLineMap.GetKeyAt(i);
1265 const CStringW& cmd = pEditType->CommandLineMap.GetValueAt(i);
1266 if (!pEditType->ModifiedVerbs.Find(key))
1267 {
1268 ASSERT(RegKeyExists(hShellKey, key));
1269 continue;
1270 }
1271
1272 // create verb key
1273 HKEY hVerbKey;
1274 if (RegCreateKeyExW(hShellKey, key, 0, NULL, 0, KEY_WRITE, NULL,
1275 &hVerbKey, NULL) == ERROR_SUCCESS)
1276 {
1277 // create command key
1278 HKEY hCommandKey;
1279 if (RegCreateKeyExW(hVerbKey, L"command", 0, NULL, 0, KEY_WRITE, NULL,
1280 &hCommandKey, NULL) == ERROR_SUCCESS)
1281 {
1282 DWORD dwSize = (cmd.GetLength() + 1) * sizeof(WCHAR);
1283 DWORD dwType = REG_SZ;
1284 int exp;
1285 if ((exp = cmd.Find('%', 0)) >= 0 && cmd.Find('%', exp + 1) >= 0)
1286 dwType = REG_EXPAND_SZ;
1287 RegSetValueExW(hCommandKey, NULL, 0, dwType, LPBYTE(LPCWSTR(cmd)), dwSize);
1288 RegCloseKey(hCommandKey);
1289 }
1290
1291 RegCloseKey(hVerbKey);
1292 }
1293 }
1294
1295 RegCloseKey(hShellKey);
1296 RegCloseKey(hClassKey);
1297 return TRUE;
1298 }
1299
1300 static BOOL
EditTypeDlg_ReadClass(HWND hwndDlg,PEDITTYPE_DIALOG pEditType,EDITTYPEFLAGS & Etf)1301 EditTypeDlg_ReadClass(HWND hwndDlg, PEDITTYPE_DIALOG pEditType, EDITTYPEFLAGS &Etf)
1302 {
1303 PFILE_TYPE_ENTRY pEntry = pEditType->pEntry;
1304 LPCWSTR ClassKey;
1305 HRESULT hr = GetClassKey(*pEntry, ClassKey);
1306 HKEY hClassKey;
1307 if (FAILED(hr) || RegOpenKeyExW(HKEY_CLASSES_ROOT, ClassKey, 0, KEY_READ, &hClassKey))
1308 return FALSE;
1309
1310 UINT etfbits = (RegValueExists(hClassKey, L"AlwaysShowExt")) ? ETF_ALWAYSEXT : 0;
1311
1312 // 8 in BrowserFlags listed in KB 162059 seems to be our bit
1313 BOOL docobj = RegKeyExists(hClassKey, L"DocObject");
1314 EnableWindow(GetDlgItem(hwndDlg, IDC_EDITTYPE_SAME_WINDOW), docobj);
1315 etfbits |= (docobj && (GetRegDWORD(hClassKey, L"BrowserFlags") & 8)) ? 0 : ETF_BROWSESAME;
1316
1317 Etf = EDITTYPEFLAGS(etfbits);
1318
1319 // open "shell" key
1320 HKEY hShellKey;
1321 if (RegOpenKeyExW(hClassKey, L"shell", 0, KEY_READ, &hShellKey) != ERROR_SUCCESS)
1322 {
1323 RegCloseKey(hClassKey);
1324 return FALSE;
1325 }
1326
1327 WCHAR DefaultVerb[VERBKEY_CCHMAX];
1328 DWORD dwSize = sizeof(DefaultVerb);
1329 if (RegQueryValueExW(hShellKey, NULL, NULL, NULL,
1330 LPBYTE(DefaultVerb), &dwSize) == ERROR_SUCCESS)
1331 {
1332 StringCbCopyW(pEditType->szDefaultVerb, sizeof(pEditType->szDefaultVerb), DefaultVerb);
1333 }
1334 else
1335 {
1336 StringCbCopyW(pEditType->szDefaultVerb, sizeof(pEditType->szDefaultVerb), L"open");
1337 }
1338
1339 // enumerate shell verbs
1340 WCHAR szVerbName[VERBKEY_CCHMAX];
1341 DWORD dwIndex = 0;
1342 while (RegEnumKeyW(hShellKey, dwIndex, szVerbName, _countof(szVerbName)) == ERROR_SUCCESS)
1343 {
1344 // open verb key
1345 HKEY hVerbKey;
1346 LONG nResult = RegOpenKeyExW(hShellKey, szVerbName, 0, KEY_READ, &hVerbKey);
1347 if (nResult == ERROR_SUCCESS)
1348 {
1349 // open command key
1350 HKEY hCommandKey;
1351 nResult = RegOpenKeyExW(hVerbKey, L"command", 0, KEY_READ, &hCommandKey);
1352 if (nResult == ERROR_SUCCESS)
1353 {
1354 // get command line
1355 WCHAR szValue[MAX_PATH * 2];
1356 dwSize = sizeof(szValue);
1357 nResult = RegQueryValueExW(hCommandKey, NULL, NULL, NULL, LPBYTE(szValue), &dwSize);
1358 if (nResult == ERROR_SUCCESS)
1359 {
1360 pEditType->CommandLineMap.SetAt(szVerbName, szValue);
1361 }
1362
1363 RegCloseKey(hCommandKey);
1364 }
1365
1366 RegCloseKey(hVerbKey);
1367 }
1368 SendDlgItemMessageW(hwndDlg, IDC_EDITTYPE_LISTBOX, LB_ADDSTRING, 0, LPARAM(szVerbName));
1369 ++dwIndex;
1370 }
1371
1372 WCHAR TypeName[TYPENAME_CCHMAX];
1373 dwSize = sizeof(TypeName);
1374 if (!RegQueryValueExW(hClassKey, NULL, NULL, NULL, LPBYTE(TypeName), &dwSize))
1375 {
1376 TypeName[_countof(TypeName) - 1] = UNICODE_NULL; // Terminate
1377 SetDlgItemTextW(hwndDlg, IDC_EDITTYPE_TEXT, TypeName);
1378 }
1379
1380 RegCloseKey(hShellKey);
1381 RegCloseKey(hClassKey);
1382 return TRUE;
1383 }
1384
1385 static void
EditTypeDlg_OnOK(HWND hwndDlg,PEDITTYPE_DIALOG pEditType)1386 EditTypeDlg_OnOK(HWND hwndDlg, PEDITTYPE_DIALOG pEditType)
1387 {
1388 PFILE_TYPE_ENTRY pEntry = pEditType->pEntry;
1389
1390 WCHAR TypeName[TYPENAME_CCHMAX];
1391 GetDlgItemTextW(hwndDlg, IDC_EDITTYPE_TEXT, TypeName, _countof(TypeName));
1392 StrTrimW(TypeName, g_pszSpace);
1393
1394 UINT etf = 0;
1395 pEntry->EditFlags &= ~(FTA_MODIFYMASK);
1396 if (!SendDlgItemMessageW(hwndDlg, IDC_EDITTYPE_CONFIRM_OPEN, BM_GETCHECK, 0, 0))
1397 pEntry->EditFlags |= FTA_OpenIsSafe;
1398 if (SendDlgItemMessageW(hwndDlg, IDC_EDITTYPE_SHOW_EXT, BM_GETCHECK, 0, 0))
1399 etf |= ETF_ALWAYSEXT;
1400 if (SendDlgItemMessageW(hwndDlg, IDC_EDITTYPE_SAME_WINDOW, BM_GETCHECK, 0, 0))
1401 etf |= ETF_BROWSESAME;
1402
1403 // update entry icon
1404 EditTypeDlg_UpdateEntryIcon(hwndDlg, pEditType);
1405
1406 // write registry
1407 EditTypeDlg_WriteClass(hwndDlg, pEditType, TypeName, (EDITTYPEFLAGS)etf);
1408
1409 pEntry->InvalidateDefaultApp();
1410
1411 // update the icon cache
1412 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSHNOWAIT, NULL, NULL);
1413
1414 EndDialog(hwndDlg, IDOK);
1415 }
1416
1417 static BOOL
EditTypeDlg_OnRemove(HWND hwndDlg,PEDITTYPE_DIALOG pEditType)1418 EditTypeDlg_OnRemove(HWND hwndDlg, PEDITTYPE_DIALOG pEditType)
1419 {
1420 // get current selection
1421 INT iItem = SendDlgItemMessageW(hwndDlg, IDC_EDITTYPE_LISTBOX, LB_GETCURSEL, 0, 0);
1422 if (iItem == LB_ERR)
1423 return FALSE;
1424
1425 // ask user for removal
1426 CStringW strText(MAKEINTRESOURCEW(IDS_REMOVE_ACTION));
1427 CStringW strTitle(MAKEINTRESOURCEW(IDS_FILE_TYPES));
1428 if (MessageBoxW(hwndDlg, strText, strTitle, MB_ICONINFORMATION | MB_YESNO) == IDNO)
1429 return FALSE;
1430
1431 // get text
1432 WCHAR szText[VERBKEY_CCHMAX];
1433 szText[0] = 0;
1434 SendDlgItemMessageW(hwndDlg, IDC_EDITTYPE_LISTBOX, LB_GETTEXT, iItem, (LPARAM)szText);
1435 StrTrimW(szText, g_pszSpace);
1436
1437 // remove it
1438 pEditType->CommandLineMap.Remove(szText);
1439 SendDlgItemMessageW(hwndDlg, IDC_EDITTYPE_LISTBOX, LB_DELETESTRING, iItem, 0);
1440 return TRUE;
1441 }
1442
1443 static void
EditTypeDlg_OnCommand(HWND hwndDlg,UINT id,UINT code,PEDITTYPE_DIALOG pEditType)1444 EditTypeDlg_OnCommand(HWND hwndDlg, UINT id, UINT code, PEDITTYPE_DIALOG pEditType)
1445 {
1446 INT iItem, iIndex;
1447 ACTION_DIALOG action;
1448 switch (id)
1449 {
1450 case IDOK:
1451 EditTypeDlg_OnOK(hwndDlg, pEditType);
1452 break;
1453
1454 case IDCANCEL:
1455 EndDialog(hwndDlg, IDCANCEL);
1456 break;
1457
1458 case IDC_EDITTYPE_CHANGE_ICON:
1459 EditTypeDlg_OnChangeIcon(hwndDlg, pEditType);
1460 break;
1461
1462 case IDC_EDITTYPE_NEW:
1463 // open 'New Action' dialog
1464 action.bUseDDE = FALSE;
1465 action.hwndLB = GetDlgItem(hwndDlg, IDC_EDITTYPE_LISTBOX);
1466 action.pEntry = pEditType->pEntry;
1467 if (IDOK == DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_ACTION), hwndDlg,
1468 NewActionDlgProc, LPARAM(&action)))
1469 {
1470 if (SendMessageW(action.hwndLB, LB_FINDSTRING, -1, (LPARAM)action.szAction) != LB_ERR)
1471 {
1472 // already exists, error
1473 HWND hwndCtrl = GetDlgItem(hwndDlg, IDC_ACTION_ACTION);
1474 SendMessageW(hwndCtrl, EM_SETSEL, 0, -1);
1475 SetFocus(hwndCtrl);
1476
1477 CStringW strText, strTitle(MAKEINTRESOURCEW(IDS_FILE_TYPES));
1478 strText.Format(IDS_ACTION_EXISTS, action.szAction);
1479 MessageBoxW(hwndDlg, strText, strTitle, MB_ICONERROR);
1480 }
1481 else
1482 {
1483 // add it
1484 CStringW strCommandLine = action.szApp;
1485 QuoteAppPathForCommand(strCommandLine);
1486 strCommandLine += L" \"%1\"";
1487 pEditType->CommandLineMap.SetAt(action.szAction, strCommandLine);
1488 pEditType->ModifiedVerbs.AddHead(action.szAction);
1489 SendMessageW(action.hwndLB, LB_ADDSTRING, 0, LPARAM(action.szAction));
1490 if (SendMessageW(action.hwndLB, LB_GETCOUNT, 0, 0) == 1)
1491 {
1492 // set default
1493 StringCbCopyW(pEditType->szDefaultVerb, sizeof(pEditType->szDefaultVerb), action.szAction);
1494 InvalidateRect(action.hwndLB, NULL, TRUE);
1495 }
1496 }
1497 }
1498 break;
1499
1500 case IDC_EDITTYPE_LISTBOX:
1501 if (code == LBN_SELCHANGE)
1502 {
1503 action.hwndLB = GetDlgItem(hwndDlg, IDC_EDITTYPE_LISTBOX);
1504 INT iItem = SendMessageW(action.hwndLB, LB_GETCURSEL, 0, 0);
1505 SendMessageW(action.hwndLB, LB_GETTEXT, iItem, LPARAM(action.szAction));
1506 if (lstrcmpiW(action.szAction, pEditType->szDefaultVerb) == 0)
1507 {
1508 EnableWindow(GetDlgItem(hwndDlg, IDC_EDITTYPE_SET_DEFAULT), FALSE);
1509 }
1510 else
1511 {
1512 EnableWindow(GetDlgItem(hwndDlg, IDC_EDITTYPE_SET_DEFAULT), TRUE);
1513 }
1514 EditTypeDlg_Restrict(hwndDlg, pEditType);
1515 break;
1516 }
1517 else if (code != LBN_DBLCLK)
1518 {
1519 break;
1520 }
1521 // FALL THROUGH
1522
1523 case IDC_EDITTYPE_EDIT_BUTTON:
1524 action.bUseDDE = FALSE;
1525 action.hwndLB = GetDlgItem(hwndDlg, IDC_EDITTYPE_LISTBOX);
1526 action.pG = pEditType->pG;
1527 action.pEntry = pEditType->pEntry;
1528 iItem = SendMessageW(action.hwndLB, LB_GETCURSEL, 0, 0);
1529 if (iItem == LB_ERR)
1530 break;
1531
1532 // get action
1533 SendMessageW(action.hwndLB, LB_GETTEXT, iItem, LPARAM(action.szAction));
1534
1535 // get app
1536 {
1537 iIndex = pEditType->CommandLineMap.FindKey(action.szAction);
1538 CStringW str = pEditType->CommandLineMap.GetValueAt(iIndex);
1539 StringCbCopyW(action.szApp, sizeof(action.szApp), LPCWSTR(str));
1540 }
1541
1542 // open dialog
1543 if (IDOK == DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_ACTION), hwndDlg,
1544 EditActionDlgProc, LPARAM(&action)))
1545 {
1546 SendMessageW(action.hwndLB, LB_DELETESTRING, iItem, 0);
1547 SendMessageW(action.hwndLB, LB_INSERTSTRING, iItem, LPARAM(action.szAction));
1548 pEditType->CommandLineMap.SetAt(action.szAction, action.szApp);
1549 pEditType->ModifiedVerbs.AddHead(action.szAction);
1550 }
1551 break;
1552
1553 case IDC_EDITTYPE_REMOVE:
1554 EditTypeDlg_OnRemove(hwndDlg, pEditType);
1555 break;
1556
1557 case IDC_EDITTYPE_SET_DEFAULT:
1558 action.hwndLB = GetDlgItem(hwndDlg, IDC_EDITTYPE_LISTBOX);
1559 iItem = SendMessageW(action.hwndLB, LB_GETCURSEL, 0, 0);
1560 if (iItem == LB_ERR)
1561 break;
1562
1563 SendMessageW(action.hwndLB, LB_GETTEXT, iItem, LPARAM(action.szAction));
1564
1565 // set default
1566 StringCbCopyW(pEditType->szDefaultVerb, sizeof(pEditType->szDefaultVerb), action.szAction);
1567 EnableWindow(GetDlgItem(hwndDlg, IDC_EDITTYPE_SET_DEFAULT), FALSE);
1568 InvalidateRect(action.hwndLB, NULL, TRUE);
1569 break;
1570 }
1571 }
1572
1573 static BOOL
EditTypeDlg_OnInitDialog(HWND hwndDlg,PEDITTYPE_DIALOG pEditType)1574 EditTypeDlg_OnInitDialog(HWND hwndDlg, PEDITTYPE_DIALOG pEditType)
1575 {
1576 PFILE_TYPE_ENTRY pEntry = pEditType->pEntry;
1577 EDITTYPEFLAGS Etf;
1578 ExpandEnvironmentStringsW(pEntry->IconPath, pEditType->szIconPath, _countof(pEditType->szIconPath));
1579 pEditType->nIconIndex = pEntry->nIconIndex;
1580 StringCbCopyW(pEditType->szDefaultVerb, sizeof(pEditType->szDefaultVerb), L"open");
1581 pEditType->ChangedIcon = false;
1582
1583 // set info
1584 HICON hIco = DoExtractIcon(pEditType->szIconPath, pEditType->nIconIndex);
1585 SendDlgItemMessageW(hwndDlg, IDC_EDITTYPE_ICON, STM_SETICON, (WPARAM)hIco, 0);
1586 EditTypeDlg_ReadClass(hwndDlg, pEditType, Etf);
1587 SendDlgItemMessageW(hwndDlg, IDC_EDITTYPE_CONFIRM_OPEN, BM_SETCHECK, !(pEntry->EditFlags & FTA_OpenIsSafe), 0);
1588 SendDlgItemMessageW(hwndDlg, IDC_EDITTYPE_SHOW_EXT, BM_SETCHECK, !!(Etf & ETF_ALWAYSEXT), 0);
1589 EnableWindow(GetDlgItem(hwndDlg, IDC_EDITTYPE_SHOW_EXT), pEntry->IsExtension());
1590 SendDlgItemMessageW(hwndDlg, IDC_EDITTYPE_SAME_WINDOW, BM_SETCHECK, !!(Etf & ETF_BROWSESAME), 0);
1591 InvalidateRect(GetDlgItem(hwndDlg, IDC_EDITTYPE_LISTBOX), NULL, TRUE);
1592
1593 // select first item
1594 SendDlgItemMessageW(hwndDlg, IDC_EDITTYPE_LISTBOX, LB_SETCURSEL, 0, 0);
1595 // is listbox empty?
1596 if (SendDlgItemMessageW(hwndDlg, IDC_EDITTYPE_LISTBOX, LB_GETCOUNT, 0, 0) == 0)
1597 {
1598 EnableWindow(GetDlgItem(hwndDlg, IDC_EDITTYPE_EDIT_BUTTON), FALSE);
1599 EnableWindow(GetDlgItem(hwndDlg, IDC_EDITTYPE_REMOVE), FALSE);
1600 EnableWindow(GetDlgItem(hwndDlg, IDC_EDITTYPE_SET_DEFAULT), FALSE);
1601 }
1602 EditTypeDlg_Restrict(hwndDlg, pEditType);
1603 return TRUE;
1604 }
1605
1606 // IDD_EDITTYPE
1607 static INT_PTR CALLBACK
EditTypeDlgProc(HWND hwndDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)1608 EditTypeDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
1609 {
1610 static PEDITTYPE_DIALOG s_pEditType = NULL;
1611 LPDRAWITEMSTRUCT pDraw;
1612 LPMEASUREITEMSTRUCT pMeasure;
1613
1614 switch (uMsg)
1615 {
1616 case WM_INITDIALOG:
1617 s_pEditType = (PEDITTYPE_DIALOG)lParam;
1618 return EditTypeDlg_OnInitDialog(hwndDlg, s_pEditType);
1619
1620 case WM_DESTROY:
1621 {
1622 HICON hOld = (HICON)SendDlgItemMessageW(hwndDlg, IDC_EDITTYPE_ICON, STM_GETICON, 0, 0);
1623 if (hOld)
1624 DestroyIcon(hOld);
1625 break;
1626 }
1627
1628 case WM_DRAWITEM:
1629 pDraw = LPDRAWITEMSTRUCT(lParam);
1630 return EditTypeDlg_OnDrawItem(hwndDlg, pDraw, s_pEditType);
1631
1632 case WM_MEASUREITEM:
1633 pMeasure = LPMEASUREITEMSTRUCT(lParam);
1634 return EditTypeDlg_OnMeasureItem(hwndDlg, pMeasure, s_pEditType);
1635
1636 case WM_COMMAND:
1637 EditTypeDlg_OnCommand(hwndDlg, LOWORD(wParam), HIWORD(wParam), s_pEditType);
1638 break;
1639 }
1640
1641 return 0;
1642 }
1643
1644 /////////////////////////////////////////////////////////////////////////////
1645 // FileTypesDlg
1646
1647 static INT CALLBACK
FileTypesDlg_CompareItems(LPARAM lParam1,LPARAM lParam2,LPARAM lParamSort)1648 FileTypesDlg_CompareItems(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
1649 {
1650 PFILE_TYPE_GLOBALS pG = (PFILE_TYPE_GLOBALS)lParamSort;
1651 PFILE_TYPE_ENTRY entry1 = (PFILE_TYPE_ENTRY)lParam1, entry2 = (PFILE_TYPE_ENTRY)lParam2;
1652 int x = 0;
1653 if (pG->SortCol == 1)
1654 x = wcsicmp(GetTypeName(entry1, pG), GetTypeName(entry2, pG));
1655 if (!x && !(x = entry1->IsExtension() - entry2->IsExtension()))
1656 x = wcsicmp(entry1->FileExtension, entry2->FileExtension);
1657 return x * pG->SortReverse;
1658 }
1659
1660 static void
FileTypesDlg_Sort(PFILE_TYPE_GLOBALS pG,HWND hListView,INT Column=-1)1661 FileTypesDlg_Sort(PFILE_TYPE_GLOBALS pG, HWND hListView, INT Column = -1)
1662 {
1663 pG->SortReverse = pG->SortCol == Column ? pG->SortReverse * -1 : 1;
1664 pG->SortCol = Column < 0 ? 0 : (INT8) Column;
1665 ListView_SortItems(hListView, FileTypesDlg_CompareItems, (LPARAM)pG);
1666 }
1667
1668 static VOID
FileTypesDlg_InitListView(HWND hwndDlg,HWND hListView)1669 FileTypesDlg_InitListView(HWND hwndDlg, HWND hListView)
1670 {
1671 RECT clientRect;
1672 LPCWSTR columnName;
1673 WCHAR szBuf[50];
1674
1675 LVCOLUMNW col;
1676 col.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM | LVCF_FMT;
1677 col.fmt = 0;
1678
1679 GetClientRect(hListView, &clientRect);
1680 INT column0Size = (clientRect.right - clientRect.left) / 4;
1681
1682 columnName = L"Extensions"; // Default to English
1683 if (LoadStringW(shell32_hInstance, IDS_COLUMN_EXTENSION, szBuf, _countof(szBuf)))
1684 columnName = szBuf;
1685 col.pszText = const_cast<LPWSTR>(columnName);
1686 col.iSubItem = 0;
1687 col.cx = column0Size;
1688 SendMessageW(hListView, LVM_INSERTCOLUMNW, 0, (LPARAM)&col);
1689
1690 columnName = L"File Types"; // Default to English
1691 if (LoadStringW(shell32_hInstance, IDS_FILE_TYPES, szBuf, _countof(szBuf)))
1692 {
1693 columnName = szBuf;
1694 }
1695 else
1696 {
1697 ERR("Failed to load localized string!\n");
1698 }
1699 col.pszText = const_cast<LPWSTR>(columnName);
1700 col.iSubItem = 1;
1701 col.cx = clientRect.right - clientRect.left - column0Size - GetSystemMetrics(SM_CYVSCROLL);
1702 SendMessageW(hListView, LVM_INSERTCOLUMNW, 1, (LPARAM)&col);
1703
1704 const UINT lvexstyle = LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP;
1705 ListView_SetExtendedListViewStyleEx(hListView, lvexstyle, lvexstyle);
1706 }
1707
1708 static void
FileTypesDlg_SetGroupboxText(HWND hwndDlg,LPCWSTR Assoc)1709 FileTypesDlg_SetGroupboxText(HWND hwndDlg, LPCWSTR Assoc)
1710 {
1711 CStringW buf;
1712 buf.Format(IDS_FILE_DETAILS, Assoc);
1713 SetDlgItemTextW(hwndDlg, IDC_FILETYPES_DETAILS_GROUPBOX, buf.GetString());
1714 }
1715
1716 static void
FileTypesDlg_Refresh(HWND hwndDlg,HWND hListView,PFILE_TYPE_GLOBALS pG)1717 FileTypesDlg_Refresh(HWND hwndDlg, HWND hListView, PFILE_TYPE_GLOBALS pG)
1718 {
1719 ListView_DeleteAllItems(hListView);
1720 ImageList_RemoveAll(pG->himlSmall);
1721 InitializeDefaultIcons(pG);
1722 FileTypesDlg_SetGroupboxText(hwndDlg, L"");
1723 SetDlgItemTextW(hwndDlg, IDC_FILETYPES_DESCRIPTION, L"");
1724 SetDlgItemTextW(hwndDlg, IDC_FILETYPES_APPNAME, L"");
1725 EnableWindow(GetDlgItem(hwndDlg, IDC_FILETYPES_DELETE), FALSE);
1726 RedrawWindow(hwndDlg, NULL, NULL, RDW_ALLCHILDREN | RDW_INVALIDATE | RDW_UPDATENOW);
1727 #if DBG
1728 DWORD statTickStart = GetTickCount();
1729 #endif
1730
1731 INT iItem = 0;
1732 WCHAR szName[ASSOC_CCHMAX];
1733 DWORD dwName = _countof(szName);
1734 DWORD dwIndex = 0;
1735 SendMessage(hListView, WM_SETREDRAW, FALSE, 0);
1736 while (RegEnumKeyExW(HKEY_CLASSES_ROOT, dwIndex++, szName, &dwName,
1737 NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
1738 {
1739 if (FileTypesDlg_InsertToLV(hListView, szName, iItem, pG))
1740 ++iItem;
1741 dwName = _countof(szName);
1742 }
1743 FileTypesDlg_Sort(pG, hListView);
1744 SendMessage(hListView, WM_SETREDRAW, TRUE, 0);
1745 RedrawWindow(hListView, NULL, NULL, RDW_ALLCHILDREN | RDW_ERASE | RDW_FRAME | RDW_INVALIDATE);
1746
1747 #if DBG
1748 DbgPrint("FT loaded %u (%ums)\n", iItem, GetTickCount() - statTickStart);
1749 #endif
1750 // select first item
1751 ListView_SetItemState(hListView, 0, -1, LVIS_FOCUSED | LVIS_SELECTED);
1752 }
1753
1754 static PFILE_TYPE_GLOBALS
FileTypesDlg_Initialize(HWND hwndDlg)1755 FileTypesDlg_Initialize(HWND hwndDlg)
1756 {
1757 HWND hListView = GetDlgItem(hwndDlg, IDC_FILETYPES_LISTVIEW);
1758 PFILE_TYPE_GLOBALS pG = (PFILE_TYPE_GLOBALS)SHAlloc(sizeof(*pG));
1759 if (!pG)
1760 return pG;
1761
1762 pG->SortReverse = 1;
1763 pG->hDefExtIconSmall = NULL;
1764 pG->hOpenWithImage = NULL;
1765 pG->IconSize = GetSystemMetrics(SM_CXSMICON); // Shell icons are always square
1766 pG->himlSmall = ImageList_Create(pG->IconSize, pG->IconSize, ILC_COLOR32 | ILC_MASK, 256, 20);
1767 pG->hHeap = GetProcessHeap();
1768
1769 pG->NoneString[0] = UNICODE_NULL;
1770 LoadStringW(shell32_hInstance, IDS_NONE, pG->NoneString, _countof(pG->NoneString));
1771
1772 if (!(pG->Restricted = SHRestricted(REST_NOFILEASSOCIATE)))
1773 {
1774 HKEY hKey;
1775 if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Classes", 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL))
1776 pG->Restricted = TRUE;
1777 else
1778 RegCloseKey(hKey);
1779 }
1780
1781 FileTypesDlg_InitListView(hwndDlg, hListView);
1782 ListView_SetImageList(hListView, pG->himlSmall, LVSIL_SMALL);
1783 EnableWindow(GetDlgItem(hwndDlg, IDC_FILETYPES_NEW), !pG->Restricted);
1784
1785 // Delay loading the items so the propertysheet has time to finalize the UI
1786 PostMessage(hListView, WM_KEYDOWN, VK_F5, 0);
1787 return pG;
1788 }
1789
1790 static inline PFILE_TYPE_ENTRY
FileTypesDlg_GetEntry(HWND hListView,INT iItem=-1)1791 FileTypesDlg_GetEntry(HWND hListView, INT iItem = -1)
1792 {
1793 if (iItem == -1)
1794 {
1795 iItem = ListView_GetNextItem(hListView, -1, LVNI_SELECTED);
1796 if (iItem == -1)
1797 return NULL;
1798 }
1799
1800 LV_ITEMW lvItem = { LVIF_PARAM, iItem };
1801 if (ListView_GetItem(hListView, &lvItem))
1802 return (PFILE_TYPE_ENTRY)lvItem.lParam;
1803
1804 return NULL;
1805 }
1806
1807 static void
FileTypesDlg_OnDelete(HWND hwndDlg)1808 FileTypesDlg_OnDelete(HWND hwndDlg)
1809 {
1810 CStringW strRemoveExt(MAKEINTRESOURCEW(IDS_REMOVE_EXT));
1811 CStringW strTitle(MAKEINTRESOURCEW(IDS_FILE_TYPES));
1812 if (MessageBoxW(hwndDlg, strRemoveExt, strTitle, MB_ICONQUESTION | MB_YESNO) == IDYES)
1813 {
1814 FileTypesDlg_RemoveExt(hwndDlg);
1815
1816 // Select first item (Win2k3 does it)
1817 HWND hListView = GetDlgItem(hwndDlg, IDC_FILETYPES_LISTVIEW);
1818 ListView_SetItemState(hListView, 0, -1, LVIS_FOCUSED | LVIS_SELECTED);
1819 }
1820 }
1821
1822 static void
FileTypesDlg_OnItemChanging(HWND hwndDlg,PFILE_TYPE_ENTRY pEntry,PFILE_TYPE_GLOBALS pG)1823 FileTypesDlg_OnItemChanging(HWND hwndDlg, PFILE_TYPE_ENTRY pEntry, PFILE_TYPE_GLOBALS pG)
1824 {
1825 HBITMAP &hbmProgram = pG->hOpenWithImage;
1826 LPCWSTR DispAssoc = pEntry->GetAssocForDisplay();
1827 LPCWSTR TypeName = GetTypeName(pEntry, pG);
1828 CStringW buf;
1829
1830 // format buffer and set description
1831 FileTypesDlg_SetGroupboxText(hwndDlg, DispAssoc);
1832 if (pEntry->IsExtension())
1833 buf.Format(IDS_FILE_DETAILSADV, DispAssoc, TypeName, TypeName);
1834 else
1835 buf = L"";
1836 SetDlgItemTextW(hwndDlg, IDC_FILETYPES_DESCRIPTION, buf.GetString());
1837
1838 // delete previous program image
1839 if (hbmProgram)
1840 {
1841 DeleteObject(hbmProgram);
1842 hbmProgram = NULL;
1843 }
1844
1845 // set program name
1846 LPCWSTR appname = GetAppName(pEntry);
1847 SetDlgItemTextW(hwndDlg, IDC_FILETYPES_APPNAME, appname);
1848
1849 // set program image
1850 HICON hIconSm = NULL;
1851 LPCWSTR exe = GetProgramPath(pEntry);
1852 if (*exe)
1853 {
1854 ExtractIconExW(exe, 0, NULL, &hIconSm, 1);
1855 }
1856 hbmProgram = BitmapFromIcon(hIconSm, 16, 16);
1857 DestroyIcon(hIconSm);
1858 SendDlgItemMessageW(hwndDlg, IDC_FILETYPES_ICON, STM_SETIMAGE, IMAGE_BITMAP, LPARAM(hbmProgram));
1859
1860 // Enable/Disable the buttons
1861 EnableWindow(GetDlgItem(hwndDlg, IDC_FILETYPES_CHANGE),
1862 !pG->Restricted && pEntry->IsExtension());
1863 EnableWindow(GetDlgItem(hwndDlg, IDC_FILETYPES_ADVANCED),
1864 !(pEntry->EditFlags & FTA_NoEdit) && !pG->Restricted);
1865 EnableWindow(GetDlgItem(hwndDlg, IDC_FILETYPES_DELETE),
1866 !(pEntry->EditFlags & FTA_NoRemove) && !pG->Restricted && pEntry->IsExtension());
1867 }
1868
1869 // IDD_FOLDER_OPTIONS_FILETYPES
1870 INT_PTR CALLBACK
FolderOptionsFileTypesDlg(HWND hwndDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)1871 FolderOptionsFileTypesDlg(
1872 HWND hwndDlg,
1873 UINT uMsg,
1874 WPARAM wParam,
1875 LPARAM lParam)
1876 {
1877 PFILE_TYPE_GLOBALS pGlobals = (PFILE_TYPE_GLOBALS)GetWindowLongPtrW(hwndDlg, DWLP_USER);
1878 if (!pGlobals && uMsg != WM_INITDIALOG)
1879 return FALSE;
1880 LPNMLISTVIEW lppl;
1881 PFILE_TYPE_ENTRY pEntry;
1882 NEWEXT_DIALOG newext;
1883 EDITTYPE_DIALOG edittype;
1884
1885 switch (uMsg)
1886 {
1887 case WM_INITDIALOG:
1888 pGlobals = FileTypesDlg_Initialize(hwndDlg);
1889 SetWindowLongPtrW(hwndDlg, DWLP_USER, (LONG_PTR)pGlobals);
1890 return TRUE;
1891
1892 case WM_DESTROY:
1893 SetWindowLongPtrW(hwndDlg, DWLP_USER, 0);
1894 if (pGlobals)
1895 {
1896 DestroyIcon(pGlobals->hDefExtIconSmall);
1897 DeleteObject(pGlobals->hOpenWithImage);
1898 SHFree(pGlobals);
1899 }
1900 break;
1901
1902 case WM_COMMAND:
1903 switch (LOWORD(wParam))
1904 {
1905 case IDC_FILETYPES_NEW:
1906 newext.hwndLV = GetDlgItem(hwndDlg, IDC_FILETYPES_LISTVIEW);
1907 if (IDOK == DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_NEWEXTENSION),
1908 hwndDlg, NewExtDlgProc, (LPARAM)&newext))
1909 {
1910 FileTypesDlg_AddExt(hwndDlg, newext.szExt, newext.szFileType, pGlobals);
1911 }
1912 break;
1913
1914 case IDC_FILETYPES_DELETE:
1915 FileTypesDlg_OnDelete(hwndDlg);
1916 break;
1917
1918 case IDC_FILETYPES_CHANGE:
1919 pEntry = FileTypesDlg_GetEntry(GetDlgItem(hwndDlg, IDC_FILETYPES_LISTVIEW));
1920 if (pEntry)
1921 {
1922 OPENASINFO oai = { pEntry->FileExtension, 0, OAIF_FORCE_REGISTRATION | OAIF_REGISTER_EXT };
1923 if (SUCCEEDED(SHOpenWithDialog(hwndDlg, &oai)))
1924 {
1925 pEntry->InvalidateDefaultApp();
1926 FileTypesDlg_OnItemChanging(hwndDlg, pEntry, pGlobals);
1927 }
1928 }
1929 break;
1930
1931 case IDC_FILETYPES_ADVANCED:
1932 edittype.hwndLV = GetDlgItem(hwndDlg, IDC_FILETYPES_LISTVIEW);
1933 edittype.pG = pGlobals;
1934 edittype.pEntry = FileTypesDlg_GetEntry(edittype.hwndLV);
1935 if (Normalize(edittype.pEntry))
1936 {
1937 DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_EDITTYPE),
1938 hwndDlg, EditTypeDlgProc, (LPARAM)&edittype);
1939 FileTypesDlg_OnItemChanging(hwndDlg, edittype.pEntry, pGlobals);
1940 }
1941 break;
1942 }
1943 break;
1944
1945 case WM_NOTIFY:
1946 lppl = (LPNMLISTVIEW) lParam;
1947 switch (lppl->hdr.code)
1948 {
1949 case LVN_GETDISPINFO:
1950 {
1951 LPNMLVDISPINFOW pLVDI = (LPNMLVDISPINFOW)lParam;
1952 PFILE_TYPE_ENTRY entry = (PFILE_TYPE_ENTRY)pLVDI->item.lParam;
1953 if (entry && (pLVDI->item.mask & LVIF_TEXT))
1954 {
1955 if (pLVDI->item.iSubItem == 1)
1956 {
1957 pLVDI->item.pszText = GetTypeName(entry, pGlobals);
1958 pLVDI->item.mask |= LVIF_DI_SETITEM;
1959 }
1960 }
1961 break;
1962 }
1963
1964 case LVN_KEYDOWN:
1965 {
1966 LV_KEYDOWN *pKeyDown = (LV_KEYDOWN *)lParam;
1967 switch (pKeyDown->wVKey)
1968 {
1969 case VK_DELETE:
1970 FileTypesDlg_OnDelete(hwndDlg);
1971 break;
1972 case VK_F5:
1973 FileTypesDlg_Refresh(hwndDlg, pKeyDown->hdr.hwndFrom, pGlobals);
1974 break;
1975 }
1976 break;
1977 }
1978
1979 case NM_DBLCLK:
1980 SendMessage(hwndDlg, WM_COMMAND, IDC_FILETYPES_ADVANCED, 0);
1981 break;
1982
1983 case LVN_DELETEALLITEMS:
1984 return FALSE; // send LVN_DELETEITEM
1985
1986 case LVN_DELETEITEM:
1987 pEntry = FileTypesDlg_GetEntry(lppl->hdr.hwndFrom, lppl->iItem);
1988 if (pEntry)
1989 {
1990 pEntry->DestroyIcons();
1991 HeapFree(pGlobals->hHeap, 0, pEntry);
1992 }
1993 return FALSE;
1994
1995 case LVN_ITEMCHANGING:
1996 pEntry = FileTypesDlg_GetEntry(lppl->hdr.hwndFrom, lppl->iItem);
1997 if (!pEntry)
1998 {
1999 return TRUE;
2000 }
2001
2002 if (!(lppl->uOldState & LVIS_FOCUSED) && (lppl->uNewState & LVIS_FOCUSED))
2003 {
2004 FileTypesDlg_OnItemChanging(hwndDlg, pEntry, pGlobals);
2005 }
2006 break;
2007
2008 case LVN_COLUMNCLICK:
2009 FileTypesDlg_Sort(pGlobals, lppl->hdr.hwndFrom, lppl->iSubItem);
2010 break;
2011
2012 case PSN_SETACTIVE:
2013 // On page activation, set the focus to the listview
2014 SetFocus(GetDlgItem(hwndDlg, IDC_FILETYPES_LISTVIEW));
2015 break;
2016 }
2017 break;
2018 }
2019
2020 return FALSE;
2021 }
2022