1 /*
2  * PROJECT:     ReactOS shell32
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Copy as Path Menu implementation
5  * COPYRIGHT:   Copyright 2024 Whindmar Saksit <whindsaks@proton.me>
6  *              Copyright 2024 Thamatip Chitpong <thamatip.chitpong@reactos.org>
7  */
8 
9 #include "precomp.h"
10 
11 WINE_DEFAULT_DEBUG_CHANNEL(shell);
12 
13 #define IDC_COPYASPATH 0
14 
15 CCopyAsPathMenu::CCopyAsPathMenu()
16 {
17 }
18 
19 CCopyAsPathMenu::~CCopyAsPathMenu()
20 {
21 }
22 
23 static DWORD
24 SetClipboard(UINT cf, const void* data, SIZE_T size)
25 {
26     BOOL succ = FALSE;
27     HGLOBAL handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, size);
28     if (handle)
29     {
30         LPVOID clipdata = GlobalLock(handle);
31         if (clipdata)
32         {
33             CopyMemory(clipdata, data, size);
34             GlobalUnlock(handle);
35             if (OpenClipboard(NULL))
36             {
37                 EmptyClipboard();
38                 succ = SetClipboardData(cf, handle) != NULL;
39                 CloseClipboard();
40             }
41         }
42         if (!succ)
43         {
44             GlobalFree(handle);
45         }
46     }
47     return succ ? ERROR_SUCCESS : GetLastError();
48 }
49 
50 static DWORD
51 SetClipboardFromString(LPCWSTR str)
52 {
53     SIZE_T cch = lstrlenW(str) + 1, size = cch * sizeof(WCHAR);
54     if (size > cch)
55         return SetClipboard(CF_UNICODETEXT, str, size);
56     else
57         return ERROR_BUFFER_OVERFLOW;
58 }
59 
60 static void
61 AppendToPathList(CStringW &paths, LPCWSTR path, DWORD index)
62 {
63     if (index)
64         paths += L"\r\n";
65     LPCWSTR quote = StrChrW(path, L' ');
66     if (quote)
67         paths += L'\"';
68     paths += path;
69     if (quote)
70         paths += L'\"';
71 }
72 
73 HRESULT
74 CCopyAsPathMenu::DoCopyAsPath(IDataObject *pdto)
75 {
76     CStringW paths;
77     DWORD i, count;
78 #if 0
79     CComPtr<IShellItemArray> array;
80     HRESULT hr = SHCreateShellItemArrayFromDataObject(pdto, IID_PPV_ARG(IShellItemArray, &array));
81     if (SUCCEEDED(hr))
82     {
83         for (i = 0, array->GetCount(&count); i < count && SUCCEEDED(hr); ++i)
84         {
85             CComPtr<IShellItem> item;
86             hr = array->GetItemAt(i, &item);
87             if (SUCCEEDED(hr))
88             {
89                 CComHeapPtr<WCHAR> path;
90                 hr = item->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &path);
91                 if (SUCCEEDED(hr))
92                 {
93                     AppendToPathList(paths, path, i);
94                 }
95             }
96         }
97     }
98 #else
99     FIXME("Implement and use SHCreateShellItemArrayFromDataObject\n");
100     CDataObjectHIDA pCIDA(pdto);
101     HRESULT hr = pCIDA.hr();
102     if (SUCCEEDED(hr))
103     {
104         for (i = 0, count = pCIDA->cidl; i < count && SUCCEEDED(hr); ++i)
105         {
106             PCUIDLIST_ABSOLUTE folder = HIDA_GetPIDLFolder(pCIDA);
107             PCUIDLIST_RELATIVE item = HIDA_GetPIDLItem(pCIDA, i);
108             CComHeapPtr<ITEMIDLIST> full;
109             hr = SHILCombine(folder, item, &full);
110             if (SUCCEEDED(hr))
111             {
112                 PCUITEMID_CHILD child;
113                 CComPtr<IShellFolder> sf;
114                 hr = SHBindToParent(full, IID_PPV_ARG(IShellFolder, &sf), &child);
115                 if (SUCCEEDED(hr))
116                 {
117                     STRRET strret;
118                     hr = sf->GetDisplayNameOf(child, SHGDN_FORPARSING, &strret);
119                     if (SUCCEEDED(hr))
120                     {
121                         CComHeapPtr<WCHAR> path;
122                         hr = StrRetToStrW(&strret, child, &path);
123                         if (SUCCEEDED(hr))
124                         {
125                             AppendToPathList(paths, path, i);
126                         }
127                     }
128                 }
129             }
130         }
131     }
132     else
133     {
134         FORMATETC fmte = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
135         STGMEDIUM stgm;
136         hr = pdto->GetData(&fmte, &stgm);
137         if (SUCCEEDED(hr))
138         {
139             for (i = 0, count = DragQueryFileW((HDROP)stgm.hGlobal, -1, NULL, 0); i < count && SUCCEEDED(hr); ++i)
140             {
141                 WCHAR path[MAX_PATH];
142                 if (DragQueryFileW((HDROP)stgm.hGlobal, i, path, _countof(path)))
143                 {
144                     AppendToPathList(paths, path, i);
145                 }
146             }
147             ReleaseStgMedium(&stgm);
148         }
149     }
150 #endif
151 
152     if (SUCCEEDED(hr))
153     {
154         DWORD err = SetClipboardFromString(paths);
155         hr = HRESULT_FROM_WIN32(err);
156     }
157 
158     return hr;
159 }
160 
161 STDMETHODIMP
162 CCopyAsPathMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
163 {
164     MENUITEMINFOW mii;
165 
166     TRACE("CCopyAsPathMenu::QueryContextMenu(%p %p %u %u %u %u)\n", this,
167           hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
168 
169     if ((uFlags & CMF_NOVERBS) || !(uFlags & CMF_EXTENDEDVERBS))
170         return MAKE_HRESULT(SEVERITY_SUCCESS, 0, idCmdFirst);
171 
172     // Insert "Copy as path"
173     CStringW strText(MAKEINTRESOURCEW(IDS_COPYASPATHMENU));
174     ZeroMemory(&mii, sizeof(mii));
175     mii.cbSize = sizeof(mii);
176     mii.fMask = MIIM_ID | MIIM_TYPE;
177     mii.fType = MFT_STRING;
178     mii.wID = idCmdFirst + IDC_COPYASPATH;
179     mii.dwTypeData = strText.GetBuffer();
180     if (InsertMenuItemW(hMenu, indexMenu, TRUE, &mii))
181         return MAKE_HRESULT(SEVERITY_SUCCESS, 0, mii.wID - idCmdFirst + 1);
182 
183     return MAKE_HRESULT(SEVERITY_SUCCESS, 0, idCmdFirst);
184 }
185 
186 STDMETHODIMP
187 CCopyAsPathMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
188 {
189     TRACE("CCopyAsPathMenu::InvokeCommand(%p %p)\n", this, lpcmi);
190 
191     if (IS_INTRESOURCE(lpcmi->lpVerb) && LOWORD(lpcmi->lpVerb) == IDC_COPYASPATH)
192         return DoCopyAsPath(m_pDataObject);
193 
194     return E_FAIL;
195 }
196 
197 STDMETHODIMP
198 CCopyAsPathMenu::GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen)
199 {
200     FIXME("CCopyAsPathMenu::GetCommandString(%p %lu %u %p %p %u)\n", this,
201           idCommand, uFlags, lpReserved, lpszName, uMaxNameLen);
202 
203     return E_NOTIMPL;
204 }
205 
206 STDMETHODIMP
207 CCopyAsPathMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
208 {
209     m_pDataObject = pdtobj;
210     return S_OK;
211 }
212