explorer: Add a start menu.
[wine/multimedia.git] / programs / explorer / startmenu.c
blob069bf1aabea1b885897abdc622dd128f1ee04104
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"
30 WINE_DEFAULT_DEBUG_CHANNEL(explorer);
32 struct menu_item
34 struct list entry;
35 LPWSTR displayname;
37 /* parent information */
38 struct menu_item* parent;
39 LPITEMIDLIST pidl; /* relative to parent; absolute if parent->pidl is NULL */
41 /* folder information */
42 IShellFolder* folder;
43 struct menu_item* base;
44 HMENU menuhandle;
45 BOOL menu_filled;
48 static struct list items = LIST_INIT(items);
50 static struct menu_item root_menu;
51 static struct menu_item public_startmenu;
52 static struct menu_item user_startmenu;
54 static ULONG copy_pidls(struct menu_item* item, LPITEMIDLIST dest)
56 ULONG item_size;
57 ULONG bytes_copied = 2;
59 if (item->parent->pidl)
61 bytes_copied = copy_pidls(item->parent, dest);
64 item_size = ILGetSize(item->pidl);
66 if (dest)
67 memcpy(((char*)dest) + bytes_copied - 2, item->pidl, item_size);
69 return bytes_copied + item_size - 2;
72 static LPITEMIDLIST build_pidl(struct menu_item* item)
74 ULONG length;
75 LPITEMIDLIST result;
77 length = copy_pidls(item, NULL);
79 result = CoTaskMemAlloc(length);
81 copy_pidls(item, result);
83 return result;
86 static void exec_item(struct menu_item* item)
88 LPITEMIDLIST abs_pidl;
89 SHELLEXECUTEINFOW sei;
91 abs_pidl = build_pidl(item);
93 ZeroMemory(&sei, sizeof(sei));
94 sei.cbSize = sizeof(sei);
95 sei.fMask = SEE_MASK_IDLIST;
96 sei.nShow = SW_SHOWNORMAL;
97 sei.lpIDList = abs_pidl;
99 ShellExecuteExW(&sei);
101 CoTaskMemFree(abs_pidl);
104 static HRESULT pidl_to_shellfolder(LPITEMIDLIST pidl, LPWSTR *displayname, IShellFolder **out_folder)
106 IShellFolder* parent_folder=NULL;
107 LPCITEMIDLIST relative_pidl=NULL;
108 STRRET strret;
109 HRESULT hr;
111 hr = SHBindToParent(pidl, &IID_IShellFolder, (void**)&parent_folder, &relative_pidl);
113 if (displayname)
115 if (SUCCEEDED(hr))
116 hr = IShellFolder_GetDisplayNameOf(parent_folder, relative_pidl, SHGDN_INFOLDER, &strret);
118 if (SUCCEEDED(hr))
119 hr = StrRetToStrW(&strret, NULL, displayname);
122 if (SUCCEEDED(hr))
123 hr = IShellFolder_BindToObject(parent_folder, relative_pidl, NULL, &IID_IShellFolder, (void**)out_folder);
125 if (parent_folder)
126 IShellFolder_Release(parent_folder);
128 return hr;
131 /* add an individual file or folder to the menu, takes ownership of pidl */
132 static struct menu_item* add_shell_item(struct menu_item* parent, LPITEMIDLIST pidl)
134 struct menu_item* item;
135 MENUITEMINFOW mii;
136 HMENU parent_menu;
137 int existing_item_count, i;
138 BOOL match = FALSE;
139 SFGAOF flags;
141 item = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct menu_item));
143 if (parent->pidl == NULL)
145 pidl_to_shellfolder(pidl, &item->displayname, &item->folder);
147 else
149 STRRET strret;
151 IShellFolder_GetDisplayNameOf(parent->folder, pidl, SHGDN_INFOLDER, &strret);
152 StrRetToStrW(&strret, NULL, &item->displayname);
154 flags = SFGAO_FOLDER;
155 IShellFolder_GetAttributesOf(parent->folder, 1, (LPCITEMIDLIST*)&pidl, &flags);
157 if (flags & SFGAO_FOLDER)
158 IShellFolder_BindToObject(parent->folder, pidl, NULL, &IID_IShellFolder, (void *)&item->folder);
161 parent_menu = parent->menuhandle;
163 item->parent = parent;
164 item->pidl = pidl;
166 existing_item_count = GetMenuItemCount(parent_menu);
167 mii.cbSize = sizeof(mii);
168 mii.fMask = MIIM_SUBMENU|MIIM_DATA;
170 /* search for an existing menu item with this name or the spot to insert this item */
171 if (parent->pidl != NULL)
173 for (i=0; i<existing_item_count; i++)
175 struct menu_item* existing_item;
176 int cmp;
178 GetMenuItemInfoW(parent_menu, i, TRUE, &mii);
179 existing_item = ((struct menu_item*)mii.dwItemData);
181 if (!existing_item)
182 continue;
184 /* folders before files */
185 if (existing_item->folder && !item->folder)
186 continue;
187 if (!existing_item->folder && item->folder)
188 break;
190 cmp = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, item->displayname, -1, existing_item->displayname, -1);
192 if (cmp == CSTR_LESS_THAN)
193 break;
195 if (cmp == CSTR_EQUAL)
197 match = TRUE;
198 break;
202 else
203 /* This item manually added to the root menu, so put it at the end */
204 i = existing_item_count;
206 if (!match)
208 /* no existing item with the same name; just add it */
209 mii.fMask = MIIM_STRING|MIIM_DATA;
210 mii.dwTypeData = item->displayname;
211 mii.dwItemData = (ULONG_PTR)item;
213 if (item->folder)
215 MENUINFO mi;
216 item->menuhandle = CreatePopupMenu();
217 mii.fMask |= MIIM_SUBMENU;
218 mii.hSubMenu = item->menuhandle;
220 mi.cbSize = sizeof(mi);
221 mi.fMask = MIM_MENUDATA;
222 mi.dwMenuData = (ULONG_PTR)item;
223 SetMenuInfo(item->menuhandle, &mi);
226 InsertMenuItemW(parent->menuhandle, i, TRUE, &mii);
228 list_add_tail(&items, &item->entry);
230 else if (item->folder)
232 /* there is an existing folder with the same name, combine them */
233 MENUINFO mi;
235 item->base = (struct menu_item*)mii.dwItemData;
236 item->menuhandle = item->base->menuhandle;
238 mii.dwItemData = (ULONG_PTR)item;
239 SetMenuItemInfoW(parent_menu, i, TRUE, &mii);
241 mi.cbSize = sizeof(mi);
242 mi.fMask = MIM_MENUDATA;
243 mi.dwMenuData = (ULONG_PTR)item;
244 SetMenuInfo(item->menuhandle, &mi);
246 list_add_tail(&items, &item->entry);
248 else {
249 /* duplicate shortcut, do nothing */
250 HeapFree(GetProcessHeap(), 0, item->displayname);
251 HeapFree(GetProcessHeap(), 0, item);
252 CoTaskMemFree(pidl);
253 item = NULL;
256 return item;
259 static void add_folder_contents(struct menu_item* parent)
261 IEnumIDList* enumidl;
263 if (IShellFolder_EnumObjects(parent->folder, NULL,
264 SHCONTF_FOLDERS|SHCONTF_NONFOLDERS, &enumidl) == S_OK)
266 LPITEMIDLIST rel_pidl=NULL;
267 while (S_OK == IEnumIDList_Next(enumidl, 1, &rel_pidl, NULL))
269 add_shell_item(parent, rel_pidl);
272 IEnumIDList_Release(enumidl);
276 static void destroy_menus(void)
278 if (!root_menu.menuhandle)
279 return;
281 DestroyMenu(root_menu.menuhandle);
282 root_menu.menuhandle = NULL;
284 while (!list_empty(&items))
286 struct menu_item* item;
288 item = LIST_ENTRY(list_head(&items), struct menu_item, entry);
290 if (item->folder)
291 IShellFolder_Release(item->folder);
293 CoTaskMemFree(item->pidl);
294 CoTaskMemFree(item->displayname);
296 list_remove(&item->entry);
297 HeapFree(GetProcessHeap(), 0, item);
301 static void fill_menu(struct menu_item* item)
303 if (!item->menu_filled)
305 add_folder_contents(item);
307 if (item->base)
309 fill_menu(item->base);
312 item->menu_filled = TRUE;
316 LRESULT menu_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
318 switch (msg)
320 case WM_INITMENUPOPUP:
322 HMENU hmenu = (HMENU)wparam;
323 struct menu_item* item;
324 MENUINFO mi;
326 mi.cbSize = sizeof(mi);
327 mi.fMask = MIM_MENUDATA;
328 GetMenuInfo(hmenu, &mi);
329 item = (struct menu_item*)mi.dwMenuData;
331 if (item)
332 fill_menu(item);
333 return 0;
335 break;
337 case WM_MENUCOMMAND:
339 HMENU hmenu = (HMENU)lparam;
340 struct menu_item* item;
341 MENUITEMINFOW mii;
343 mii.cbSize = sizeof(mii);
344 mii.fMask = MIIM_DATA;
345 GetMenuItemInfoW(hmenu, wparam, TRUE, &mii);
346 item = (struct menu_item*)mii.dwItemData;
348 exec_item(item);
350 destroy_menus();
352 return 0;
356 return DefWindowProcW(hwnd, msg, wparam, lparam);
359 void do_startmenu(HWND hwnd)
361 LPITEMIDLIST pidl;
362 MENUINFO mi;
363 RECT rc={0,0,0,0};
364 TPMPARAMS tpm;
366 destroy_menus();
368 WINE_TRACE("creating start menu\n");
370 root_menu.menuhandle = public_startmenu.menuhandle = user_startmenu.menuhandle = CreatePopupMenu();
371 if (!root_menu.menuhandle)
373 return;
376 user_startmenu.parent = public_startmenu.parent = &root_menu;
377 user_startmenu.base = &public_startmenu;
378 user_startmenu.menu_filled = public_startmenu.menu_filled = FALSE;
380 if (!user_startmenu.pidl)
381 SHGetSpecialFolderLocation(NULL, CSIDL_STARTMENU, &user_startmenu.pidl);
383 if (!user_startmenu.folder)
384 pidl_to_shellfolder(user_startmenu.pidl, NULL, &user_startmenu.folder);
386 if (!public_startmenu.pidl)
387 SHGetSpecialFolderLocation(NULL, CSIDL_COMMON_STARTMENU, &public_startmenu.pidl);
389 if (!public_startmenu.folder)
390 pidl_to_shellfolder(public_startmenu.pidl, NULL, &public_startmenu.folder);
392 fill_menu(&user_startmenu);
394 AppendMenuW(root_menu.menuhandle, MF_SEPARATOR, 0, NULL);
396 if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_CONTROLS, &pidl)))
397 add_shell_item(&root_menu, pidl);
399 mi.cbSize = sizeof(mi);
400 mi.fMask = MIM_STYLE;
401 mi.dwStyle = MNS_NOTIFYBYPOS;
402 SetMenuInfo(root_menu.menuhandle, &mi);
404 GetWindowRect(hwnd, &rc);
406 tpm.cbSize = sizeof(tpm);
407 tpm.rcExclude = rc;
409 if (!TrackPopupMenuEx(root_menu.menuhandle,
410 TPM_LEFTALIGN|TPM_BOTTOMALIGN|TPM_VERTICAL,
411 rc.left, rc.top, hwnd, &tpm))
413 WINE_ERR("couldn't display menu\n");