PageRenderTime 171ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/win32/shellext/CShellExtCMenu.cpp

https://bitbucket.org/tortoisehg/hgtk/
C++ | 1056 lines | 867 code | 156 blank | 33 comment | 145 complexity | 4fd9fafab7810d6b62d9de7497f0e134 MD5 | raw file
Possible License(s): GPL-2.0
  1. #include "stdafx.h"
  2. #include "TortoiseUtils.h"
  3. #include "StringUtils.h"
  4. #include "Thgstatus.h"
  5. #include "Winstat.h"
  6. #include "InitStatus.h"
  7. #include "SysInfo.h"
  8. #include "ShellExt.h"
  9. #include "RegistryConfig.h"
  10. #include "TortoiseIconBitmap.h"
  11. #include "ThgVersion.h"
  12. #include "Msi.h"
  13. #include <map>
  14. #include "CShellExtCMenu.h"
  15. struct MenuDescription
  16. {
  17. std::string name;
  18. std::wstring menuText;
  19. std::wstring helpText;
  20. std::string iconName;
  21. UINT idCmd;
  22. };
  23. // According to http://msdn.microsoft.com/en-us/library/bb776094%28VS.85%29.aspx
  24. // the help texts for the commands should be reasonably short (under 40 characters)
  25. MenuDescription menuDescList[] =
  26. {
  27. {"commit", L"Commit...",
  28. L"Commit changes in repository",
  29. "menucommit.ico", 0},
  30. {"init", L"Create Repository Here",
  31. L"Create a new repository",
  32. "menucreaterepos.ico", 0},
  33. {"clone", L"Clone...",
  34. L"Create clone here from source",
  35. "menuclone.ico", 0},
  36. {"shelve", L"Shelve Changes",
  37. L"Shelve or unshelve file changes",
  38. "shelve.ico", 0},
  39. {"status", L"View File Status",
  40. L"Repository status & changes",
  41. "menushowchanged.ico", 0},
  42. {"add", L"Add Files...",
  43. L"Add files to version control",
  44. "menuadd.ico", 0},
  45. {"revert", L"Revert Files...",
  46. L"Revert file changes",
  47. "menurevert.ico", 0},
  48. {"remove", L"Remove Files...",
  49. L"Remove files from version control",
  50. "menudelete.ico", 0},
  51. {"rename", L"Rename File...",
  52. L"Rename file or directory",
  53. "general.ico", 0},
  54. {"workbench", L"Workbench",
  55. L"View change history of repository",
  56. "menulog.ico", 0},
  57. {"log", L"Revision History",
  58. L"View change history of selected files",
  59. "menulog.ico", 0},
  60. {"synch", L"Synchronize",
  61. L"Synchronize with remote repository",
  62. "menusynch.ico", 0},
  63. {"serve", L"Web Server",
  64. L"Start web server for this repository",
  65. "proxy.ico", 0},
  66. {"update", L"Update...",
  67. L"Update working directory",
  68. "menucheckout.ico", 0},
  69. {"thgstatus", L"Update Icons",
  70. L"Update icons for this repository",
  71. "refresh_overlays.ico", 0},
  72. {"userconf", L"Global Settings",
  73. L"Configure user wide settings",
  74. "settings_user.ico", 0},
  75. {"repoconf", L"Repository Settings",
  76. L"Configure repository settings",
  77. "settings_repo.ico", 0},
  78. {"about", L"About TortoiseHg",
  79. L"Show About Dialog",
  80. "menuabout.ico", 0},
  81. {"vdiff", L"Visual Diff",
  82. L"View changes using GUI diff tool",
  83. "TortoiseMerge.ico", 0},
  84. {"hgignore", L"Edit Ignore Filter",
  85. L"Edit repository ignore filter",
  86. "ignore.ico", 0},
  87. {"guess", L"Guess Renames",
  88. L"Detect renames and copies",
  89. "detect_rename.ico", 0},
  90. {"grep", L"Search History",
  91. L"Search file revisions for patterns",
  92. "menurepobrowse.ico", 0},
  93. {"forget", L"Forget Files...",
  94. L"Remove files from version control",
  95. "menudelete.ico", 0},
  96. {"shellconf", L"Explorer Extension Settings",
  97. L"Configure Explorer extension",
  98. "settings_repo.ico", 0},
  99. /* Add new items here */
  100. // template
  101. //{"", L"", L"", ".ico", 0},
  102. };
  103. const char* const RepoNoFilesMenu =
  104. "commit status shelve vdiff sep"
  105. " add revert rename forget remove sep"
  106. " workbench update grep sep"
  107. " synch serve clone init thgstatus sep"
  108. " hgignore guess sep"
  109. " shellconf repoconf userconf sep"
  110. " about"
  111. ;
  112. const char* const RepoFilesMenu =
  113. "commit status vdiff sep"
  114. " add revert rename forget remove sep"
  115. " log sep"
  116. " about"
  117. ;
  118. const char* const NoRepoMenu =
  119. "clone init shellconf userconf thgstatus sep"
  120. " workbench sep"
  121. " about"
  122. ;
  123. typedef std::map<std::string, MenuDescription> MenuDescriptionMap;
  124. typedef std::map<UINT, MenuDescription> MenuIdCmdMap;
  125. MenuDescriptionMap MenuDescMap;
  126. MenuIdCmdMap MenuIdMap;
  127. void AddMenuList(UINT idCmd, const std::string& name)
  128. {
  129. TDEBUG_TRACE("AddMenuList: idCmd = " << idCmd << " name = " << name);
  130. MenuIdMap[idCmd] = MenuDescMap[name];
  131. }
  132. void GetCMenuTranslation(
  133. const std::string& lang,
  134. const std::string& name,
  135. std::wstring& menuText,
  136. std::wstring& helpText
  137. )
  138. {
  139. std::wstring subkey = L"Software\\TortoiseHg\\CMenu\\";
  140. subkey += _WCSTR(lang.c_str());
  141. subkey += L"\\";
  142. subkey += _WCSTR(name.c_str());
  143. TDEBUG_TRACEW(L"GetCMenuTranslation: " << subkey);
  144. HKEY hkey = 0;
  145. LONG rv = RegOpenKeyExW(
  146. HKEY_CURRENT_USER, subkey.c_str(), 0, KEY_READ, &hkey);
  147. if (rv == ERROR_SUCCESS && hkey)
  148. {
  149. GetRegSZValueW(hkey, L"menuText", menuText);
  150. GetRegSZValueW(hkey, L"helpText", helpText);
  151. }
  152. else
  153. {
  154. TDEBUG_TRACEW(
  155. L"GetCMenuTranslation: RegOpenKeyExW(\""
  156. << subkey << "\") failed"
  157. );
  158. }
  159. if (hkey)
  160. RegCloseKey(hkey);
  161. }
  162. void InitMenuMaps()
  163. {
  164. if (MenuDescMap.empty())
  165. {
  166. std::string lang;
  167. GetRegistryConfig("CMenuLang", lang);
  168. std::size_t sz = sizeof(menuDescList) / sizeof(MenuDescription);
  169. for (std::size_t i = 0; i < sz; i++)
  170. {
  171. MenuDescription md = menuDescList[i];
  172. if (md.name.size() == 0)
  173. {
  174. TDEBUG_TRACE("**** InitMenuMaps: ignoring entry with empty name");
  175. break;
  176. }
  177. TDEBUG_TRACE("InitMenuMaps: adding " << md.name);
  178. // Look for translation of menu and help text
  179. if (lang.size())
  180. GetCMenuTranslation(lang, md.name, md.menuText, md.helpText);
  181. MenuDescMap[md.name] = md;
  182. }
  183. }
  184. MenuIdMap.clear();
  185. }
  186. void InsertMenuItemWithIcon1(
  187. HMENU hMenu, UINT indexMenu, UINT idCmd,
  188. const std::wstring& menuText, const std::string& iconName)
  189. {
  190. // MFT_STRING is obsolete and should not be used (replaced by MIIM_STRING
  191. // from Win2K onward)
  192. MENUITEMINFOW mi;
  193. memset(&mi, 0, sizeof(mi));
  194. mi.cbSize = sizeof(mi);
  195. mi.fMask = MIIM_ID | MIIM_STRING;
  196. mi.dwTypeData = const_cast<wchar_t*>(menuText.c_str());
  197. mi.cch = static_cast<UINT>(menuText.length());
  198. mi.wID = idCmd;
  199. if (SysInfo::Instance().IsVistaOrLater())
  200. {
  201. HBITMAP hBmp = GetTortoiseIconBitmap(iconName);
  202. if (hBmp)
  203. {
  204. mi.fMask |= MIIM_BITMAP;
  205. mi.hbmpItem = hBmp;
  206. }
  207. else
  208. {
  209. TDEBUG_TRACE(" ***** InsertMenuItemWithIcon1: can't find " + iconName);
  210. }
  211. }
  212. else
  213. {
  214. HICON h = GetTortoiseIcon(iconName);
  215. if (h)
  216. {
  217. mi.fMask |= MIIM_BITMAP | MIIM_DATA;
  218. mi.dwItemData = (ULONG_PTR) h;
  219. mi.hbmpItem = HBMMENU_CALLBACK;
  220. }
  221. else
  222. {
  223. TDEBUG_TRACE(" ***** InsertMenuItemWithIcon1: can't find " + iconName);
  224. }
  225. }
  226. InsertMenuItemW(hMenu, indexMenu, TRUE, &mi);
  227. TDEBUG_TRACEW(
  228. L"InsertMenuItemWithIcon1(\"" << menuText << L"\") finished");
  229. }
  230. void InsertSubMenuItemWithIcon2(
  231. HMENU hMenu, HMENU hSubMenu, UINT indexMenu, UINT idCmd,
  232. const std::wstring& menuText, const std::string& iconName)
  233. {
  234. // MFT_STRING is obsolete and should not be used (replaced by MIIM_STRING
  235. // from Win2K onward)
  236. MENUITEMINFOW mi;
  237. memset(&mi, 0, sizeof(mi));
  238. mi.cbSize = sizeof(mi);
  239. mi.fMask = MIIM_SUBMENU | MIIM_ID | MIIM_STRING;
  240. mi.dwTypeData = const_cast<wchar_t*>(menuText.c_str());
  241. mi.cch = static_cast<UINT>(menuText.length());
  242. mi.wID = idCmd;
  243. mi.hSubMenu = hSubMenu;
  244. if (SysInfo::Instance().IsVistaOrLater())
  245. {
  246. HBITMAP hBmp = GetTortoiseIconBitmap(iconName);
  247. if (hBmp)
  248. {
  249. mi.fMask |= MIIM_BITMAP;
  250. mi.hbmpItem = hBmp;
  251. }
  252. else
  253. {
  254. TDEBUG_TRACE(" ***** InsertSubMenuItemWithIcon2: can't find " + iconName);
  255. }
  256. }
  257. else
  258. {
  259. HICON h = GetTortoiseIcon(iconName);
  260. if (h)
  261. {
  262. mi.fMask |= MIIM_BITMAP | MIIM_DATA;
  263. mi.dwItemData = (ULONG_PTR) h;
  264. mi.hbmpItem = HBMMENU_CALLBACK;
  265. }
  266. else
  267. {
  268. TDEBUG_TRACE(" ***** InsertSubMenuItemWithIcon2: can't find " + iconName);
  269. }
  270. }
  271. InsertMenuItemW(hMenu, indexMenu, TRUE, &mi);
  272. TDEBUG_TRACEW(
  273. L"InsertMenuItemWithIcon2(\"" << menuText << L"\") finished");
  274. }
  275. void InsertMenuItemByName(
  276. HMENU hMenu, const std::string& name, UINT indexMenu,
  277. UINT idCmd, UINT idCmdFirst, const std::wstring& prefix)
  278. {
  279. MenuDescriptionMap::iterator iter = MenuDescMap.find(name);
  280. if (iter == MenuDescMap.end())
  281. {
  282. TDEBUG_TRACE("***** InsertMenuItemByName: can't find menu info for " << name);
  283. return;
  284. }
  285. MenuDescription md = iter->second;
  286. AddMenuList(idCmd - idCmdFirst, name);
  287. InsertMenuItemWithIcon1(
  288. hMenu, indexMenu, idCmd, prefix + md.menuText, md.iconName);
  289. }
  290. const std::wstring TortoiseHgMenuEntryString = L"TortoiseHg";
  291. int HasTortoiseMenu(HMENU hMenu, bool& hasmenu)
  292. // returns -1 on error, 0 otherwise
  293. {
  294. hasmenu = false;
  295. const int count = ::GetMenuItemCount(hMenu);
  296. if (count == -1)
  297. {
  298. TDEBUG_TRACE("***** HasTortoiseMenu: GetMenuItemCount returned -1");
  299. return -1;
  300. }
  301. MENUITEMINFOW mii;
  302. for (int i = 0; i < count; ++i)
  303. {
  304. memset(&mii, 0, sizeof(MENUITEMINFOW));
  305. mii.cbSize = sizeof(MENUITEMINFOW);
  306. // first GetMenuItemInfoW call: get size of menu item string
  307. mii.fMask = MIIM_STRING;
  308. BOOL res = ::GetMenuItemInfoW(hMenu, i, true, &mii);
  309. if (res == 0) {
  310. TDEBUG_TRACE("HasTortoiseMenu: "
  311. << "first GetMenuItemInfo returned 0");
  312. continue;
  313. }
  314. if (mii.dwTypeData != MFT_STRING)
  315. {
  316. // not a string
  317. continue;
  318. }
  319. // allocate buffer for the string
  320. std::vector<wchar_t> text(mii.cch + 1);
  321. // second GetMenuItemInfoW call: get string into buffer
  322. mii.dwTypeData = &text[0];
  323. ++mii.cch; // size of buffer is one more than length of string
  324. res = ::GetMenuItemInfoW(hMenu, i, true, &mii);
  325. if (res == 0) {
  326. TDEBUG_TRACE("HasTortoiseMenu: "
  327. << "second GetMenuItemInfo returned 0");
  328. continue;
  329. }
  330. const std::wstring menuitemtext(&text[0]);
  331. //TDEBUG_TRACEW(L"HasTortoiseMenu: "
  332. // << L"menuitemtext is '" << menuitemtext << L"'");
  333. if (menuitemtext == TortoiseHgMenuEntryString)
  334. {
  335. TDEBUG_TRACE("HasTortoiseMenu: FOUND TortoiseHg menu entry");
  336. hasmenu = true;
  337. return 0;
  338. }
  339. }
  340. TDEBUG_TRACE("HasTortoiseMenu: TortoiseHg menu entry NOT found");
  341. return 0;
  342. }
  343. #define ResultFromShort(i) ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(i)))
  344. // IContextMenu
  345. STDMETHODIMP
  346. CShellExtCMenu::QueryContextMenu(
  347. HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
  348. {
  349. TDEBUG_TRACE("CShellExtCMenu::QueryContextMenu");
  350. UINT idCmd = idCmdFirst;
  351. BOOL bAppendItems = TRUE;
  352. if ((uFlags & 0x000F) == CMF_NORMAL)
  353. bAppendItems = TRUE;
  354. else if (uFlags & CMF_VERBSONLY)
  355. bAppendItems = TRUE;
  356. else if (uFlags & CMF_EXPLORE)
  357. bAppendItems = TRUE;
  358. else
  359. bAppendItems = FALSE;
  360. if (!bAppendItems)
  361. return S_OK;
  362. bool hasthgmenu = false;
  363. if (HasTortoiseMenu(hMenu, hasthgmenu) == 0 && hasthgmenu)
  364. {
  365. TDEBUG_TRACE("CShellExtCMenu::QueryContextMenu: "
  366. << "TortoiseHg menu entry already in menu -> skipping");
  367. return S_OK;
  368. }
  369. InitMenuMaps();
  370. typedef std::vector<std::string> entriesT;
  371. typedef entriesT::const_iterator entriesIter;
  372. std::string promoted_string = "commit,workbench"; // default value if key not found
  373. GetRegistryConfig("PromotedItems", promoted_string);
  374. entriesT promoted;
  375. Tokenize(promoted_string, promoted, ",");
  376. // Select menu to show
  377. bool fileMenu = myFiles.size() > 0;
  378. bool isHgrepo = false;
  379. std::string cwd;
  380. if (!myFolder.empty())
  381. {
  382. cwd = myFolder;
  383. }
  384. else if (!myFiles.empty())
  385. {
  386. cwd = IsDirectory(myFiles[0])? myFiles[0] : DirName(myFiles[0]);
  387. }
  388. if (!cwd.empty())
  389. {
  390. // check if target directory is a Mercurial repository
  391. std::string root = GetHgRepoRoot(cwd);
  392. isHgrepo = !root.empty();
  393. if (myFiles.size() == 1 && root == myFiles[0])
  394. {
  395. fileMenu = false;
  396. myFolder = cwd;
  397. myFiles.clear();
  398. }
  399. }
  400. if ((uFlags & CMF_EXTENDEDVERBS) == 0)
  401. {
  402. // shift key is not down
  403. if (!isHgrepo)
  404. {
  405. // we are not inside a repo
  406. std::string cval;
  407. if (GetRegistryConfig("HideMenuOutsideRepo", cval) != 0 && cval == "1")
  408. {
  409. return S_OK; // don't show thg cmenu entries
  410. }
  411. }
  412. }
  413. TDEBUG_TRACE(
  414. "CShellExtCMenu::QueryContextMenu: isHgrepo = "
  415. << isHgrepo << ", fileMenu = " << fileMenu
  416. );
  417. /* We have three menu types: files-selected, no-files-selected, no-repo */
  418. const char* entries_string = 0;
  419. if (isHgrepo)
  420. if (fileMenu)
  421. entries_string = RepoFilesMenu;
  422. else
  423. entries_string = RepoNoFilesMenu;
  424. else
  425. entries_string = NoRepoMenu;
  426. // start building TortoiseHg menus and submenus
  427. InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, NULL);
  428. entriesT entries;
  429. Tokenize(entries_string, entries, " ");
  430. for (entriesIter i = entries.begin(); i != entries.end(); i++)
  431. {
  432. std::string name = *i;
  433. if (contains(promoted, name))
  434. {
  435. InsertMenuItemByName(
  436. hMenu, name, indexMenu++,
  437. idCmd++, idCmdFirst, L"Hg "
  438. );
  439. }
  440. }
  441. const HMENU hSubMenu = CreatePopupMenu();
  442. if (hSubMenu)
  443. {
  444. UINT indexSubMenu = 0;
  445. bool isSeparator = true;
  446. for (entriesIter i = entries.begin(); i != entries.end(); i++)
  447. {
  448. std::string name = *i;
  449. if (name == "sep")
  450. {
  451. if (!isSeparator)
  452. {
  453. InsertMenu(
  454. hSubMenu, indexSubMenu++,
  455. MF_SEPARATOR | MF_BYPOSITION, 0, NULL
  456. );
  457. isSeparator = true;
  458. }
  459. }
  460. else
  461. {
  462. if (!contains(promoted, name))
  463. {
  464. InsertMenuItemByName(
  465. hSubMenu, name,
  466. indexSubMenu++, idCmd++, idCmdFirst, L""
  467. );
  468. isSeparator = false;
  469. }
  470. }
  471. }
  472. if (isSeparator && indexSubMenu > 0)
  473. RemoveMenu(hSubMenu, indexSubMenu - 1, MF_BYPOSITION);
  474. if (SysInfo::Instance().IsVistaOrLater())
  475. {
  476. MENUINFO MenuInfo;
  477. memset(&MenuInfo, 0, sizeof(MenuInfo));
  478. MenuInfo.cbSize = sizeof(MenuInfo);
  479. MenuInfo.fMask = MIM_STYLE | MIM_APPLYTOSUBMENUS;
  480. MenuInfo.dwStyle = MNS_CHECKORBMP;
  481. SetMenuInfo(hSubMenu, &MenuInfo);
  482. }
  483. }
  484. TDEBUG_TRACE(" CShellExtCMenu::QueryContextMenu: adding main THG menu");
  485. InsertSubMenuItemWithIcon2(hMenu, hSubMenu, indexMenu++, idCmd++,
  486. TortoiseHgMenuEntryString, "hg.ico");
  487. InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, NULL);
  488. InitStatus::check();
  489. if (SysInfo::Instance().IsVistaOrLater())
  490. {
  491. MENUINFO MenuInfo;
  492. memset(&MenuInfo, 0, sizeof(MenuInfo));
  493. MenuInfo.cbSize = sizeof(MenuInfo);
  494. MenuInfo.fMask = MIM_STYLE | MIM_APPLYTOSUBMENUS;
  495. MenuInfo.dwStyle = MNS_CHECKORBMP;
  496. SetMenuInfo(hMenu, &MenuInfo);
  497. }
  498. return ResultFromShort(idCmd - idCmdFirst);
  499. }
  500. STDMETHODIMP
  501. CShellExtCMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
  502. {
  503. TDEBUG_TRACE("CShellExtCMenu::InvokeCommand");
  504. HRESULT hr = E_INVALIDARG;
  505. if (!HIWORD(lpcmi->lpVerb))
  506. {
  507. UINT idCmd = LOWORD(lpcmi->lpVerb);
  508. TDEBUG_TRACE("CShellExtCMenu::InvokeCommand: idCmd = " << idCmd);
  509. MenuIdCmdMap::iterator iter = MenuIdMap.find(idCmd);
  510. if (iter != MenuIdMap.end())
  511. {
  512. RunDialog(iter->second.name);
  513. hr = S_OK;
  514. }
  515. else
  516. {
  517. TDEBUG_TRACE(
  518. "***** CShellExtCMenu::InvokeCommand: action not found for idCmd "
  519. << idCmd
  520. );
  521. }
  522. }
  523. return hr;
  524. }
  525. STDMETHODIMP
  526. CShellExtCMenu::GetCommandString(
  527. UINT_PTR idCmd, UINT uFlags, UINT FAR *reserved,
  528. LPSTR pszName, UINT cchMax)
  529. {
  530. // see http://msdn.microsoft.com/en-us/library/bb776094%28VS.85%29.aspx
  531. HRESULT res = S_FALSE;
  532. const char* psz = "";
  533. const wchar_t* pszw = 0;
  534. std::string sflags = "?";
  535. switch (uFlags)
  536. {
  537. case GCS_HELPTEXTW:
  538. sflags = "GCS_HELPTEXTW"; break;
  539. case GCS_HELPTEXTA:
  540. sflags = "GCS_HELPTEXTA"; break;
  541. case GCS_VALIDATEW:
  542. sflags = "GCS_VALIDATEW"; break;
  543. case GCS_VALIDATEA:
  544. sflags = "GCS_VALIDATEA"; break;
  545. case GCS_VERBW:
  546. sflags = "GCS_VERBW"; break;
  547. case GCS_VERBA:
  548. sflags = "GCS_VERBA"; break;
  549. }
  550. TDEBUG_TRACE(
  551. "CShellExtCMenu::GetCommandString: idCmd = " << idCmd
  552. << ", uFlags = " << uFlags << " (" << sflags << ")"
  553. << ", cchMax = " << cchMax
  554. );
  555. MenuIdCmdMap::iterator iter = MenuIdMap.find(static_cast<UINT>(idCmd));
  556. if (iter == MenuIdMap.end())
  557. {
  558. TDEBUG_TRACE("***** CShellExtCMenu::GetCommandString: idCmd not found");
  559. }
  560. else
  561. {
  562. TDEBUG_TRACE(
  563. "CShellExtCMenu::GetCommandString: name = \"" << iter->second.name << "\"");
  564. if (uFlags == GCS_HELPTEXTW)
  565. {
  566. pszw = iter->second.helpText.c_str();
  567. res = S_OK;
  568. size_t size = iter->second.helpText.size();
  569. if (size >= 40)
  570. {
  571. TDEBUG_TRACE(
  572. "***** CShellExtCMenu::GetCommandString: warning:"
  573. << " length of help text is " << size
  574. << ", which is not reasonably short (<40)");
  575. }
  576. }
  577. else if (uFlags == GCS_HELPTEXTA)
  578. {
  579. // we don't provide ansi help texts
  580. psz = "";
  581. res = S_OK;
  582. }
  583. else if (uFlags == GCS_VERBW || uFlags == GCS_VERBA)
  584. {
  585. #if 0
  586. psz = iter->second.name.c_str();
  587. #else
  588. // bugfix: don't provide verbs ("rename" conflicted with rename of explorer)
  589. psz = "";
  590. #endif
  591. res = S_OK;
  592. }
  593. else if (uFlags == GCS_VALIDATEW || uFlags == GCS_VALIDATEA)
  594. {
  595. res = S_OK;
  596. }
  597. }
  598. if (cchMax < 1)
  599. {
  600. TDEBUG_TRACE("CShellExtCMenu::GetCommandString: cchMax = "
  601. << cchMax << " (is <1)");
  602. return res;
  603. }
  604. size_t size = 0;
  605. if (uFlags & GCS_UNICODE)
  606. {
  607. wchar_t* const dest = reinterpret_cast<wchar_t*>(pszName);
  608. const wchar_t* const src = pszw ? pszw : _WCSTR(psz);
  609. wcsncpy(dest, src, cchMax-1);
  610. *(dest + cchMax-1) = 0;
  611. size = wcslen(src);
  612. TDEBUG_TRACEW(L"CShellExtCMenu::GetCommandString: res = " << int(res)
  613. << L", pszName (wide) = \"" << dest << L"\"");
  614. }
  615. else
  616. {
  617. strncpy(pszName, psz, cchMax-1);
  618. *(pszName + cchMax-1) = 0;
  619. size = strlen(psz);
  620. TDEBUG_TRACE("CShellExtCMenu::GetCommandString: res = " << int(res)
  621. << ", pszName = \"" << psz << "\"");
  622. }
  623. if (size > cchMax-1)
  624. {
  625. TDEBUG_TRACE(
  626. "***** CShellExtCMenu::GetCommandString: string was truncated: size = "
  627. << size << ", cchMax = " << cchMax);
  628. }
  629. return res;
  630. }
  631. STDMETHODIMP
  632. CShellExtCMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
  633. {
  634. LRESULT res;
  635. return HandleMenuMsg2(uMsg, wParam, lParam, &res);
  636. }
  637. STDMETHODIMP
  638. CShellExtCMenu::HandleMenuMsg2(
  639. UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
  640. {
  641. // A great tutorial on owner drawn menus in shell extension can be found
  642. // here: http://www.codeproject.com/shell/shellextguide7.asp
  643. LRESULT res;
  644. if (!pResult)
  645. pResult = &res;
  646. *pResult = FALSE;
  647. switch (uMsg)
  648. {
  649. case WM_MEASUREITEM:
  650. {
  651. MEASUREITEMSTRUCT* lpmis = (MEASUREITEMSTRUCT*)lParam;
  652. if (lpmis==NULL)
  653. break;
  654. lpmis->itemWidth += 2;
  655. if(lpmis->itemHeight < 16)
  656. lpmis->itemHeight = 16;
  657. *pResult = TRUE;
  658. }
  659. break;
  660. case WM_DRAWITEM:
  661. {
  662. DRAWITEMSTRUCT* lpdis = (DRAWITEMSTRUCT*)lParam;
  663. if (!lpdis || (lpdis->CtlType != ODT_MENU) || !lpdis->itemData)
  664. break; //not for a menu
  665. DrawIconEx(
  666. lpdis->hDC,
  667. lpdis->rcItem.left - 16,
  668. lpdis->rcItem.top
  669. + (lpdis->rcItem.bottom - lpdis->rcItem.top - 16) / 2,
  670. (HICON) lpdis->itemData, 16, 16,
  671. 0, 0, DI_NORMAL
  672. );
  673. *pResult = TRUE;
  674. }
  675. break;
  676. default:
  677. return S_OK;
  678. }
  679. return S_OK;
  680. }
  681. void CShellExtCMenu::RunDialog(const std::string &cmd)
  682. {
  683. std::string dir = GetTHgProgRoot();
  684. if (dir.empty())
  685. {
  686. TDEBUG_TRACE("RunDialog: THG root is empty");
  687. return;
  688. }
  689. std::string hgcmd = dir + "\\thg.exe";
  690. WIN32_FIND_DATAA data;
  691. HANDLE hfind = FindFirstFileA(hgcmd.c_str(), &data);
  692. if (hfind == INVALID_HANDLE_VALUE)
  693. {
  694. hgcmd = dir + "\\hgtk.exe";
  695. hfind = FindFirstFileA(hgcmd.c_str(), &data);
  696. if (hfind == INVALID_HANDLE_VALUE)
  697. hgcmd = dir + "\\thg.cmd";
  698. else
  699. FindClose(hfind);
  700. }
  701. else
  702. FindClose(hfind);
  703. hgcmd = Quote(hgcmd) + " --nofork " + cmd;
  704. std::string cwd;
  705. if (!myFolder.empty())
  706. {
  707. cwd = myFolder;
  708. }
  709. else if (!myFiles.empty())
  710. {
  711. cwd = IsDirectory(myFiles[0]) ? myFiles[0] : DirName(myFiles[0]);
  712. }
  713. else
  714. {
  715. TDEBUG_TRACE("***** RunDialog: can't get cwd");
  716. return;
  717. }
  718. if (!myFiles.empty())
  719. {
  720. const std::string tempfile = GetTemporaryFile();
  721. if (tempfile.empty())
  722. {
  723. TDEBUG_TRACE("***** RunDialog: error: GetTemporaryFile returned empty string");
  724. return;
  725. }
  726. TDEBUG_TRACE("RunDialog: temp file = " << tempfile);
  727. HANDLE tempfileHandle = CreateFileA(
  728. tempfile.c_str(), GENERIC_WRITE,
  729. FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0
  730. );
  731. if (tempfileHandle == INVALID_HANDLE_VALUE)
  732. {
  733. TDEBUG_TRACE("***** RunDialog: error: failed to create file " << tempfile);
  734. return;
  735. }
  736. typedef std::vector<std::string>::size_type ST;
  737. for (ST i = 0; i < myFiles.size(); i++)
  738. {
  739. DWORD dwWritten;
  740. TDEBUG_TRACE("RunDialog: temp file adding " << myFiles[i]);
  741. WriteFile(
  742. tempfileHandle, myFiles[i].c_str(),
  743. static_cast<DWORD>(myFiles[i].size()), &dwWritten, 0
  744. );
  745. WriteFile(tempfileHandle, "\n", 1, &dwWritten, 0);
  746. }
  747. CloseHandle(tempfileHandle);
  748. hgcmd += " --listfile " + Quote(tempfile);
  749. }
  750. if (cmd == "thgstatus")
  751. {
  752. if (Thgstatus::remove(cwd) != 0)
  753. {
  754. std::string p = dir + "\\TortoiseHgOverlayServer.exe";
  755. LaunchCommand(Quote(p), dir);
  756. }
  757. InitStatus::check();
  758. return;
  759. }
  760. LaunchCommand(hgcmd, cwd);
  761. InitStatus::check();
  762. }
  763. STDMETHODIMP CShellExtCMenu::Initialize(
  764. LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObj, HKEY hRegKey)
  765. {
  766. TCHAR name[MAX_PATH+1];
  767. TDEBUG_TRACE("CShellExtCMenu::Initialize");
  768. // get installed MSI product id (for debugging purposes for now)
  769. #ifdef _M_X64
  770. const char* shellexid = "{D5D1E532-CDAD-4FFD-9695-757B8A29B4BA}";
  771. #else
  772. const char* shellexid = "{728E8840-5878-4EA7-918F-281C2697ABB1}";
  773. #endif
  774. std::vector<char> product_id(50, 0);
  775. UINT msires = ::MsiGetProductCodeA(shellexid, &product_id[0]);
  776. TDEBUG_TRACE("MSI shellexid: " << shellexid);
  777. TDEBUG_TRACE("MSI msires: " << msires);
  778. TDEBUG_TRACE("MSI installed product id: " << &product_id[0]);
  779. DWORD busize = 300;
  780. std::vector<char> buf(busize, 0);
  781. msires = ::MsiGetProductInfoA(
  782. &product_id[0], INSTALLPROPERTY_INSTALLLOCATION, &buf[0], &busize);
  783. if (msires == ERROR_SUCCESS)
  784. {
  785. TDEBUG_TRACE("MSI install location: " << &buf[0]);
  786. }
  787. else
  788. {
  789. TDEBUG_TRACE("MSI install location: error " << msires);
  790. }
  791. TDEBUG_TRACEW(
  792. L"---- TortoiseHg shell extension version "
  793. << ThgVersion::get() << L"----"
  794. );
  795. TDEBUG_TRACE(" pIDFolder: " << pIDFolder);
  796. TDEBUG_TRACE(" pDataObj: " << pDataObj);
  797. myFolder.clear();
  798. myFiles.clear();
  799. if (pDataObj)
  800. {
  801. FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
  802. STGMEDIUM stg = { TYMED_HGLOBAL };
  803. if (SUCCEEDED(pDataObj->GetData(&fmt, &stg)) && stg.hGlobal)
  804. {
  805. HDROP hDrop = (HDROP) GlobalLock(stg.hGlobal);
  806. if (hDrop)
  807. {
  808. UINT uNumFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
  809. TDEBUG_TRACE(" hDrop uNumFiles = " << uNumFiles);
  810. for (UINT i = 0; i < uNumFiles; ++i) {
  811. if (DragQueryFile(hDrop, i, name, MAX_PATH) > 0)
  812. {
  813. TDEBUG_TRACE(" DragQueryFile [" << i << "] = " << name);
  814. myFiles.push_back(name);
  815. }
  816. }
  817. }
  818. else
  819. {
  820. TDEBUG_TRACE(" hDrop is NULL ");
  821. }
  822. GlobalUnlock(stg.hGlobal);
  823. if (stg.pUnkForRelease)
  824. {
  825. IUnknown* relInterface = (IUnknown*) stg.pUnkForRelease;
  826. relInterface->Release();
  827. }
  828. }
  829. else
  830. {
  831. TDEBUG_TRACE(" pDataObj->GetData failed");
  832. }
  833. }
  834. // if a directory background
  835. if (pIDFolder)
  836. {
  837. SHGetPathFromIDList(pIDFolder, name);
  838. TDEBUG_TRACE(" Folder " << name);
  839. myFolder = name;
  840. }
  841. // disable context menu if neither the folder nor the files
  842. // have been found
  843. if (myFolder.empty() && myFiles.empty()) {
  844. TDEBUG_TRACE(" shell extension not available on this object");
  845. return E_FAIL;
  846. } else {
  847. return S_OK;
  848. }
  849. }
  850. CShellExtCMenu::CShellExtCMenu(char dummy) :
  851. m_ppszFileUserClickedOn(0)
  852. {
  853. m_cRef = 0L;
  854. CShellExt::IncDllRef();
  855. }
  856. CShellExtCMenu::~CShellExtCMenu()
  857. {
  858. CShellExt::DecDllRef();
  859. }
  860. STDMETHODIMP_(ULONG) CShellExtCMenu::AddRef()
  861. {
  862. ThgCriticalSection cs(CShellExt::GetCriticalSection());
  863. return ++m_cRef;
  864. }
  865. STDMETHODIMP_(ULONG) CShellExtCMenu::Release()
  866. {
  867. ThgCriticalSection cs(CShellExt::GetCriticalSection());
  868. if(--m_cRef)
  869. return m_cRef;
  870. delete this;
  871. return 0L;
  872. }
  873. STDMETHODIMP CShellExtCMenu::QueryInterface(REFIID riid, LPVOID FAR* ppv)
  874. {
  875. if (ppv == 0)
  876. return E_POINTER;
  877. *ppv = NULL;
  878. if (IsEqualIID(riid, IID_IShellExtInit) || IsEqualIID(riid, IID_IUnknown))
  879. {
  880. *ppv = (LPSHELLEXTINIT) this;
  881. }
  882. else if (IsEqualIID(riid, IID_IContextMenu))
  883. {
  884. *ppv = (LPCONTEXTMENU) this;
  885. }
  886. else if (IsEqualIID(riid, IID_IContextMenu2))
  887. {
  888. *ppv = (IContextMenu2*) this;
  889. }
  890. else if (IsEqualIID(riid, IID_IContextMenu3))
  891. {
  892. *ppv = (IContextMenu3*) this;
  893. }
  894. if (*ppv)
  895. {
  896. AddRef();
  897. return S_OK;
  898. }
  899. return E_NOINTERFACE;
  900. }