From: Vincent Povirk Subject: [3/6] explorer: Add a start menu. Message-Id: Date: Tue, 10 Dec 2013 15:08:06 -0600 From f3757497dc237a15a2018b827735b0f5c7950589 Mon Sep 17 00:00:00 2001 From: Vincent Povirk Date: Fri, 6 Dec 2013 14:16:54 -0600 Subject: [PATCH 3/6] explorer: Add a start menu. --- programs/explorer/Makefile.in | 1 + programs/explorer/explorer_private.h | 2 + programs/explorer/startmenu.c | 419 +++++++++++++++++++++++++++++++++++ programs/explorer/systray.c | 9 + 4 files changed, 431 insertions(+) create mode 100644 programs/explorer/startmenu.c diff --git a/programs/explorer/Makefile.in b/programs/explorer/Makefile.in index 52296fe..28bf590 100644 --- a/programs/explorer/Makefile.in +++ b/programs/explorer/Makefile.in @@ -7,6 +7,7 @@ C_SRCS = \ appbar.c \ desktop.c \ explorer.c \ + startmenu.c \ systray.c RC_SRCS = explorer.rc diff --git a/programs/explorer/explorer_private.h b/programs/explorer/explorer_private.h index 74ca7f0..108a3ab 100644 --- a/programs/explorer/explorer_private.h +++ b/programs/explorer/explorer_private.h @@ -24,5 +24,7 @@ extern void manage_desktop( WCHAR *arg ); extern void initialize_systray( HMODULE graphics_driver, BOOL using_root ); extern void initialize_appbar(void); +extern void do_startmenu( HWND owner ); +extern LRESULT CALLBACK menu_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); #endif /* __WINE_EXPLORER_PRIVATE_H */ diff --git a/programs/explorer/startmenu.c b/programs/explorer/startmenu.c new file mode 100644 index 0000000..6fcca46 --- /dev/null +++ b/programs/explorer/startmenu.c @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2008 Vincent Povirk + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define UNICODE +#define COBJMACROS +#include +#include +#include +#include +#include +#include +#include "wine/debug.h" +#include "wine/list.h" +#include "explorer_private.h" + +WINE_DEFAULT_DEBUG_CHANNEL(explorer); + +struct menu_item +{ + struct list entry; + LPWSTR displayname; + + /* parent information */ + struct menu_item* parent; + LPITEMIDLIST pidl; /* relative to parent; absolute if parent->pidl is NULL */ + + /* folder information */ + IShellFolder* folder; + struct menu_item* base; + HMENU menuhandle; + BOOL menu_filled; +}; + +static struct list items = LIST_INIT(items); + +static struct menu_item root_menu = {{0}}; +static struct menu_item public_startmenu = {{0}}; +static struct menu_item user_startmenu = {{0}}; + +static ULONG copy_pidls(struct menu_item* item, LPITEMIDLIST dest) +{ + ULONG item_size; + ULONG bytes_copied = 2; + + if (item->parent->pidl) + { + bytes_copied = copy_pidls(item->parent, dest); + } + + item_size = ILGetSize(item->pidl); + + if (dest) + memcpy(((char*)dest) + bytes_copied - 2, item->pidl, item_size); + + return bytes_copied + item_size - 2; +} + +static LPITEMIDLIST build_pidl(struct menu_item* item) +{ + ULONG length; + LPITEMIDLIST result; + + length = copy_pidls(item, NULL); + + result = CoTaskMemAlloc(length); + + copy_pidls(item, result); + + return result; +} + +static void exec_item(struct menu_item* item) +{ + LPITEMIDLIST abs_pidl; + SHELLEXECUTEINFOW sei; + + abs_pidl = build_pidl(item); + + ZeroMemory(&sei, sizeof(sei)); + sei.cbSize = sizeof(sei); + sei.fMask = SEE_MASK_IDLIST; + sei.nShow = SW_SHOWNORMAL; + sei.lpIDList = abs_pidl; + + ShellExecuteExW(&sei); + + CoTaskMemFree(abs_pidl); +} + +static HRESULT pidl_to_shellfolder(LPITEMIDLIST pidl, LPWSTR *displayname, IShellFolder **out_folder) +{ + IShellFolder* parent_folder=NULL; + LPCITEMIDLIST relative_pidl=NULL; + STRRET strret; + HRESULT hr; + + hr = SHBindToParent(pidl, &IID_IShellFolder, (void**)&parent_folder, &relative_pidl); + + if (displayname) + { + if (SUCCEEDED(hr)) + hr = IShellFolder_GetDisplayNameOf(parent_folder, relative_pidl, SHGDN_INFOLDER, &strret); + + if (SUCCEEDED(hr)) + hr = StrRetToStrW(&strret, NULL, displayname); + } + + if (SUCCEEDED(hr)) + hr = IShellFolder_BindToObject(parent_folder, relative_pidl, NULL, &IID_IShellFolder, (void**)out_folder); + + if (parent_folder) + IShellFolder_Release(parent_folder); + + return hr; +} + +/* add an individual file or folder to the menu, takes ownership of pidl */ +static struct menu_item* add_shell_item(struct menu_item* parent, LPITEMIDLIST pidl) +{ + struct menu_item* item; + MENUITEMINFOW mii; + HMENU parent_menu; + int existing_item_count, i; + BOOL match = FALSE; + SFGAOF flags; + + item = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct menu_item)); + + if (parent->pidl == NULL) + { + pidl_to_shellfolder(pidl, &item->displayname, &item->folder); + } + else + { + STRRET strret; + + IShellFolder_GetDisplayNameOf(parent->folder, pidl, SHGDN_INFOLDER, &strret); + StrRetToStrW(&strret, NULL, &item->displayname); + + flags = SFGAO_FOLDER; + IShellFolder_GetAttributesOf(parent->folder, 1, (LPCITEMIDLIST*)&pidl, &flags); + + if (flags & SFGAO_FOLDER) + IShellFolder_BindToObject(parent->folder, pidl, NULL, &IID_IShellFolder, (void *)&item->folder); + } + + parent_menu = parent->menuhandle; + + item->parent = parent; + item->pidl = pidl; + + existing_item_count = GetMenuItemCount(parent_menu); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_SUBMENU|MIIM_DATA; + + /* search for an existing menu item with this name or the spot to insert this item */ + if (parent->pidl != NULL) + { + for (i=0; ifolder && !item->folder) + continue; + if (!existing_item->folder && item->folder) + break; + + cmp = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, item->displayname, -1, existing_item->displayname, -1); + + if (cmp == CSTR_LESS_THAN) + break; + + if (cmp == CSTR_EQUAL) + { + match = TRUE; + break; + } + } + } + else + /* This item manually added to the root menu, so put it at the end */ + i = existing_item_count; + + if (!match) + { + /* no existing item with the same name; just add it */ + mii.fMask = MIIM_STRING|MIIM_DATA; + mii.dwTypeData = item->displayname; + mii.dwItemData = (ULONG_PTR)item; + + if (item->folder) + { + MENUINFO mi; + item->menuhandle = CreatePopupMenu(); + mii.fMask |= MIIM_SUBMENU; + mii.hSubMenu = item->menuhandle; + + mi.cbSize = sizeof(mi); + mi.fMask = MIM_MENUDATA; + mi.dwMenuData = (ULONG_PTR)item; + SetMenuInfo(item->menuhandle, &mi); + } + + InsertMenuItemW(parent->menuhandle, i, TRUE, &mii); + + list_add_tail(&items, &item->entry); + } + else if (item->folder) + { + /* there is an existing folder with the same name, combine them */ + MENUINFO mi; + + item->base = (struct menu_item*)mii.dwItemData; + item->menuhandle = item->base->menuhandle; + + mii.dwItemData = (ULONG_PTR)item; + SetMenuItemInfoW(parent_menu, i, TRUE, &mii); + + mi.cbSize = sizeof(mi); + mi.fMask = MIM_MENUDATA; + mi.dwMenuData = (ULONG_PTR)item; + SetMenuInfo(item->menuhandle, &mi); + + list_add_tail(&items, &item->entry); + } + else { + /* duplicate shortcut, do nothing */ + HeapFree(GetProcessHeap(), 0, item->displayname); + HeapFree(GetProcessHeap(), 0, item); + CoTaskMemFree(pidl); + item = NULL; + } + + return item; +} + +static struct menu_item* add_folder_contents(struct menu_item* parent) +{ + IEnumIDList* enumidl; + + if (IShellFolder_EnumObjects(parent->folder, NULL, + SHCONTF_FOLDERS|SHCONTF_NONFOLDERS, &enumidl) == S_OK) + { + LPITEMIDLIST rel_pidl=NULL; + while (S_OK == IEnumIDList_Next(enumidl, 1, &rel_pidl, NULL)) + { + add_shell_item(parent, rel_pidl); + } + + IEnumIDList_Release(enumidl); + } + + return parent; +} + +static void destroy_menus(void) +{ + if (!root_menu.menuhandle) + return; + + DestroyMenu(root_menu.menuhandle); + root_menu.menuhandle = NULL; + + while (!list_empty(&items)) + { + struct menu_item* item; + + item = LIST_ENTRY(list_head(&items), struct menu_item, entry); + + if (item->folder) + IShellFolder_Release(item->folder); + + CoTaskMemFree(item->pidl); + CoTaskMemFree(item->displayname); + + list_remove(&item->entry); + HeapFree(GetProcessHeap(), 0, item); + } +} + +static void fill_menu(struct menu_item* item) +{ + if (!item->menu_filled) + { + add_folder_contents(item); + + if (item->base) + { + fill_menu(item->base); + } + + item->menu_filled = TRUE; + } +} + +LRESULT CALLBACK menu_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + switch (msg) + { + case WM_INITMENUPOPUP: + { + HMENU hmenu = (HMENU)wparam; + struct menu_item* item; + MENUINFO mi; + + mi.cbSize = sizeof(mi); + mi.fMask = MIM_MENUDATA; + GetMenuInfo(hmenu, &mi); + item = (struct menu_item*)mi.dwMenuData; + + if (item) + fill_menu(item); + return 0; + } + break; + + case WM_MENUCOMMAND: + { + HMENU hmenu = (HMENU)lparam; + struct menu_item* item; + MENUITEMINFOW mii; + + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_DATA; + GetMenuItemInfoW(hmenu, wparam, TRUE, &mii); + item = (struct menu_item*)mii.dwItemData; + + exec_item(item); + + destroy_menus(); + + return 0; + } + } + + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +void do_startmenu(HWND hwnd) +{ + LPITEMIDLIST pidl; + MENUINFO mi; + RECT rc={0,0,0,0}; + TPMPARAMS tpm; + + destroy_menus(); + + WINE_TRACE("creating start menu\n"); + + root_menu.menuhandle = public_startmenu.menuhandle = user_startmenu.menuhandle = CreatePopupMenu(); + if (!root_menu.menuhandle) + { + return; + } + + user_startmenu.parent = public_startmenu.parent = &root_menu; + user_startmenu.base = &public_startmenu; + user_startmenu.menu_filled = public_startmenu.menu_filled = FALSE; + + if (!user_startmenu.pidl) + SHGetSpecialFolderLocation(NULL, CSIDL_STARTMENU, &user_startmenu.pidl); + + if (!user_startmenu.folder) + pidl_to_shellfolder(user_startmenu.pidl, NULL, &user_startmenu.folder); + + if (!public_startmenu.pidl) + SHGetSpecialFolderLocation(NULL, CSIDL_COMMON_STARTMENU, &public_startmenu.pidl); + + if (!public_startmenu.folder) + pidl_to_shellfolder(public_startmenu.pidl, NULL, &public_startmenu.folder); + + fill_menu(&user_startmenu); + + AppendMenuW(root_menu.menuhandle, MF_SEPARATOR, 0, NULL); + + if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_CONTROLS, &pidl))) + add_shell_item(&root_menu, pidl); + + mi.cbSize = sizeof(mi); + mi.fMask = MIM_STYLE; + mi.dwStyle = MNS_NOTIFYBYPOS; + SetMenuInfo(root_menu.menuhandle, &mi); + + GetWindowRect(hwnd, &rc); + + tpm.cbSize = sizeof(tpm); + tpm.rcExclude = rc; + + if (!TrackPopupMenuEx(root_menu.menuhandle, + TPM_LEFTALIGN|TPM_BOTTOMALIGN|TPM_VERTICAL, + rc.left, rc.top, hwnd, &tpm)) + { + WINE_ERR("couldn't display menu\n"); + } +} + diff --git a/programs/explorer/systray.c b/programs/explorer/systray.c index 96fc61d..f263205 100644 --- a/programs/explorer/systray.c +++ b/programs/explorer/systray.c @@ -621,6 +621,15 @@ static LRESULT WINAPI tray_wndproc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM l ShowWindow( hwnd, SW_HIDE ); return 0; + case WM_COMMAND: + if ((HWND)lparam == start_button && HIWORD(wparam) == BN_CLICKED) + do_startmenu(hwnd); + break; + + case WM_INITMENUPOPUP: + case WM_MENUCOMMAND: + return menu_wndproc(hwnd, msg, wparam, lparam); + default: return DefWindowProcW( hwnd, msg, wparam, lparam ); } -- 1.8.1.2