1 /////////////////////////////////////////////////////////////////////////////
3 // This program is free software; you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation; either version 2 of the License, or (at
6 // your option) any later version.
8 // This program is distributed in the hope that it will be useful, but
9 // WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16 /////////////////////////////////////////////////////////////////////////////
17 // Look at http://www.codeproject.com/shell/ for excellent guide
18 // to Windows Shell programming by Michael Dunn.
20 // This extension needs two registry values to be defined:
21 // HKEY_CURRENT_USER\Software\Thingamahoochie\WinMerge\ContextMenuEnabled
22 // defines if context menu is shown (extension enabled) and if
23 // we show simple or advanced menu
24 // HKEY_CURRENT_USER\Software\Thingamahoochie\WinMerge\Executable
25 // contains path to program to run (can be batch file too)
27 // HKEY_CURRENT_USER\Software\Thingamahoochie\WinMerge\FirstSelection
28 // is used to store path for first selection in advanced mode
30 // HKEY_CURRENT_USER\Software\Thingamahoochie\WinMerge\PriExecutable
31 // overwrites 'Executable' if defined. Useful to overwrite
32 // option set from UI when debugging/testing.
33 /////////////////////////////////////////////////////////////////////////////
35 * @file WinMergeShell.cpp
37 * @brief Implementation of the ShellExtension class
39 // ID line follows -- this is updated by SVN
40 // $Id: WinMergeShell.cpp 6933 2009-07-26 14:07:03Z kimmov $
43 #include "ShellExtension.h"
44 #include "WinMergeShell.h"
45 #include "UnicodeString.h"
47 #include <sys/types.h>
50 OBJECT_ENTRY_AUTO(CLSID_WinMergeShell, CWinMergeShell)
53 * @brief Flags for enabling and other settings of context menu.
57 EXT_ENABLED = 0x01, /**< ShellExtension enabled/disabled. */
58 EXT_ADVANCED = 0x02, /**< Advanced menuitems enabled/disabled. */
68 CMD_LAST = CMD_RESELECT_LEFT,
71 /// Max. filecount to select
72 static const int MaxFileCount = 3;
73 /// Registry path to WinMerge
74 #define REGDIR _T("Software\\Thingamahoochie\\WinMerge")
75 static const TCHAR f_RegDir[] = REGDIR;
76 static const TCHAR f_RegLocaleDir[] = REGDIR _T("\\Locale");
77 static const TCHAR f_RegSettingsDir[] = REGDIR _T("\\Settings");
80 * @name Registry valuenames.
83 /** Shell context menuitem enabled/disabled */
84 static const TCHAR f_RegValueEnabled[] = _T("ContextMenuEnabled");
85 /** 'Saved' path in advanced mode */
86 static const TCHAR f_FirstSelection[] = _T("FirstSelection");
87 /** 'Saved' path in advanced mode */
88 static const TCHAR f_SecondSelection[] = _T("SecondSelection");
89 /** Path to WinMergeU.exe */
90 static const TCHAR f_RegValuePath[] = _T("Executable");
91 /** Path to WinMergeU.exe, overwrites f_RegValuePath if present. */
92 static const TCHAR f_RegValuePriPath[] = _T("PriExecutable");
94 static const TCHAR f_LanguageId[] = _T("LanguageId");
96 static const TCHAR f_Recurse[] = _T("Recurse");
100 * @brief The states in which the menu can be.
101 * These states define what items are added to the menu and how those
106 MENU_SIMPLE = 0, /**< Simple menu, only "Compare item" is shown. */
107 MENU_ONESEL_NOPREV, /**< One item selected, no previous selections. */
108 MENU_ONESEL_PREV, /**< One item selected, previous selection exists. */
109 MENU_ONESEL_TWO_PREV, /**< One item selected, two previous selections exist. */
110 MENU_TWOSEL, /**< Two items are selected. */
114 // GreyMerlin (03 Sept 2017) - The following Version Info checking code is a
115 // short extract from the Microsoft <versionhelpers.h> file. Unfortunatly,
116 // that file is not available for WinXP-compatible Platform Toolsets (e.g.
117 // v141_xp for VS2017). Fortunatly, all the actual API interfaces do exist
118 // in WinXP (actually, in all Windows products since Win2000). Use of this
119 // <versionhelpers.h> code avoids the unpleasant deprecation of the GetVersionEx()
120 // API begining with Win 8.1. This Version Info checking code is also fully
121 // compatible with all non-XP-compatible Toolsets as well (e.g. v141).
123 #ifndef _WIN32_WINNT_VISTA
124 #define _WIN32_WINNT_VISTA 0x0600
126 #ifndef _WIN32_WINNT_WIN8
127 #define _WIN32_WINNT_WIN8 0x0602
130 #ifndef VERSIONHELPERAPI
131 #define VERSIONHELPERAPI inline bool
134 IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor)
136 OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 };
137 DWORDLONG const dwlConditionMask = VerSetConditionMask(
140 0, VER_MAJORVERSION, VER_GREATER_EQUAL),
141 VER_MINORVERSION, VER_GREATER_EQUAL),
142 VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
144 osvi.dwMajorVersion = wMajorVersion;
145 osvi.dwMinorVersion = wMinorVersion;
146 osvi.wServicePackMajor = wServicePackMajor;
148 return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;
153 IsWindows8OrGreater()
155 return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0);
157 #endif // VERSIONHELPERAPI
159 HBITMAP ConvertHICONtoHBITMAP(HICON hIcon, int cx, int cy)
162 BITMAPINFO bmi = { { sizeof(BITMAPINFOHEADER), cx, cy, 1, IsWindows8OrGreater() ? 32u : 24u } };
163 HDC hdcMem = CreateCompatibleDC(NULL);
164 HBITMAP hbmp = CreateDIBSection(NULL, (BITMAPINFO*)&bmi, DIB_RGB_COLORS, &lpBits, NULL, 0);
167 HBITMAP hbmpPrev = (HBITMAP)SelectObject(hdcMem, hbmp);
168 RECT rc = { 0, 0, cx, cy };
169 if (bmi.bmiHeader.biBitCount <= 24)
171 SetBkColor(hdcMem, GetSysColor(COLOR_MENU));
172 ExtTextOut(hdcMem, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
174 DrawIconEx(hdcMem, 0, 0, hIcon, cx, cy, 0, NULL, DI_NORMAL);
175 SelectObject(hdcMem, hbmpPrev);
181 /////////////////////////////////////////////////////////////////////////////
184 /// Default constructor, loads icon bitmap
185 CWinMergeShell::CWinMergeShell()
186 : m_dwContextMenuEnabled(false)
187 , m_nSelectedItems(0)
189 , m_langID(GetUserDefaultUILanguage())
191 int cx = GetSystemMetrics(SM_CXMENUCHECK);
192 int cy = GetSystemMetrics(SM_CYMENUCHECK);
194 // compress or stretch icon bitmap according to menu item height
195 HICON hMergeIcon = (HICON)LoadImage(_AtlComModule.m_hInstTypeLib, MAKEINTRESOURCE(IDI_WINMERGE), IMAGE_ICON,
196 cx, cy, LR_DEFAULTCOLOR);
197 HICON hMergeDirIcon = (HICON)LoadImage(_AtlComModule.m_hInstTypeLib, MAKEINTRESOURCE(IDI_WINMERGEDIR), IMAGE_ICON,
198 cx, cy, LR_DEFAULTCOLOR);
200 m_MergeBmp = ConvertHICONtoHBITMAP(hMergeIcon, cx, cy);
201 m_MergeDirBmp = ConvertHICONtoHBITMAP(hMergeDirIcon, cx, cy);
203 DestroyIcon(hMergeIcon);
204 DestroyIcon(hMergeDirIcon);
207 if (reg.Open(HKEY_CURRENT_USER, f_RegLocaleDir) == ERROR_SUCCESS)
208 m_langID = static_cast<LANGID>(reg.ReadDword(f_LanguageId, m_langID));
212 /// Default destructor, unloads bitmap
213 CWinMergeShell::~CWinMergeShell()
215 DeleteObject(m_MergeDirBmp);
216 DeleteObject(m_MergeBmp);
219 /// Reads selected paths
220 HRESULT CWinMergeShell::Initialize(LPCITEMIDLIST pidlFolder,
221 LPDATAOBJECT pDataObj, HKEY hProgID)
223 HRESULT hr = E_INVALIDARG;
225 for (auto& path: m_strPaths)
228 // Files/folders selected normally from the explorer
231 FORMATETC fmt = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
232 STGMEDIUM stg = {TYMED_HGLOBAL};
235 // Look for CF_HDROP data in the data object.
236 if (FAILED(pDataObj->GetData(&fmt, &stg)))
237 // Nope! Return an "invalid argument" error back to Explorer.
240 // Get a pointer to the actual data.
241 hDropInfo = (HDROP) GlobalLock(stg.hGlobal);
243 // Make sure it worked.
244 if (NULL == hDropInfo)
247 // Sanity check & make sure there is at least one filename.
248 UINT uNumFilesDropped = DragQueryFile(hDropInfo, 0xFFFFFFFF, NULL, 0);
249 m_nSelectedItems = uNumFilesDropped;
251 if (uNumFilesDropped == 0)
253 GlobalUnlock(stg.hGlobal);
254 ReleaseStgMedium(&stg);
260 // Get all file names.
261 for (WORD x = 0 ; x < uNumFilesDropped; x++)
263 // Get the number of bytes required by the file's full pathname
264 UINT wPathnameSize = DragQueryFile(hDropInfo, x, NULL, 0);
266 // Allocate memory to contain full pathname & zero byte
268 LPTSTR npszFile = (TCHAR *) new TCHAR[wPathnameSize];
270 // If not enough memory, skip this one
271 if (npszFile == NULL)
274 // Copy the pathname into the buffer
275 DragQueryFile(hDropInfo, x, npszFile, wPathnameSize);
277 if (x < MaxFileCount)
278 m_strPaths[x] = npszFile;
282 GlobalUnlock(stg.hGlobal);
283 ReleaseStgMedium(&stg);
287 m_nSelectedItems = 0;
290 // No item selected - selection is the folder background
293 TCHAR szPath[MAX_PATH] = {0};
295 if (SHGetPathFromIDList(pidlFolder, szPath))
297 if (m_nSelectedItems < MaxFileCount)
298 m_strPaths[m_nSelectedItems++] = szPath;
309 /// Adds context menu item
310 HRESULT CWinMergeShell::QueryContextMenu(HMENU hmenu, UINT uMenuIndex,
311 UINT uidFirstCmd, UINT uidLastCmd, UINT uFlags)
313 // check whether menu items are already added
314 if (hmenu == s_hMenuLastAdded)
316 MENUITEMINFO mii{ sizeof mii };
317 mii.fMask = MIIM_DATA;
318 if (GetMenuItemInfo(hmenu, s_uidCmdLastAdded, FALSE, &mii))
320 if (mii.dwItemData >= IDS_COMPARE && mii.dwItemData <= IDS_RESELECT_LEFT)
321 return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
325 s_hMenuLastAdded = hmenu;
326 s_uidCmdLastAdded = uidFirstCmd;
328 int uidUserLastCmd = 0;
330 // If the flags include CMF_DEFAULTONLY then we shouldn't do anything.
331 if (uFlags & CMF_DEFAULTONLY)
332 return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
334 // Check if user wants to use context menu
336 if (reg.Open(HKEY_CURRENT_USER, f_RegDir) != ERROR_SUCCESS)
337 return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
339 m_dwContextMenuEnabled = reg.ReadDword(f_RegValueEnabled, 0);
340 m_strPreviousPath[0] = reg.ReadString(f_FirstSelection, _T("")).c_str();
341 m_strPreviousPath[1] = reg.ReadString(f_SecondSelection, _T("")).c_str();
343 if (m_dwContextMenuEnabled & EXT_ENABLED) // Context menu enabled
345 // Check if advanced menuitems enabled
346 if ((m_dwContextMenuEnabled & EXT_ADVANCED) == 0)
348 m_dwMenuState = MENU_SIMPLE;
349 uidUserLastCmd = DrawSimpleMenu(hmenu, uMenuIndex, uidFirstCmd);
353 if (m_nSelectedItems == 1 && m_strPreviousPath[0].empty())
354 m_dwMenuState = MENU_ONESEL_NOPREV;
355 else if (m_nSelectedItems == 1 && !m_strPreviousPath[0].empty() && m_strPreviousPath[1].empty())
356 m_dwMenuState = MENU_ONESEL_PREV;
357 else if (m_nSelectedItems == 1 && !m_strPreviousPath[0].empty() && !m_strPreviousPath[1].empty())
358 m_dwMenuState = MENU_ONESEL_TWO_PREV;
359 else if (m_nSelectedItems == 2)
360 m_dwMenuState = MENU_TWOSEL;
361 else if (m_nSelectedItems == 3)
362 m_dwMenuState = MENU_THREESEL;
364 uidUserLastCmd = DrawAdvancedMenu(hmenu, uMenuIndex, uidFirstCmd);
367 return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, (uidUserLastCmd - uidFirstCmd) + 1);
369 return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
372 /// Gets string shown explorer's status bar when menuitem selected
373 HRESULT CWinMergeShell::GetCommandString(UINT_PTR idCmd, UINT uFlags,
374 UINT* pwReserved, LPSTR pszName, UINT cchMax)
378 // Check idCmd, it must be 0 in simple mode and 0 or 1 in advanced mode.
379 if ((m_dwMenuState & EXT_ADVANCED) == 0)
390 // If Explorer is asking for a help string, copy our string into the
392 if (uFlags & GCS_HELPTEXT)
394 String strHelp = GetHelpText(idCmd);
396 if (uFlags & GCS_UNICODE)
397 // We need to cast pszName to a Unicode string, and then use the
398 // Unicode string copy API.
399 lstrcpynW((LPWSTR) pszName, T2CW(strHelp.c_str()), cchMax);
401 // Use the ANSI string copy API to return the help string.
402 lstrcpynA(pszName, T2CA(strHelp.c_str()), cchMax);
409 /// Runs WinMerge with given paths
410 HRESULT CWinMergeShell::InvokeCommand(LPCMINVOKECOMMANDINFO pCmdInfo)
413 String strWinMergePath;
414 BOOL bCompare = FALSE;
415 BOOL bAlterSubFolders = FALSE;
417 // If lpVerb really points to a string, ignore this function call and bail out.
418 if (HIWORD(pCmdInfo->lpVerb) != 0)
421 // Read WinMerge location from registry
422 if (!GetWinMergeDir(strWinMergePath))
425 // Check that file we are trying to execute exists
426 if (!PathFileExists(strWinMergePath.c_str()))
429 if (LOWORD(pCmdInfo->lpVerb) == CMD_COMPARE)
431 switch (m_dwMenuState)
437 case MENU_ONESEL_PREV:
438 m_strPaths[1] = m_strPaths[0];
439 m_strPaths[0] = m_strPreviousPath[0];
442 // Forget previous selection
443 if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
445 reg.WriteString(f_FirstSelection, _T(""));
446 reg.WriteString(f_SecondSelection, _T(""));
450 case MENU_ONESEL_TWO_PREV:
451 m_strPaths[2] = m_strPaths[0];
452 m_strPaths[0] = m_strPreviousPath[0];
453 m_strPaths[1] = m_strPreviousPath[1];
456 // Forget previous selection
457 if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
459 reg.WriteString(f_FirstSelection, _T(""));
460 reg.WriteString(f_SecondSelection, _T(""));
466 // "Compare" - compare paths
468 m_strPreviousPath[0].erase();
469 m_strPreviousPath[1].erase();
473 else if (LOWORD(pCmdInfo->lpVerb) == CMD_COMPARE_ELLIPSE)
475 // "Compare..." - user wants to compare this single item and open WinMerge
476 m_strPaths[1].erase();
477 m_strPaths[2].erase();
480 else if (LOWORD(pCmdInfo->lpVerb) == CMD_SELECT_LEFT)
483 m_strPreviousPath[0] = m_strPaths[0];
484 if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
485 reg.WriteString(f_FirstSelection, m_strPreviousPath[0].c_str());
487 else if (LOWORD(pCmdInfo->lpVerb) == CMD_SELECT_MIDDLE)
490 m_strPreviousPath[1] = m_strPaths[0];
491 if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
492 reg.WriteString(f_SecondSelection, m_strPreviousPath[1].c_str());
494 else if (LOWORD(pCmdInfo->lpVerb) == CMD_RESELECT_LEFT)
497 m_strPreviousPath[0] = m_strPaths[0];
498 m_strPreviousPath[1].clear();
499 if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
501 reg.WriteString(f_FirstSelection, m_strPreviousPath[0].c_str());
502 reg.WriteString(f_SecondSelection, _T(""));
508 if (bCompare == FALSE)
511 if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0)
512 bAlterSubFolders = TRUE;
514 String strCommandLine = FormatCmdLine(strWinMergePath,
515 m_strPaths[0], m_strPaths[1], m_strPaths[2], bAlterSubFolders);
517 // Finally start a new WinMerge process
519 STARTUPINFO stInfo = {0};
520 stInfo.cb = sizeof(STARTUPINFO);
521 PROCESS_INFORMATION processInfo = {0};
523 retVal = CreateProcess(NULL, (LPTSTR)strCommandLine.c_str(),
524 NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
525 &stInfo, &processInfo);
529 CloseHandle(processInfo.hThread);
530 CloseHandle(processInfo.hProcess);
532 else if (GetLastError() == ERROR_ELEVATION_REQUIRED)
534 String strCommandLine = FormatCmdLine(_T(""),
535 m_strPaths[0], m_strPaths[1], m_strPaths[2], bAlterSubFolders);
536 HINSTANCE hInstance = ShellExecute(nullptr, _T("runas"), strWinMergePath.c_str(), strCommandLine.c_str(), 0, SW_SHOWNORMAL);
537 if (reinterpret_cast<intptr_t>(hInstance) < 32)
549 * @brief Load a string from resource.
550 * @param [in] Resource string ID.
551 * @return String loaded from resource.
553 String CWinMergeShell::GetResourceString(UINT resourceID)
556 s_pLang = new CLanguageSelect();
557 if (s_pLang->GetLangId() != m_langID)
559 TCHAR szFileName[1024] = {0};
560 GetModuleFileName(_AtlComModule.m_hInstTypeLib, szFileName, sizeof(szFileName) / sizeof(TCHAR));
561 PathRemoveFileSpec(szFileName);
562 String languagesFolder = String(szFileName) + _T("\\Languages\\ShellExtension");
563 s_pLang->LoadLanguageFile(m_langID, languagesFolder);
565 TCHAR resStr[1024] = {0};
566 int res = LoadString(_AtlComModule.m_hInstTypeLib, resourceID, resStr, 1024);
569 s_pLang->TranslateString(resStr, strResource);
573 BOOL CWinMergeShell::InsertMenuString(HMENU hMenu, UINT uPosition, UINT uIDNewItem, UINT uStringId)
575 String str = GetResourceString(uStringId);
576 MENUITEMINFO mii{sizeof mii};
577 mii.fMask = MIIM_ID | MIIM_STRING | MIIM_DATA;
578 mii.wID = uIDNewItem;
579 mii.dwTypeData = const_cast<LPTSTR>(str.c_str());
580 mii.dwItemData = uStringId;
581 return InsertMenuItem(hMenu, uPosition, TRUE, &mii);
584 /// Reads WinMerge path from registry
585 BOOL CWinMergeShell::GetWinMergeDir(String &strDir)
588 if (!reg.QueryRegUser(f_RegDir))
591 // Try first reading debug/test value
592 strDir = reg.ReadString(f_RegValuePriPath, _T(""));
595 strDir = reg.ReadString(f_RegValuePath, _T(""));
603 /// Create menu for simple mode
604 int CWinMergeShell::DrawSimpleMenu(HMENU hmenu, UINT uMenuIndex,
607 InsertMenuString(hmenu, uMenuIndex, uidFirstCmd, IDS_CONTEXT_MENU);
610 HBITMAP hBitmap = PathIsDirectory(m_strPaths[0].c_str()) ? m_MergeDirBmp : m_MergeBmp;
612 SetMenuItemBitmaps(hmenu, uMenuIndex, MF_BYPOSITION, hBitmap, NULL);
614 // Show menu item as grayed if more than two items selected
615 if (m_nSelectedItems > MaxFileCount)
616 EnableMenuItem(hmenu, uMenuIndex, MF_BYPOSITION | MF_GRAYED);
618 return uidFirstCmd + CMD_LAST;
621 /// Create menu for advanced mode
622 int CWinMergeShell::DrawAdvancedMenu(HMENU hmenu, UINT uMenuIndex,
627 switch (m_dwMenuState)
629 // No items selected earlier
630 // Select item as first item to compare
631 case MENU_ONESEL_NOPREV:
632 InsertMenuString(hmenu, uMenuIndex++, uidFirstCmd + CMD_SELECT_LEFT, IDS_SELECT_LEFT);
633 InsertMenuString(hmenu, uMenuIndex, uidFirstCmd + CMD_COMPARE_ELLIPSE, IDS_COMPARE_ELLIPSIS);
637 // One item selected earlier:
638 // Allow re-selecting first item or selecting second item
639 case MENU_ONESEL_PREV:
640 InsertMenuString(hmenu, uMenuIndex++, uidFirstCmd + CMD_COMPARE, IDS_COMPARE);
641 InsertMenuString(hmenu, uMenuIndex++, uidFirstCmd + CMD_SELECT_MIDDLE, IDS_SELECT_MIDDLE);
642 InsertMenuString(hmenu, uMenuIndex, uidFirstCmd + CMD_RESELECT_LEFT, IDS_RESELECT_LEFT);
646 // Two items are selected earlier:
647 // Allow re-selecting first item or selecting second item
648 case MENU_ONESEL_TWO_PREV:
649 InsertMenuString(hmenu, uMenuIndex++, uidFirstCmd + CMD_COMPARE, IDS_COMPARE);
650 InsertMenuString(hmenu, uMenuIndex, uidFirstCmd + CMD_RESELECT_LEFT, IDS_RESELECT_LEFT);
654 // Two items selected
655 // Select both items for compare
658 InsertMenuString(hmenu, uMenuIndex, uidFirstCmd + CMD_COMPARE, IDS_COMPARE);
663 InsertMenuString(hmenu, uMenuIndex, uidFirstCmd + CMD_COMPARE, IDS_COMPARE);
669 HBITMAP hBitmap = PathIsDirectory(m_strPaths[0].c_str()) ? m_MergeDirBmp : m_MergeBmp;
672 for (int i = 0; i < nItemsAdded; i++)
673 SetMenuItemBitmaps(hmenu, uMenuIndex - (nItemsAdded - 1 - i), MF_BYPOSITION, hBitmap, NULL);
676 // Show menu item as grayed if more than two items selected
677 if (m_nSelectedItems > MaxFileCount)
679 for (int i = 0; i < nItemsAdded; i++)
680 EnableMenuItem(hmenu, uMenuIndex - (nItemsAdded - 1 - i), MF_BYPOSITION | MF_GRAYED);
683 return uidFirstCmd + CMD_LAST;
686 /// Determine help text shown in explorer's statusbar
687 String CWinMergeShell::GetHelpText(UINT_PTR idCmd)
691 // More than two items selected, advice user
692 if (m_nSelectedItems > MaxFileCount)
694 strHelp = GetResourceString(IDS_CONTEXT_HELP_MANYITEMS);
698 if (idCmd == CMD_COMPARE)
700 switch (m_dwMenuState)
702 case MENU_ONESEL_PREV:
703 strHelp = GetResourceString(IDS_HELP_COMPARESAVED);
704 strutils::replace(strHelp, _T("%1"), m_strPreviousPath[0]);
707 case MENU_ONESEL_TWO_PREV:
708 strHelp = GetResourceString(IDS_HELP_COMPARESAVED);
709 strutils::replace(strHelp, _T("%1"), m_strPreviousPath[0] + _T(" - ") + m_strPreviousPath[1]);
712 strHelp = GetResourceString(IDS_CONTEXT_HELP);
716 else if (idCmd == CMD_COMPARE_ELLIPSE)
718 strHelp = GetResourceString(IDS_CONTEXT_HELP);
720 else if (idCmd == CMD_SELECT_LEFT)
722 strHelp = GetResourceString(IDS_HELP_SAVETHIS);
724 else if (idCmd == CMD_SELECT_MIDDLE)
726 strHelp = GetResourceString(IDS_HELP_SAVETHIS);
728 else if (idCmd == CMD_RESELECT_LEFT)
730 strHelp = GetResourceString(IDS_HELP_SAVETHIS);
735 /// Format commandline used to start WinMerge
736 String CWinMergeShell::FormatCmdLine(const String &winmergePath,
737 const String &path1, const String &path2, const String &path3, BOOL bAlterSubFolders)
739 String strCommandline = winmergePath.empty() ? _T("") : _T("\"") + winmergePath + _T("\"");
741 // Check if user wants to use context menu
742 BOOL bSubfoldersByDefault = FALSE;
744 if (reg.Open(HKEY_CURRENT_USER, f_RegSettingsDir) == ERROR_SUCCESS)
745 bSubfoldersByDefault = reg.ReadBool(f_Recurse, FALSE);
747 if (bAlterSubFolders && !bSubfoldersByDefault)
748 strCommandline += _T(" /r");
749 else if (!bAlterSubFolders && bSubfoldersByDefault)
750 strCommandline += _T(" /r");
752 strCommandline += _T(" \"") + path1 + _T("\"");
754 if (!m_strPaths[1].empty())
755 strCommandline += _T(" \"") + path2 + _T("\"");
757 if (!m_strPaths[2].empty())
758 strCommandline += _T(" \"") + path3 + _T("\"");
760 return strCommandline;