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.
OnAddStartMenuItems(HWND hDlg)17 VOID 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 
OnRemoveStartmenuItems(HWND hDlg)29 VOID OnRemoveStartmenuItems(HWND hDlg)
30 {
31     ShellExecuteW(hDlg, L"open", L"rundll32.exe", L"appwiz.cpl,ConfigStartMenu", NULL, SW_SHOWNORMAL);
32 }
33 
OnAdvancedStartMenuItems()34 VOID 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 
RecentHasShortcut(HWND hwnd)44 static 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 
OnClearRecentItems(HWND hwnd)61 static VOID OnClearRecentItems(HWND hwnd)
62 {
63     SHAddToRecentDocs(SHARD_PIDL, NULL);
64     EnableWindow(GetDlgItem(hwnd, IDC_CLASSICSTART_CLEAR), RecentHasShortcut(hwnd));
65 }
66 
67 struct CUSTOM_ENTRY;
68 
69 typedef BOOL (CALLBACK *FN_CUSTOM_GET)(const CUSTOM_ENTRY *entry);
70 typedef VOID (CALLBACK *FN_CUSTOM_SET)(const CUSTOM_ENTRY *entry, BOOL bValue);
71 
72 struct CUSTOM_ENTRY
73 {
74     LPARAM id;
75     LPCWSTR name;
76     BOOL bDefaultValue;
77     FN_CUSTOM_GET fnGetValue;
78     FN_CUSTOM_SET fnSetValue;
79     RESTRICTIONS policy1, policy2;
80 };
81 
CustomGetAdvanced(const CUSTOM_ENTRY * entry)82 static BOOL CALLBACK CustomGetAdvanced(const CUSTOM_ENTRY *entry)
83 {
84     return GetAdvancedBool(entry->name, entry->bDefaultValue);
85 }
86 
CustomSetAdvanced(const CUSTOM_ENTRY * entry,BOOL bValue)87 static VOID CALLBACK CustomSetAdvanced(const CUSTOM_ENTRY *entry, BOOL bValue)
88 {
89     SetAdvancedDword(entry->name, bValue);
90 }
91 
CustomGetSmallStartMenu(const CUSTOM_ENTRY * entry)92 static BOOL CALLBACK CustomGetSmallStartMenu(const CUSTOM_ENTRY *entry)
93 {
94     return g_TaskbarSettings.sr.SmSmallIcons;
95 }
96 
CustomSetSmallStartMenu(const CUSTOM_ENTRY * entry,BOOL bValue)97 static VOID CALLBACK CustomSetSmallStartMenu(const CUSTOM_ENTRY *entry, BOOL bValue)
98 {
99     g_TaskbarSettings.sr.SmSmallIcons = bValue;
100 }
101 
102 static const CUSTOM_ENTRY s_CustomEntries[] =
103 {
104     {
105         IDS_ADVANCED_DISPLAY_ADMINTOOLS, L"StartMenuAdminTools", TRUE,
106         CustomGetAdvanced, CustomSetAdvanced,
107     },
108     {
109         IDS_ADVANCED_DISPLAY_FAVORITES, L"StartMenuFavorites", FALSE,
110         CustomGetAdvanced, CustomSetAdvanced,
111         REST_NOFAVORITESMENU,
112     },
113     {
114         IDS_ADVANCED_DISPLAY_LOG_OFF, L"StartMenuLogoff", FALSE,
115         CustomGetAdvanced, CustomSetAdvanced,
116         REST_STARTMENULOGOFF,
117     },
118     {
119         IDS_ADVANCED_DISPLAY_RUN, L"StartMenuRun", TRUE,
120         CustomGetAdvanced, CustomSetAdvanced,
121         REST_NORUN,
122     },
123     {
124         IDS_ADVANCED_EXPAND_MY_DOCUMENTS, L"CascadeMyDocuments", FALSE,
125         CustomGetAdvanced, CustomSetAdvanced,
126         REST_NOSMMYDOCS,
127     },
128     {
129         IDS_ADVANCED_EXPAND_MY_PICTURES, L"CascadeMyPictures", FALSE,
130         CustomGetAdvanced, CustomSetAdvanced,
131         REST_NOSMMYPICS,
132     },
133     {
134         IDS_ADVANCED_EXPAND_CONTROL_PANEL, L"CascadeControlPanel", FALSE,
135         CustomGetAdvanced, CustomSetAdvanced,
136         REST_NOSETFOLDERS, REST_NOCONTROLPANEL,
137     },
138     {
139         IDS_ADVANCED_EXPAND_PRINTERS, L"CascadePrinters", FALSE,
140         CustomGetAdvanced, CustomSetAdvanced,
141         REST_NOSETFOLDERS,
142     },
143     {
144         IDS_ADVANCED_EXPAND_NET_CONNECTIONS, L"CascadeNetworkConnections", FALSE,
145         CustomGetAdvanced, CustomSetAdvanced,
146         REST_NOSETFOLDERS, REST_NONETWORKCONNECTIONS,
147     },
148     {
149         IDS_ADVANCED_SMALL_START_MENU, NULL, FALSE,
150         CustomGetSmallStartMenu, CustomSetSmallStartMenu,
151     },
152 };
153 
AddCustomItem(HWND hTreeView,const CUSTOM_ENTRY * entry)154 static VOID AddCustomItem(HWND hTreeView, const CUSTOM_ENTRY *entry)
155 {
156     if (SHRestricted(entry->policy1) || SHRestricted(entry->policy2))
157     {
158         TRACE("%p: Restricted\n", entry->id);
159         return; // Restricted. Don't show
160     }
161 
162     WCHAR szText[MAX_PATH];
163     LoadStringW(GetModuleHandleW(L"shell32.dll"), entry->id, szText, _countof(szText));
164 
165     BOOL bChecked = entry->fnGetValue(entry);
166     TRACE("%p: %d\n", entry->id, bChecked);
167 
168     TV_INSERTSTRUCT Insert = { TVI_ROOT, TVI_LAST, { TVIF_TEXT | TVIF_STATE | TVIF_PARAM } };
169     Insert.item.pszText = szText;
170     Insert.item.lParam = entry->id;
171     Insert.item.stateMask = TVIS_STATEIMAGEMASK;
172     Insert.item.state = INDEXTOSTATEIMAGEMASK(bChecked ? I_CHECKED : I_UNCHECKED);
173     TreeView_InsertItem(hTreeView, &Insert);
174 }
175 
CustomizeClassic_OnInitDialog(HWND hwnd)176 static void CustomizeClassic_OnInitDialog(HWND hwnd)
177 {
178     EnableWindow(GetDlgItem(hwnd, IDC_CLASSICSTART_CLEAR), RecentHasShortcut(hwnd));
179 
180     HWND hTreeView = GetDlgItem(hwnd, IDC_CLASSICSTART_SETTINGS);
181 
182     DWORD_PTR style = GetWindowLongPtrW(hTreeView, GWL_STYLE);
183     SetWindowLongPtrW(hTreeView, GWL_STYLE, style | TVS_CHECKBOXES);
184 
185     for (auto& entry : s_CustomEntries)
186     {
187         AddCustomItem(hTreeView, &entry);
188     }
189 }
190 
CustomizeClassic_OnOK(HWND hwnd)191 static BOOL CustomizeClassic_OnOK(HWND hwnd)
192 {
193     HWND hTreeView = GetDlgItem(hwnd, IDC_CLASSICSTART_SETTINGS);
194 
195     for (HTREEITEM hItem = TreeView_GetRoot(hTreeView);
196          hItem != NULL;
197          hItem = TreeView_GetNextVisible(hTreeView, hItem))
198     {
199         TV_ITEM item = { TVIF_PARAM | TVIF_STATE, hItem };
200         item.stateMask = TVIS_STATEIMAGEMASK;
201         TreeView_GetItem(hTreeView, &item);
202 
203         BOOL bChecked = !!(item.state & INDEXTOSTATEIMAGEMASK(I_CHECKED));
204         for (auto& entry : s_CustomEntries)
205         {
206             if (SHRestricted(entry.policy1) || SHRestricted(entry.policy2))
207                 continue;
208 
209             if (item.lParam == entry.id)
210             {
211                 TRACE("%p: %d\n", item.lParam, bChecked);
212                 entry.fnSetValue(&entry, bChecked);
213                 break;
214             }
215         }
216     }
217 
218     SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)L"TraySettings",
219                         SMTO_ABORTIFHUNG, 200, NULL);
220     return TRUE;
221 }
222 
CustomizeClassicProc(HWND hwnd,UINT Message,WPARAM wParam,LPARAM lParam)223 INT_PTR CALLBACK CustomizeClassicProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
224 {
225     switch (Message)
226     {
227         case WM_INITDIALOG:
228             CustomizeClassic_OnInitDialog(hwnd);
229             return TRUE;
230         case WM_COMMAND:
231             switch (LOWORD(wParam))
232             {
233                 case IDC_CLASSICSTART_ADD:
234                     OnAddStartMenuItems(hwnd);
235                     break;
236                 case IDC_CLASSICSTART_REMOVE:
237                     OnRemoveStartmenuItems(hwnd);
238                     break;
239                 case IDC_CLASSICSTART_ADVANCED:
240                     OnAdvancedStartMenuItems();
241                     break;
242                 case IDC_CLASSICSTART_CLEAR:
243                     OnClearRecentItems(hwnd);
244                     break;
245                 case IDOK:
246                     if (CustomizeClassic_OnOK(hwnd))
247                         EndDialog(hwnd, IDOK);
248                     break;
249                 case IDCANCEL:
250                     EndDialog(hwnd, IDCANCEL);
251                     break;
252             }
253             break;
254         default:
255             break;
256     }
257 
258     return FALSE;
259 }
260 
ShowCustomizeClassic(HINSTANCE hInst,HWND hExplorer)261 VOID ShowCustomizeClassic(HINSTANCE hInst, HWND hExplorer)
262 {
263     DialogBoxW(hInst, MAKEINTRESOURCEW(IDD_CLASSICSTART_CUSTOMIZE), hExplorer, CustomizeClassicProc);
264 }
265