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
26 #include "wine/debug.h"
27 #include "wine/list.h"
28 #include "explorer_private.h"
31 WINE_DEFAULT_DEBUG_CHANNEL(explorer
);
38 /* parent information */
39 struct menu_item
* parent
;
40 LPITEMIDLIST pidl
; /* relative to parent; absolute if parent->pidl is NULL */
42 /* folder information */
44 struct menu_item
* base
;
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
;
56 #define MENU_ID_EXIT 2
58 static ULONG
copy_pidls(struct menu_item
* item
, LPITEMIDLIST dest
)
61 ULONG bytes_copied
= 2;
63 if (item
->parent
->pidl
)
65 bytes_copied
= copy_pidls(item
->parent
, dest
);
68 item_size
= ILGetSize(item
->pidl
);
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
)
81 length
= copy_pidls(item
, NULL
);
83 result
= CoTaskMemAlloc(length
);
85 copy_pidls(item
, 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
;
115 hr
= SHBindToParent(pidl
, &IID_IShellFolder
, (void**)&parent_folder
, &relative_pidl
);
120 hr
= IShellFolder_GetDisplayNameOf(parent_folder
, relative_pidl
, SHGDN_INFOLDER
, &strret
);
123 hr
= StrRetToStrW(&strret
, NULL
, displayname
);
127 hr
= IShellFolder_BindToObject(parent_folder
, relative_pidl
, NULL
, &IID_IShellFolder
, (void**)out_folder
);
130 IShellFolder_Release(parent_folder
);
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
)
145 IEnumIDList_Release(enumidl
);
149 IEnumIDList_Release(enumidl
);
152 if (IShellFolder_EnumObjects(folder
, NULL
, SHCONTF_FOLDERS
, &enumidl
) == S_OK
)
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
))
164 IShellFolder_Release(child_folder
);
170 IEnumIDList_Release(enumidl
);
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
;
185 int existing_item_count
, i
;
189 item
= calloc( 1, sizeof(struct menu_item
) );
191 if (parent
->pidl
== NULL
)
193 pidl_to_shellfolder(pidl
, &item
->displayname
, &item
->folder
);
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
);
218 parent_menu
= parent
->menuhandle
;
220 item
->parent
= parent
;
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
;
235 GetMenuItemInfoW(parent_menu
, i
, TRUE
, &mii
);
236 existing_item
= ((struct menu_item
*)mii
.dwItemData
);
241 /* folders before files */
242 if (existing_item
->folder
&& !item
->folder
)
244 if (!existing_item
->folder
&& item
->folder
)
247 cmp
= CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
, item
->displayname
, -1, existing_item
->displayname
, -1);
249 if (cmp
== CSTR_LESS_THAN
)
252 if (cmp
== CSTR_EQUAL
)
260 /* This item manually added to the root menu, so put it at the end */
261 i
= existing_item_count
;
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
;
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 */
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
);
306 /* duplicate shortcut, do nothing */
307 free( item
->displayname
);
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
)
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
);
348 IShellFolder_Release(item
->folder
);
350 CoTaskMemFree(item
->pidl
);
351 CoTaskMemFree(item
->displayname
);
353 list_remove(&item
->entry
);
358 static void fill_menu(struct menu_item
* item
)
360 if (!item
->menu_filled
)
362 add_folder_contents(item
);
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
);
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
)
392 LoadStringW(NULL
, IDS_EXIT_PROMPT
, prompt
, ARRAY_SIZE(prompt
));
393 ret
= MessageBoxW(hwnd
, prompt
, L
"Wine", MB_YESNO
|MB_ICONQUESTION
|MB_SYSTEMMODAL
);
398 LRESULT
menu_wndproc(HWND hwnd
, UINT msg
, WPARAM wparam
, LPARAM lparam
)
402 case WM_INITMENUPOPUP
:
404 HMENU hmenu
= (HMENU
)wparam
;
405 struct menu_item
* item
;
408 mi
.cbSize
= sizeof(mi
);
409 mi
.fMask
= MIM_MENUDATA
;
410 GetMenuInfo(hmenu
, &mi
);
411 item
= (struct menu_item
*)mi
.dwMenuData
;
421 HMENU hmenu
= (HMENU
)lparam
;
422 struct menu_item
* item
;
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
;
432 else if (mii
.wID
== MENU_ID_RUN
)
434 else if (mii
.wID
== MENU_ID_EXIT
)
443 return DefWindowProcW(hwnd
, msg
, wparam
, lparam
);
446 void do_startmenu(HWND hwnd
)
457 TRACE( "creating start menu\n" );
459 root_menu
.menuhandle
= public_startmenu
.menuhandle
= user_startmenu
.menuhandle
= CreatePopupMenu();
460 if (!root_menu
.menuhandle
)
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
);
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" );