[3/6] explorer: Add a start menu.

Vincent Povirk madewokherd at gmail.com
Tue Dec 10 15:08:06 CST 2013


-------------- next part --------------
From f3757497dc237a15a2018b827735b0f5c7950589 Mon Sep 17 00:00:00 2001
From: Vincent Povirk <vincent at codeweavers.com>
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 <windows.h>
+#include <shellapi.h>
+#include <shlguid.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+#include <shobjidl.h>
+#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; i<existing_item_count; i++)
+        {
+            struct menu_item* existing_item;
+            int cmp;
+
+            GetMenuItemInfoW(parent_menu, i, TRUE, &mii);
+            existing_item = ((struct menu_item*)mii.dwItemData);
+
+            if (!existing_item)
+                continue;
+
+            /* folders before files */
+            if (existing_item->folder && !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


More information about the wine-patches mailing list