Reactos
1/*
2 * PROJECT: ReactOS Explorer
3 * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
4 * PURPOSE: "Customize Start Menu" dialog
5 * COPYRIGHT: Copyright 2006-2007 Thomas Weidenmueller <w3seek@reactos.org>
6 * Copyright 2015 Robert Naumann <gonzomdx@gmail.com>
7 * Copyright 2024 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
8 */
9
10#include "precomp.h"
11
12// TreeView checkbox state indexes (Use with INDEXTOSTATEIMAGEMASK macro)
13#define I_UNCHECKED 1
14#define I_CHECKED 2
15
16// TODO: Windows Explorer appears to be calling NewLinkHere / ConfigStartMenu directly for both items.
17VOID OnAddStartMenuItems(HWND hDlg)
18{
19 WCHAR szPath[MAX_PATH];
20
21 if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROGRAMS, NULL, 0, szPath)))
22 {
23 WCHAR szCommand[MAX_PATH] = L"appwiz.cpl,NewLinkHere ";
24 if (SUCCEEDED(StringCchCatW(szCommand, _countof(szCommand), szPath)))
25 ShellExecuteW(hDlg, L"open", L"rundll32.exe", szCommand, NULL, SW_SHOWNORMAL);
26 }
27}
28
29VOID OnRemoveStartmenuItems(HWND hDlg)
30{
31 ShellExecuteW(hDlg, L"open", L"rundll32.exe", L"appwiz.cpl,ConfigStartMenu", NULL, SW_SHOWNORMAL);
32}
33
34VOID OnAdvancedStartMenuItems()
35{
36 WCHAR szPath[MAX_PATH];
37
38 if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_STARTMENU, NULL, 0, szPath)))
39 {
40 ShellExecuteW(NULL, L"explore", szPath, NULL, NULL, SW_SHOWNORMAL);
41 }
42}
43
44static BOOL RecentHasShortcut(HWND hwnd)
45{
46 WCHAR szPath[MAX_PATH];
47 if (FAILED(SHGetFolderPathW(hwnd, CSIDL_RECENT | CSIDL_FLAG_CREATE, NULL, 0, szPath)))
48 return FALSE;
49
50 // Find shortcut files in Recent
51 WIN32_FIND_DATAW find;
52 PathAppendW(szPath, L"*.lnk");
53 HANDLE hFind = FindFirstFileW(szPath, &find);
54 if (hFind == INVALID_HANDLE_VALUE)
55 return FALSE;
56
57 FindClose(hFind);
58 return TRUE;
59}
60
61static const PCWSTR g_MruKeys[] =
62{
63 L"Software\\Microsoft\\Internet Explorer\\TypedURLs",
64 L"Explorer\\RunMRU",
65 L"Explorer\\Comdlg32\\OpenSaveMRU",
66 L"Explorer\\Comdlg32\\LastVisitedMRU",
67};
68
69static BOOL HandleMruData(BOOL Delete)
70{
71 for (UINT i = 0; i < _countof(g_MruKeys); ++i)
72 {
73 WCHAR szKey[200];
74 PCWSTR pszKey = g_MruKeys[i];
75 if (*pszKey != 'S') // Keys not starting with S[oftware] are assumed to be relative to "SMWCV"
76 {
77 wsprintfW(szKey, L"%s\\%s", L"Software\\Microsoft\\Windows\\CurrentVersion", pszKey);
78 pszKey = szKey;
79 }
80
81 HKEY hKey;
82 if (Delete)
83 {
84 SHDeleteKeyW(HKEY_CURRENT_USER, pszKey);
85 }
86 else if (RegOpenKeyExW(HKEY_CURRENT_USER, pszKey, 0, KEY_WRITE, &hKey) == ERROR_SUCCESS)
87 {
88 RegCloseKey(hKey);
89 return TRUE;
90 }
91 }
92 return FALSE;
93}
94
95VOID ClearRecentAndMru()
96{
97 SHAddToRecentDocs(SHARD_PIDL, NULL);
98 HandleMruData(TRUE);
99}
100
101static VOID InitializeClearButton(HWND hwnd)
102{
103 HWND hWndClear = GetDlgItem(hwnd, IDC_CLASSICSTART_CLEAR);
104 BOOL bHasData = RecentHasShortcut(hwnd) || HandleMruData(FALSE);
105 if (!bHasData && hWndClear == GetFocus())
106 SendMessage(hwnd, WM_NEXTDLGCTL, 0, FALSE);
107 EnableWindow(hWndClear, bHasData);
108}
109
110struct CUSTOM_ENTRY;
111
112typedef BOOL (CALLBACK *FN_CUSTOM_GET)(const CUSTOM_ENTRY *entry);
113typedef VOID (CALLBACK *FN_CUSTOM_SET)(const CUSTOM_ENTRY *entry, BOOL bValue);
114
115struct CUSTOM_ENTRY
116{
117 LPARAM id;
118 LPCWSTR name;
119 BOOL bDefaultValue;
120 FN_CUSTOM_GET fnGetValue;
121 FN_CUSTOM_SET fnSetValue;
122 RESTRICTIONS policy1, policy2;
123};
124
125static BOOL CALLBACK CustomGetAdvanced(const CUSTOM_ENTRY *entry)
126{
127 return GetAdvancedBool(entry->name, entry->bDefaultValue);
128}
129
130static VOID CALLBACK CustomSetAdvanced(const CUSTOM_ENTRY *entry, BOOL bValue)
131{
132 SetAdvancedDword(entry->name, bValue);
133}
134
135static BOOL CALLBACK CustomGetSmallStartMenu(const CUSTOM_ENTRY *entry)
136{
137 return g_TaskbarSettings.sr.SmSmallIcons;
138}
139
140static VOID CALLBACK CustomSetSmallStartMenu(const CUSTOM_ENTRY *entry, BOOL bValue)
141{
142 g_TaskbarSettings.sr.SmSmallIcons = bValue;
143}
144
145static const CUSTOM_ENTRY s_CustomEntries[] =
146{
147 {
148 IDS_ADVANCED_DISPLAY_ADMINTOOLS, L"StartMenuAdminTools", TRUE,
149 CustomGetAdvanced, CustomSetAdvanced,
150 },
151 {
152 IDS_ADVANCED_DISPLAY_FAVORITES, L"StartMenuFavorites", FALSE,
153 CustomGetAdvanced, CustomSetAdvanced,
154 REST_NOFAVORITESMENU,
155 },
156 {
157 IDS_ADVANCED_DISPLAY_LOG_OFF, L"StartMenuLogoff", FALSE,
158 CustomGetAdvanced, CustomSetAdvanced,
159 REST_STARTMENULOGOFF,
160 },
161 {
162 IDS_ADVANCED_DISPLAY_RUN, L"StartMenuRun", TRUE,
163 CustomGetAdvanced, CustomSetAdvanced,
164 REST_NORUN,
165 },
166 {
167 IDS_ADVANCED_EXPAND_MY_DOCUMENTS, L"CascadeMyDocuments", FALSE,
168 CustomGetAdvanced, CustomSetAdvanced,
169 REST_NOSMMYDOCS,
170 },
171 {
172 IDS_ADVANCED_EXPAND_MY_PICTURES, L"CascadeMyPictures", FALSE,
173 CustomGetAdvanced, CustomSetAdvanced,
174 REST_NOSMMYPICS,
175 },
176 {
177 IDS_ADVANCED_EXPAND_CONTROL_PANEL, L"CascadeControlPanel", FALSE,
178 CustomGetAdvanced, CustomSetAdvanced,
179 REST_NOSETFOLDERS, REST_NOCONTROLPANEL,
180 },
181 {
182 IDS_ADVANCED_EXPAND_PRINTERS, L"CascadePrinters", FALSE,
183 CustomGetAdvanced, CustomSetAdvanced,
184 REST_NOSETFOLDERS,
185 },
186 {
187 IDS_ADVANCED_EXPAND_NET_CONNECTIONS, L"CascadeNetworkConnections", FALSE,
188 CustomGetAdvanced, CustomSetAdvanced,
189 REST_NOSETFOLDERS, REST_NONETWORKCONNECTIONS,
190 },
191 {
192 IDS_ADVANCED_SMALL_START_MENU, NULL, FALSE,
193 CustomGetSmallStartMenu, CustomSetSmallStartMenu,
194 },
195};
196
197static VOID AddCustomItem(HWND hTreeView, const CUSTOM_ENTRY *entry)
198{
199 if (SHRestricted(entry->policy1) || SHRestricted(entry->policy2))
200 {
201 TRACE("%p: Restricted\n", entry->id);
202 return; // Restricted. Don't show
203 }
204
205 WCHAR szText[MAX_PATH];
206 LoadStringW(GetModuleHandleW(L"shell32.dll"), entry->id, szText, _countof(szText));
207
208 BOOL bChecked = entry->fnGetValue(entry);
209 TRACE("%p: %d\n", entry->id, bChecked);
210
211 TV_INSERTSTRUCT Insert = { TVI_ROOT, TVI_LAST, { TVIF_TEXT | TVIF_STATE | TVIF_PARAM } };
212 Insert.item.pszText = szText;
213 Insert.item.lParam = entry->id;
214 Insert.item.stateMask = TVIS_STATEIMAGEMASK;
215 Insert.item.state = INDEXTOSTATEIMAGEMASK(bChecked ? I_CHECKED : I_UNCHECKED);
216 TreeView_InsertItem(hTreeView, &Insert);
217}
218
219static void CustomizeClassic_OnInitDialog(HWND hwnd)
220{
221 InitializeClearButton(hwnd);
222
223 HWND hTreeView = GetDlgItem(hwnd, IDC_CLASSICSTART_SETTINGS);
224
225 DWORD_PTR style = GetWindowLongPtrW(hTreeView, GWL_STYLE);
226 SetWindowLongPtrW(hTreeView, GWL_STYLE, style | TVS_CHECKBOXES);
227
228 for (auto& entry : s_CustomEntries)
229 {
230 AddCustomItem(hTreeView, &entry);
231 }
232}
233
234static BOOL CustomizeClassic_OnOK(HWND hwnd)
235{
236 HWND hTreeView = GetDlgItem(hwnd, IDC_CLASSICSTART_SETTINGS);
237
238 for (HTREEITEM hItem = TreeView_GetRoot(hTreeView);
239 hItem != NULL;
240 hItem = TreeView_GetNextVisible(hTreeView, hItem))
241 {
242 TV_ITEM item = { TVIF_PARAM | TVIF_STATE, hItem };
243 item.stateMask = TVIS_STATEIMAGEMASK;
244 TreeView_GetItem(hTreeView, &item);
245
246 BOOL bChecked = !!(item.state & INDEXTOSTATEIMAGEMASK(I_CHECKED));
247 for (auto& entry : s_CustomEntries)
248 {
249 if (SHRestricted(entry.policy1) || SHRestricted(entry.policy2))
250 continue;
251
252 if (item.lParam == entry.id)
253 {
254 TRACE("%p: %d\n", item.lParam, bChecked);
255 entry.fnSetValue(&entry, bChecked);
256 break;
257 }
258 }
259 }
260
261 SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)L"TraySettings",
262 SMTO_ABORTIFHUNG, 200, NULL);
263 return TRUE;
264}
265
266INT_PTR CALLBACK CustomizeClassicProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
267{
268 switch (Message)
269 {
270 case WM_INITDIALOG:
271 CustomizeClassic_OnInitDialog(hwnd);
272 return TRUE;
273 case WM_COMMAND:
274 switch (LOWORD(wParam))
275 {
276 case IDC_CLASSICSTART_ADD:
277 OnAddStartMenuItems(hwnd);
278 break;
279 case IDC_CLASSICSTART_REMOVE:
280 OnRemoveStartmenuItems(hwnd);
281 break;
282 case IDC_CLASSICSTART_ADVANCED:
283 OnAdvancedStartMenuItems();
284 break;
285 case IDC_CLASSICSTART_CLEAR:
286 ClearRecentAndMru();
287 InitializeClearButton(hwnd);
288 break;
289 case IDOK:
290 if (CustomizeClassic_OnOK(hwnd))
291 EndDialog(hwnd, IDOK);
292 break;
293 case IDCANCEL:
294 EndDialog(hwnd, IDCANCEL);
295 break;
296 }
297 break;
298 default:
299 break;
300 }
301
302 return FALSE;
303}
304
305VOID ShowCustomizeClassic(HINSTANCE hInst, HWND hExplorer)
306{
307 DialogBoxW(hInst, MAKEINTRESOURCEW(IDD_CLASSICSTART_CUSTOMIZE), hExplorer, CustomizeClassicProc);
308}