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