2bdeb45607573b390fe0bf5c1dfbf5f10731ab7c
[reactos.git] / reactos / dll / win32 / shell32 / CNewMenu.cpp
1 /*
2 * provides new shell item service
3 *
4 * Copyright 2007 Johannes Anderwald (johannes.anderwald@reactos.org)
5 * Copyright 2009 Andrew Hill
6 * Copyright 2012 Rafal Harabien
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 */
22
23 #include "precomp.h"
24
25 WINE_DEFAULT_DEBUG_CHANNEL(shell);
26
27 CNewMenu::CNewMenu()
28 {
29 m_wszPath = NULL;
30 m_pItems = NULL;
31 m_pLinkItem = NULL;
32 m_pSite = NULL;
33 m_hiconFolder = NULL;
34 m_hiconLink = NULL;
35 m_idCmdFirst = 0;
36 }
37
38 CNewMenu::~CNewMenu()
39 {
40 UnloadAllItems();
41
42 if (m_pidlFolder)
43 ILFree(m_pidlFolder);
44 }
45
46 void CNewMenu::UnloadItem(SHELLNEW_ITEM *pItem)
47 {
48 /* Note: free allows NULL as argument */
49 free(pItem->pData);
50 free(pItem->pwszDesc);
51 free(pItem->pwszExt);
52
53 if (pItem->hIcon)
54 DestroyIcon(pItem->hIcon);
55
56 HeapFree(GetProcessHeap(), 0, pItem);
57 }
58
59 void CNewMenu::UnloadAllItems()
60 {
61 SHELLNEW_ITEM *pCurItem;
62
63 /* Unload normal items */
64 while (m_pItems)
65 {
66 pCurItem = m_pItems;
67 m_pItems = m_pItems->pNext;
68
69 UnloadItem(pCurItem);
70 }
71
72 /* Unload link item */
73 if (m_pLinkItem)
74 UnloadItem(m_pLinkItem);
75 m_pLinkItem = NULL;
76 }
77
78 CNewMenu::SHELLNEW_ITEM *CNewMenu::LoadItem(LPCWSTR pwszExt)
79 {
80 HKEY hKey;
81 WCHAR wszBuf[MAX_PATH];
82 BYTE *pData = NULL;
83 DWORD cbData;
84
85 StringCbPrintfW(wszBuf, sizeof(wszBuf), L"%s\\ShellNew", pwszExt);
86
87 TRACE("LoadItem Keyname %s Name %s\n", debugstr_w(pwszExt), debugstr_w(wszBuf));
88
89 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, wszBuf, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
90 {
91 TRACE("Failed to open key\n");
92 return NULL;
93 }
94
95 /* Find first valid value */
96 struct
97 {
98 LPCWSTR pszName;
99 SHELLNEW_TYPE Type;
100 BOOL bNeedData;
101 BOOL bStr;
102 } Types[] = {
103 {L"FileName", SHELLNEW_TYPE_FILENAME, TRUE, TRUE},
104 {L"Command", SHELLNEW_TYPE_COMMAND, TRUE, TRUE},
105 {L"Data", SHELLNEW_TYPE_DATA, TRUE, FALSE},
106 {L"NullFile", SHELLNEW_TYPE_NULLFILE, FALSE},
107 {NULL}
108 };
109 UINT i;
110
111 for (i = 0; Types[i].pszName; ++i)
112 {
113 /* Note: We are using ANSI function because strings can be treated as data */
114 cbData = 0;
115 DWORD dwFlags = Types[i].bStr ? RRF_RT_REG_SZ : RRF_RT_ANY;
116 DWORD dwType;
117 if (RegGetValueW(hKey, NULL, Types[i].pszName, dwFlags, NULL, NULL, &cbData) == ERROR_SUCCESS)
118 {
119 if (Types[i].bNeedData && cbData > 0)
120 {
121 pData = (BYTE*)malloc(cbData);
122 RegGetValueW(hKey, NULL, Types[i].pszName, dwFlags, &dwType, pData, &cbData);
123 if (!Types[i].bStr && (dwType == REG_SZ || dwType == REG_EXPAND_SZ))
124 {
125 PBYTE pData2 = (PBYTE)malloc(cbData);
126 cbData = WideCharToMultiByte(CP_ACP, 0, (LPWSTR)pData, -1, (LPSTR)pData2, cbData, NULL, NULL);
127 free(pData);
128 pData = pData2;
129 }
130 }
131 break;
132 }
133 }
134 RegCloseKey(hKey);
135
136 /* Was any key found? */
137 if (!Types[i].pszName)
138 return NULL;
139
140 SHFILEINFO fi;
141 if (!SHGetFileInfoW(pwszExt, FILE_ATTRIBUTE_NORMAL, &fi, sizeof(fi), SHGFI_USEFILEATTRIBUTES|SHGFI_TYPENAME|SHGFI_ICON|SHGFI_SMALLICON))
142 return NULL;
143
144 /* Create new item */
145 SHELLNEW_ITEM *pNewItem = static_cast<SHELLNEW_ITEM *>(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SHELLNEW_ITEM)));
146 if (!pNewItem)
147 {
148 free(pData);
149 return NULL;
150 }
151
152 TRACE("new item %ls\n", fi.szTypeName);
153 pNewItem->Type = Types[i].Type;
154 pNewItem->pData = pData;
155 pNewItem->cbData = pData ? cbData : 0;
156 pNewItem->pwszExt = _wcsdup(pwszExt);
157 pNewItem->pwszDesc = _wcsdup(fi.szTypeName);
158 if (fi.hIcon)
159 pNewItem->hIcon = fi.hIcon;
160
161 return pNewItem;
162 }
163
164 BOOL
165 CNewMenu::LoadAllItems()
166 {
167 DWORD dwIndex = 0;
168 WCHAR wszName[MAX_PATH];
169 SHELLNEW_ITEM *pNewItem;
170 SHELLNEW_ITEM *pCurItem = NULL;
171
172 /* If there are any unload them */
173 UnloadAllItems();
174
175 /* Enumerate all extesions */
176 while (RegEnumKeyW(HKEY_CLASSES_ROOT, dwIndex++, wszName, _countof(wszName)) == ERROR_SUCCESS)
177 {
178 if (wszName[0] != L'.')
179 continue;
180
181 pNewItem = LoadItem(wszName);
182 if (pNewItem)
183 {
184 if (wcsicmp(pNewItem->pwszExt, L".lnk") == 0)
185 {
186 /* Link handler */
187 m_pLinkItem = pNewItem;
188 }
189 else
190 {
191 /* Add at the end of list */
192 if (pCurItem)
193 {
194 pCurItem->pNext = pNewItem;
195 pCurItem = pNewItem;
196 }
197 else
198 pCurItem = m_pItems = pNewItem;
199 }
200 }
201 }
202
203 if (!m_pLinkItem)
204 {
205 m_pLinkItem = static_cast<SHELLNEW_ITEM *>(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SHELLNEW_ITEM)));
206 if (m_pLinkItem)
207 {
208 m_pLinkItem->Type = SHELLNEW_TYPE_NULLFILE;
209 m_pLinkItem->pwszDesc = _wcsdup(L"Link");
210 m_pLinkItem->pwszExt = _wcsdup(L".lnk");
211 }
212 }
213
214 if (m_pItems == NULL)
215 return FALSE;
216 else
217 return TRUE;
218 }
219
220 UINT
221 CNewMenu::InsertShellNewItems(HMENU hMenu, UINT idCmdFirst, UINT Pos)
222 {
223 MENUITEMINFOW mii;
224 WCHAR wszBuf[256];
225 UINT idCmd = idCmdFirst;
226
227 if (m_pItems == NULL)
228 {
229 if (!LoadAllItems())
230 return 0;
231 }
232
233 ZeroMemory(&mii, sizeof(mii));
234 mii.cbSize = sizeof(mii);
235
236 /* Insert new folder action */
237 if (!LoadStringW(shell32_hInstance, FCIDM_SHVIEW_NEWFOLDER, wszBuf, _countof(wszBuf)))
238 wszBuf[0] = 0;
239 mii.fMask = MIIM_ID | MIIM_BITMAP | MIIM_STRING;
240 mii.dwTypeData = wszBuf;
241 mii.cch = wcslen(mii.dwTypeData);
242 mii.wID = idCmd;
243 mii.hbmpItem = HBMMENU_CALLBACK;
244 if (InsertMenuItemW(hMenu, Pos++, TRUE, &mii))
245 ++idCmd;
246
247 /* Insert new shortcut action */
248 if (!LoadStringW(shell32_hInstance, FCIDM_SHVIEW_NEWLINK, wszBuf, _countof(wszBuf)))
249 wszBuf[0] = 0;
250 mii.dwTypeData = wszBuf;
251 mii.cch = wcslen(mii.dwTypeData);
252 mii.wID = idCmd;
253 if (InsertMenuItemW(hMenu, Pos++, TRUE, &mii))
254 ++idCmd;
255
256 /* Insert seperator for custom new action */
257 mii.fMask = MIIM_TYPE | MIIM_ID;
258 mii.fType = MFT_SEPARATOR;
259 mii.wID = -1;
260 InsertMenuItemW(hMenu, Pos++, TRUE, &mii);
261
262 /* Insert rest of items */
263 mii.fMask = MIIM_ID | MIIM_BITMAP | MIIM_STRING;
264 mii.fType = 0;
265
266 SHELLNEW_ITEM *pCurItem = m_pItems;
267 while (pCurItem)
268 {
269 TRACE("szDesc %s\n", debugstr_w(pCurItem->pwszDesc));
270 mii.dwTypeData = pCurItem->pwszDesc;
271 mii.cch = wcslen(mii.dwTypeData);
272 mii.wID = idCmd;
273 if (InsertMenuItemW(hMenu, Pos++, TRUE, &mii))
274 ++idCmd;
275 pCurItem = pCurItem->pNext;
276 }
277
278 return idCmd - idCmdFirst;
279 }
280
281 CNewMenu::SHELLNEW_ITEM *CNewMenu::FindItemFromIdOffset(UINT IdOffset)
282 {
283 if (IdOffset == 0)
284 return NULL; /* Folder */
285
286 if (IdOffset == 1)
287 return m_pLinkItem; /* shortcut */
288
289 /* Find shell new item */
290 SHELLNEW_ITEM *pItem = m_pItems;
291 for (UINT i = 2; pItem; ++i)
292 {
293 if (i == IdOffset)
294 break;
295
296 pItem = pItem->pNext;
297 }
298
299 return pItem;
300 }
301
302 HRESULT CNewMenu::SelectNewItem(LPCMINVOKECOMMANDINFO lpici, LONG wEventId, UINT uFlags, LPWSTR pszName)
303 {
304 CComPtr<IShellBrowser> lpSB;
305 CComPtr<IShellView> lpSV;
306 HRESULT hr = E_FAIL;
307 LPITEMIDLIST pidl;
308 PITEMID_CHILD pidlNewItem;
309
310 /* Notify the view object about the new item */
311 SHChangeNotify(wEventId, uFlags, (LPCVOID) pszName, NULL);
312
313 /* FIXME: I think that this can be implemented using callbacks to the shell folder */
314
315 /* Note: CWM_GETISHELLBROWSER returns shell browser without adding reference */
316 lpSB = (LPSHELLBROWSER)SendMessageA(lpici->hwnd, CWM_GETISHELLBROWSER, 0, 0);
317 if (!lpSB)
318 return E_FAIL;
319
320 hr = lpSB->QueryActiveShellView(&lpSV);
321 if (FAILED(hr))
322 return hr;
323
324 /* Attempt to get the pidl of the new item */
325 hr = SHILCreateFromPathW(pszName, &pidl, NULL);
326 if (FAILED_UNEXPECTEDLY(hr))
327 return hr;
328
329 pidlNewItem = ILFindLastID(pidl);
330
331 hr = lpSV->SelectItem(pidlNewItem, SVSI_DESELECTOTHERS | SVSI_EDIT | SVSI_ENSUREVISIBLE |
332 SVSI_FOCUSED | SVSI_SELECT);
333
334 SHFree(pidl);
335
336 return hr;
337 }
338
339 HRESULT CNewMenu::CreateNewFolder(LPCMINVOKECOMMANDINFO lpici)
340 {
341 WCHAR wszPath[MAX_PATH];
342 WCHAR wszName[MAX_PATH];
343 WCHAR wszNewFolder[25];
344 HRESULT hr;
345
346 /* Get folder path */
347 hr = SHGetPathFromIDListW(m_pidlFolder, wszPath);
348 if (FAILED_UNEXPECTEDLY(hr))
349 return hr;
350
351 if (!LoadStringW(shell32_hInstance, IDS_NEWFOLDER, wszNewFolder, _countof(wszNewFolder)))
352 return E_FAIL;
353
354 /* Create the name of the new directory */
355 if (!PathYetAnotherMakeUniqueName(wszName, wszPath, NULL, wszNewFolder))
356 return E_FAIL;
357
358 /* Create the new directory and show the appropriate dialog in case of error */
359 if (SHCreateDirectory (lpici->hwnd, wszName) != ERROR_SUCCESS)
360 return E_FAIL;
361
362 /* Show and select the new item in the def view */
363 SelectNewItem(lpici, SHCNE_MKDIR, SHCNF_PATHW, wszName);
364
365 return S_OK;
366 }
367
368 HRESULT CNewMenu::CreateNewItem(SHELLNEW_ITEM *pItem, LPCMINVOKECOMMANDINFO lpcmi)
369 {
370 WCHAR wszBuf[MAX_PATH];
371 WCHAR wszPath[MAX_PATH];
372 HRESULT hr;
373
374 /* Get folder path */
375 hr = SHGetPathFromIDListW(m_pidlFolder, wszPath);
376 if (FAILED_UNEXPECTEDLY(hr))
377 return hr;
378
379 switch (pItem->Type)
380 {
381 case SHELLNEW_TYPE_COMMAND:
382 {
383 LPWSTR Ptr, pwszCmd;
384 WCHAR wszTemp[MAX_PATH];
385 STARTUPINFOW si;
386 PROCESS_INFORMATION pi;
387
388 if (!ExpandEnvironmentStringsW((LPWSTR)pItem->pData, wszBuf, MAX_PATH))
389 {
390 TRACE("ExpandEnvironmentStrings failed\n");
391 break;
392 }
393
394 /* Expand command parameter, FIXME: there can be more modifiers */
395 Ptr = wcsstr(wszBuf, L"%1");
396 if (Ptr)
397 {
398 Ptr[1] = 's';
399 StringCbPrintfW(wszTemp, sizeof(wszTemp), wszBuf, wszPath);
400 pwszCmd = wszTemp;
401 }
402 else
403 pwszCmd = wszBuf;
404
405 /* Create process */
406 ZeroMemory(&si, sizeof(si));
407 si.cb = sizeof(si);
408 if (CreateProcessW(NULL, pwszCmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
409 {
410 CloseHandle(pi.hProcess);
411 CloseHandle(pi.hThread);
412 } else
413 ERR("Failed to create process\n");
414 break;
415 }
416 case SHELLNEW_TYPE_DATA:
417 case SHELLNEW_TYPE_FILENAME:
418 case SHELLNEW_TYPE_NULLFILE:
419 {
420 BOOL bSuccess = TRUE;
421 LPWSTR pwszFilename = NULL;
422 size_t cchFilenameMax = 0;
423
424 /* Build new file name */
425 LoadStringW(shell32_hInstance, FCIDM_SHVIEW_NEW, wszBuf, _countof(wszBuf));
426 StringCchCatExW(wszPath, _countof(wszPath), L"\\", &pwszFilename, &cchFilenameMax, 0);
427 StringCchPrintfW(pwszFilename, cchFilenameMax, L"%s %s%s", wszBuf, pItem->pwszDesc, pItem->pwszExt);
428
429 /* Find unique name */
430 for (UINT i = 2; PathFileExistsW(wszPath); ++i)
431 {
432 StringCchPrintfW(pwszFilename, cchFilenameMax, L"%s %s (%u)%s", wszBuf, pItem->pwszDesc, i, pItem->pwszExt);
433 TRACE("New Filename %ls\n", pwszFilename);
434 }
435
436 /* Create new file */
437 HANDLE hFile = CreateFileW(wszPath, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
438 if (hFile != INVALID_HANDLE_VALUE)
439 {
440 if (pItem->Type == SHELLNEW_TYPE_DATA)
441 {
442 /* Write a content */
443 DWORD cbWritten;
444 WriteFile(hFile, pItem->pData, pItem->cbData, &cbWritten, NULL);
445 }
446
447 /* Close file now */
448 CloseHandle(hFile);
449 } else
450 bSuccess = FALSE;
451
452 if (pItem->Type == SHELLNEW_TYPE_FILENAME)
453 {
454 /* Copy file */
455 if (!CopyFileW((LPWSTR)pItem->pData, wszPath, FALSE))
456 ERR("Copy file failed: %ls\n", (LPWSTR)pItem->pData);
457 }
458
459 /* Show message if we failed */
460 if (bSuccess)
461 {
462 TRACE("Notifying fs %s\n", debugstr_w(wszPath));
463 SelectNewItem(lpcmi, SHCNE_CREATE, SHCNF_PATHW, wszPath);
464 }
465 else
466 {
467 StringCbPrintfW(wszBuf, sizeof(wszBuf), L"Cannot create file: %s", pwszFilename);
468 MessageBoxW(NULL, wszBuf, L"Cannot create file", MB_OK|MB_ICONERROR); // FIXME
469 }
470 break;
471 }
472 case SHELLNEW_TYPE_INVALID:
473 ERR("Invalid type\n");
474 break;
475 }
476
477 return S_OK;
478 }
479
480 HRESULT STDMETHODCALLTYPE CNewMenu::SetSite(IUnknown *pUnkSite)
481 {
482 m_pSite = pUnkSite;
483 return S_OK;
484 }
485
486 HRESULT STDMETHODCALLTYPE CNewMenu::GetSite(REFIID riid, void **ppvSite)
487 {
488 return m_pSite->QueryInterface(riid, ppvSite);
489 }
490
491 HRESULT
492 WINAPI
493 CNewMenu::QueryContextMenu(HMENU hMenu,
494 UINT indexMenu,
495 UINT idCmdFirst,
496 UINT idCmdLast,
497 UINT uFlags)
498 {
499 WCHAR wszNew[200];
500 MENUITEMINFOW mii;
501 UINT cItems = 0;
502
503 m_idCmdFirst = idCmdFirst;
504
505 TRACE("%p %p %u %u %u %u\n", this,
506 hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
507
508 if (!LoadStringW(shell32_hInstance, FCIDM_SHVIEW_NEW, wszNew, _countof(wszNew)))
509 return E_FAIL;
510
511 m_hSubMenu = CreateMenu();
512 if (!m_hSubMenu)
513 return E_FAIL;
514
515 cItems = InsertShellNewItems(m_hSubMenu, idCmdFirst, 0);
516
517 memset(&mii, 0, sizeof(mii));
518 mii.cbSize = sizeof(mii);
519 mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE | MIIM_SUBMENU;
520 mii.fType = MFT_STRING;
521 mii.wID = -1;
522 mii.dwTypeData = wszNew;
523 mii.cch = wcslen(mii.dwTypeData);
524 mii.fState = MFS_ENABLED;
525 mii.hSubMenu = m_hSubMenu;
526
527 if (!InsertMenuItemW(hMenu, indexMenu, TRUE, &mii))
528 return E_FAIL;
529
530 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cItems);
531 }
532
533 HRESULT
534 WINAPI
535 CNewMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
536 {
537 HRESULT hr = E_FAIL;
538
539 if (LOWORD(lpici->lpVerb) == 0)
540 hr = CreateNewFolder(lpici);
541 else
542 {
543 SHELLNEW_ITEM *pItem = FindItemFromIdOffset(LOWORD(lpici->lpVerb));
544 if (pItem)
545 hr = CreateNewItem(pItem, lpici);
546 }
547
548 TRACE("CNewMenu::InvokeCommand %x\n", hr);
549 return hr;
550 }
551
552 HRESULT
553 WINAPI
554 CNewMenu::GetCommandString(UINT_PTR idCmd,
555 UINT uType,
556 UINT *pwReserved,
557 LPSTR pszName,
558 UINT cchMax)
559 {
560 FIXME("%p %lu %u %p %p %u\n", this,
561 idCmd, uType, pwReserved, pszName, cchMax );
562
563 return E_NOTIMPL;
564 }
565
566 HRESULT
567 WINAPI
568 CNewMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
569 {
570 return S_OK;
571 }
572
573 HRESULT
574 WINAPI
575 CNewMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult)
576 {
577 switch (uMsg)
578 {
579 case WM_MEASUREITEM:
580 {
581 MEASUREITEMSTRUCT* lpmis = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
582 if (!lpmis || lpmis->CtlType != ODT_MENU)
583 break;
584
585 if (lpmis->itemWidth < (UINT)GetSystemMetrics(SM_CXMENUCHECK))
586 lpmis->itemWidth = GetSystemMetrics(SM_CXMENUCHECK);
587 if (lpmis->itemHeight < 16)
588 lpmis->itemHeight = 16;
589
590 if (plResult)
591 *plResult = TRUE;
592 break;
593 }
594 case WM_DRAWITEM:
595 {
596 DRAWITEMSTRUCT* lpdis = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
597 if (!lpdis || lpdis->CtlType != ODT_MENU)
598 break;
599
600 DWORD id = LOWORD(lpdis->itemID) - m_idCmdFirst;
601 HICON hIcon = 0;
602 if (id == 0)
603 hIcon = m_hiconFolder;
604 else if (id == 1)
605 hIcon = m_hiconLink;
606 else
607 {
608 SHELLNEW_ITEM *pItem = FindItemFromIdOffset(id);
609 if (pItem)
610 hIcon = pItem->hIcon;
611 }
612
613 if (!hIcon)
614 break;
615
616 DrawIconEx(lpdis->hDC,
617 2,
618 lpdis->rcItem.top + (lpdis->rcItem.bottom - lpdis->rcItem.top - 16) / 2,
619 hIcon,
620 16,
621 16,
622 0, NULL, DI_NORMAL);
623
624 if(plResult)
625 *plResult = TRUE;
626 }
627 }
628
629 return S_OK;
630 }
631
632 HRESULT WINAPI
633 CNewMenu::Initialize(LPCITEMIDLIST pidlFolder,
634 IDataObject *pdtobj, HKEY hkeyProgID)
635 {
636 m_pidlFolder = ILClone(pidlFolder);
637
638 /* Load folder and shortcut icons */
639 m_hiconFolder = (HICON)LoadImage(shell32_hInstance, MAKEINTRESOURCE(IDI_SHELL_FOLDER), IMAGE_ICON, 16, 16, LR_SHARED);
640 m_hiconLink = (HICON)LoadImage(shell32_hInstance, MAKEINTRESOURCE(IDI_SHELL_SHORTCUT), IMAGE_ICON, 16, 16, LR_SHARED);
641 return S_OK;
642 }