include/mscvpdb.h: Use flexible array members for the rest of structures.
[wine.git] / programs / explorer / startmenu.c
blob63120026dad0d85b9388f0111b5dbc7039cb378d
1 /*
2 * Copyright (C) 2008 Vincent Povirk
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 #define COBJMACROS
20 #include <windows.h>
21 #include <shellapi.h>
22 #include <shlguid.h>
23 #include <shlobj.h>
24 #include <shlwapi.h>
25 #include <shobjidl.h>
26 #include "wine/debug.h"
27 #include "wine/list.h"
28 #include "explorer_private.h"
29 #include "resource.h"
31 WINE_DEFAULT_DEBUG_CHANNEL(explorer);
33 struct menu_item
35 struct list entry;
36 LPWSTR displayname;
38 /* parent information */
39 struct menu_item* parent;
40 LPITEMIDLIST pidl; /* relative to parent; absolute if parent->pidl is NULL */
42 /* folder information */
43 IShellFolder* folder;
44 struct menu_item* base;
45 HMENU menuhandle;
46 BOOL menu_filled;
49 static struct list items = LIST_INIT(items);
51 static struct menu_item root_menu;
52 static struct menu_item public_startmenu;
53 static struct menu_item user_startmenu;
55 #define MENU_ID_RUN 1
56 #define MENU_ID_EXIT 2
58 static ULONG copy_pidls(struct menu_item* item, LPITEMIDLIST dest)
60 ULONG item_size;
61 ULONG bytes_copied = 2;
63 if (item->parent->pidl)
65 bytes_copied = copy_pidls(item->parent, dest);
68 item_size = ILGetSize(item->pidl);
70 if (dest)
71 memcpy(((char*)dest) + bytes_copied - 2, item->pidl, item_size);
73 return bytes_copied + item_size - 2;
76 static LPITEMIDLIST build_pidl(struct menu_item* item)
78 ULONG length;
79 LPITEMIDLIST result;
81 length = copy_pidls(item, NULL);
83 result = CoTaskMemAlloc(length);
85 copy_pidls(item, result);
87 return result;
90 static void exec_item(struct menu_item* item)
92 LPITEMIDLIST abs_pidl;
93 SHELLEXECUTEINFOW sei;
95 abs_pidl = build_pidl(item);
97 ZeroMemory(&sei, sizeof(sei));
98 sei.cbSize = sizeof(sei);
99 sei.fMask = SEE_MASK_IDLIST;
100 sei.nShow = SW_SHOWNORMAL;
101 sei.lpIDList = abs_pidl;
103 ShellExecuteExW(&sei);
105 CoTaskMemFree(abs_pidl);
108 static HRESULT pidl_to_shellfolder(LPITEMIDLIST pidl, LPWSTR *displayname, IShellFolder **out_folder)
110 IShellFolder* parent_folder=NULL;
111 LPCITEMIDLIST relative_pidl=NULL;
112 STRRET strret;
113 HRESULT hr;
115 hr = SHBindToParent(pidl, &IID_IShellFolder, (void**)&parent_folder, &relative_pidl);
117 if (displayname)
119 if (SUCCEEDED(hr))
120 hr = IShellFolder_GetDisplayNameOf(parent_folder, relative_pidl, SHGDN_INFOLDER, &strret);
122 if (SUCCEEDED(hr))
123 hr = StrRetToStrW(&strret, NULL, displayname);
126 if (SUCCEEDED(hr))
127 hr = IShellFolder_BindToObject(parent_folder, relative_pidl, NULL, &IID_IShellFolder, (void**)out_folder);
129 if (parent_folder)
130 IShellFolder_Release(parent_folder);
132 return hr;
135 static BOOL shell_folder_is_empty(IShellFolder* folder)
137 IEnumIDList* enumidl;
138 LPITEMIDLIST pidl=NULL;
140 if (IShellFolder_EnumObjects(folder, NULL, SHCONTF_NONFOLDERS, &enumidl) == S_OK)
142 if (IEnumIDList_Next(enumidl, 1, &pidl, NULL) == S_OK)
144 CoTaskMemFree(pidl);
145 IEnumIDList_Release(enumidl);
146 return FALSE;
149 IEnumIDList_Release(enumidl);
152 if (IShellFolder_EnumObjects(folder, NULL, SHCONTF_FOLDERS, &enumidl) == S_OK)
154 BOOL found = FALSE;
155 IShellFolder *child_folder;
157 while (!found && IEnumIDList_Next(enumidl, 1, &pidl, NULL) == S_OK)
159 if (IShellFolder_BindToObject(folder, pidl, NULL, &IID_IShellFolder, (void *)&child_folder) == S_OK)
161 if (!shell_folder_is_empty(child_folder))
162 found = TRUE;
164 IShellFolder_Release(child_folder);
167 CoTaskMemFree(pidl);
170 IEnumIDList_Release(enumidl);
172 if (found)
173 return FALSE;
176 return TRUE;
179 /* add an individual file or folder to the menu, takes ownership of pidl */
180 static struct menu_item* add_shell_item(struct menu_item* parent, LPITEMIDLIST pidl)
182 struct menu_item* item;
183 MENUITEMINFOW mii;
184 HMENU parent_menu;
185 int existing_item_count, i;
186 BOOL match = FALSE;
187 SFGAOF flags;
189 item = calloc( 1, sizeof(struct menu_item) );
191 if (parent->pidl == NULL)
193 pidl_to_shellfolder(pidl, &item->displayname, &item->folder);
195 else
197 STRRET strret;
199 if (SUCCEEDED(IShellFolder_GetDisplayNameOf(parent->folder, pidl, SHGDN_INFOLDER, &strret)))
200 StrRetToStrW(&strret, NULL, &item->displayname);
202 flags = SFGAO_FOLDER;
203 IShellFolder_GetAttributesOf(parent->folder, 1, (LPCITEMIDLIST*)&pidl, &flags);
205 if (flags & SFGAO_FOLDER)
206 IShellFolder_BindToObject(parent->folder, pidl, NULL, &IID_IShellFolder, (void *)&item->folder);
209 if (item->folder && shell_folder_is_empty(item->folder))
211 IShellFolder_Release(item->folder);
212 free( item->displayname );
213 free( item );
214 CoTaskMemFree(pidl);
215 return NULL;
218 parent_menu = parent->menuhandle;
220 item->parent = parent;
221 item->pidl = pidl;
223 existing_item_count = GetMenuItemCount(parent_menu);
224 mii.cbSize = sizeof(mii);
225 mii.fMask = MIIM_SUBMENU|MIIM_DATA;
227 /* search for an existing menu item with this name or the spot to insert this item */
228 if (parent->pidl != NULL)
230 for (i=0; i<existing_item_count; i++)
232 struct menu_item* existing_item;
233 int cmp;
235 GetMenuItemInfoW(parent_menu, i, TRUE, &mii);
236 existing_item = ((struct menu_item*)mii.dwItemData);
238 if (!existing_item)
239 continue;
241 /* folders before files */
242 if (existing_item->folder && !item->folder)
243 continue;
244 if (!existing_item->folder && item->folder)
245 break;
247 cmp = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, item->displayname, -1, existing_item->displayname, -1);
249 if (cmp == CSTR_LESS_THAN)
250 break;
252 if (cmp == CSTR_EQUAL)
254 match = TRUE;
255 break;
259 else
260 /* This item manually added to the root menu, so put it at the end */
261 i = existing_item_count;
263 if (!match)
265 /* no existing item with the same name; just add it */
266 mii.fMask = MIIM_STRING|MIIM_DATA;
267 mii.dwTypeData = item->displayname;
268 mii.dwItemData = (ULONG_PTR)item;
270 if (item->folder)
272 MENUINFO mi;
273 item->menuhandle = CreatePopupMenu();
274 mii.fMask |= MIIM_SUBMENU;
275 mii.hSubMenu = item->menuhandle;
277 mi.cbSize = sizeof(mi);
278 mi.fMask = MIM_MENUDATA;
279 mi.dwMenuData = (ULONG_PTR)item;
280 SetMenuInfo(item->menuhandle, &mi);
283 InsertMenuItemW(parent->menuhandle, i, TRUE, &mii);
285 list_add_tail(&items, &item->entry);
287 else if (item->folder)
289 /* there is an existing folder with the same name, combine them */
290 MENUINFO mi;
292 item->base = (struct menu_item*)mii.dwItemData;
293 item->menuhandle = item->base->menuhandle;
295 mii.dwItemData = (ULONG_PTR)item;
296 SetMenuItemInfoW(parent_menu, i, TRUE, &mii);
298 mi.cbSize = sizeof(mi);
299 mi.fMask = MIM_MENUDATA;
300 mi.dwMenuData = (ULONG_PTR)item;
301 SetMenuInfo(item->menuhandle, &mi);
303 list_add_tail(&items, &item->entry);
305 else {
306 /* duplicate shortcut, do nothing */
307 free( item->displayname );
308 free( item );
309 CoTaskMemFree(pidl);
310 item = NULL;
313 return item;
316 static void add_folder_contents(struct menu_item* parent)
318 IEnumIDList* enumidl;
320 if (IShellFolder_EnumObjects(parent->folder, NULL,
321 SHCONTF_FOLDERS|SHCONTF_NONFOLDERS, &enumidl) == S_OK)
323 LPITEMIDLIST rel_pidl=NULL;
324 while (S_OK == IEnumIDList_Next(enumidl, 1, &rel_pidl, NULL))
326 add_shell_item(parent, rel_pidl);
329 IEnumIDList_Release(enumidl);
333 static void destroy_menus(void)
335 if (!root_menu.menuhandle)
336 return;
338 DestroyMenu(root_menu.menuhandle);
339 root_menu.menuhandle = NULL;
341 while (!list_empty(&items))
343 struct menu_item* item;
345 item = LIST_ENTRY(list_head(&items), struct menu_item, entry);
347 if (item->folder)
348 IShellFolder_Release(item->folder);
350 CoTaskMemFree(item->pidl);
351 CoTaskMemFree(item->displayname);
353 list_remove(&item->entry);
354 free( item );
358 static void fill_menu(struct menu_item* item)
360 if (!item->menu_filled)
362 add_folder_contents(item);
364 if (item->base)
366 fill_menu(item->base);
369 item->menu_filled = TRUE;
373 static void run_dialog(void)
375 void (WINAPI *pRunFileDlg)(HWND owner, HICON icon, const char *dir,
376 const char *title, const char *desc, DWORD flags);
377 HMODULE hShell32;
379 hShell32 = LoadLibraryW(L"shell32");
380 pRunFileDlg = (void*)GetProcAddress(hShell32, (LPCSTR)61);
382 pRunFileDlg(NULL, NULL, NULL, NULL, NULL, 0);
384 FreeLibrary(hShell32);
387 static void shut_down(HWND hwnd)
389 WCHAR prompt[256];
390 int ret;
392 LoadStringW(NULL, IDS_EXIT_PROMPT, prompt, ARRAY_SIZE(prompt));
393 ret = MessageBoxW(hwnd, prompt, L"Wine", MB_YESNO|MB_ICONQUESTION|MB_SYSTEMMODAL);
394 if (ret == IDYES)
395 ExitWindows(0, 0);
398 LRESULT menu_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
400 switch (msg)
402 case WM_INITMENUPOPUP:
404 HMENU hmenu = (HMENU)wparam;
405 struct menu_item* item;
406 MENUINFO mi;
408 mi.cbSize = sizeof(mi);
409 mi.fMask = MIM_MENUDATA;
410 GetMenuInfo(hmenu, &mi);
411 item = (struct menu_item*)mi.dwMenuData;
413 if (item)
414 fill_menu(item);
415 return 0;
417 break;
419 case WM_MENUCOMMAND:
421 HMENU hmenu = (HMENU)lparam;
422 struct menu_item* item;
423 MENUITEMINFOW mii;
425 mii.cbSize = sizeof(mii);
426 mii.fMask = MIIM_DATA|MIIM_ID;
427 GetMenuItemInfoW(hmenu, wparam, TRUE, &mii);
428 item = (struct menu_item*)mii.dwItemData;
430 if (item)
431 exec_item(item);
432 else if (mii.wID == MENU_ID_RUN)
433 run_dialog();
434 else if (mii.wID == MENU_ID_EXIT)
435 shut_down(hwnd);
437 destroy_menus();
439 return 0;
443 return DefWindowProcW(hwnd, msg, wparam, lparam);
446 void do_startmenu(HWND hwnd)
448 LPITEMIDLIST pidl;
449 MENUINFO mi;
450 MENUITEMINFOW mii;
451 RECT rc={0,0,0,0};
452 TPMPARAMS tpm;
453 WCHAR label[64];
455 destroy_menus();
457 TRACE( "creating start menu\n" );
459 root_menu.menuhandle = public_startmenu.menuhandle = user_startmenu.menuhandle = CreatePopupMenu();
460 if (!root_menu.menuhandle)
462 return;
465 user_startmenu.parent = public_startmenu.parent = &root_menu;
466 user_startmenu.base = &public_startmenu;
467 user_startmenu.menu_filled = public_startmenu.menu_filled = FALSE;
469 if (!user_startmenu.pidl)
470 SHGetSpecialFolderLocation(NULL, CSIDL_STARTMENU, &user_startmenu.pidl);
472 if (!user_startmenu.folder)
473 pidl_to_shellfolder(user_startmenu.pidl, NULL, &user_startmenu.folder);
475 if (!public_startmenu.pidl)
476 SHGetSpecialFolderLocation(NULL, CSIDL_COMMON_STARTMENU, &public_startmenu.pidl);
478 if (!public_startmenu.folder)
479 pidl_to_shellfolder(public_startmenu.pidl, NULL, &public_startmenu.folder);
481 if ((user_startmenu.folder && !shell_folder_is_empty(user_startmenu.folder)) ||
482 (public_startmenu.folder && !shell_folder_is_empty(public_startmenu.folder)))
484 fill_menu(&user_startmenu);
486 AppendMenuW(root_menu.menuhandle, MF_SEPARATOR, 0, NULL);
489 if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_CONTROLS, &pidl)))
490 add_shell_item(&root_menu, pidl);
492 LoadStringW(NULL, IDS_RUN, label, ARRAY_SIZE(label));
493 mii.cbSize = sizeof(mii);
494 mii.fMask = MIIM_STRING|MIIM_ID;
495 mii.dwTypeData = label;
496 mii.wID = MENU_ID_RUN;
497 InsertMenuItemW(root_menu.menuhandle, -1, TRUE, &mii);
499 mii.fMask = MIIM_FTYPE;
500 mii.fType = MFT_SEPARATOR;
501 InsertMenuItemW(root_menu.menuhandle, -1, TRUE, &mii);
503 LoadStringW(NULL, IDS_EXIT_LABEL, label, ARRAY_SIZE(label));
504 mii.fMask = MIIM_STRING|MIIM_ID;
505 mii.dwTypeData = label;
506 mii.wID = MENU_ID_EXIT;
507 InsertMenuItemW(root_menu.menuhandle, -1, TRUE, &mii);
509 mi.cbSize = sizeof(mi);
510 mi.fMask = MIM_STYLE;
511 mi.dwStyle = MNS_NOTIFYBYPOS;
512 SetMenuInfo(root_menu.menuhandle, &mi);
514 GetWindowRect(hwnd, &rc);
516 tpm.cbSize = sizeof(tpm);
517 tpm.rcExclude = rc;
519 if (!TrackPopupMenuEx(root_menu.menuhandle,
520 TPM_LEFTALIGN|TPM_BOTTOMALIGN|TPM_VERTICAL,
521 rc.left, rc.top, hwnd, &tpm))
523 ERR( "couldn't display menu\n" );