OSDN Git Service

q,bat: QueryCSV and QueryTSV plugins are only supported on x64 systems
[winmerge-jp/winmerge-jp.git] / ShellExtension / WinMergeShell.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    License (GPLv2+):
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.
7 //
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.
12 //
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.
19 //
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)
26 //
27 // HKEY_CURRENT_USER\Software\Thingamahoochie\WinMerge\FirstSelection
28 //  is used to store path for first selection in advanced mode
29 //
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 /////////////////////////////////////////////////////////////////////////////
34 /**
35  * @file  WinMergeShell.cpp
36  *
37  * @brief Implementation of the ShellExtension class
38  */
39 // ID line follows -- this is updated by SVN
40 // $Id: WinMergeShell.cpp 6933 2009-07-26 14:07:03Z kimmov $
41
42 #include "stdafx.h"
43 #include "ShellExtension.h"
44 #include "WinMergeShell.h"
45 #include "UnicodeString.h"
46 #include "RegKey.h"
47 #include <sys/types.h>
48 #include <sys/stat.h>
49
50 OBJECT_ENTRY_AUTO(CLSID_WinMergeShell, CWinMergeShell)
51
52 /**
53  * @brief Flags for enabling and other settings of context menu.
54  */
55 enum ExtensionFlags
56 {
57         EXT_ENABLED = 0x01, /**< ShellExtension enabled/disabled. */
58         EXT_ADVANCED = 0x02, /**< Advanced menuitems enabled/disabled. */
59 };
60
61 /// Max. filecount to select
62 static const int MaxFileCount = 3;
63 /// Registry path to WinMerge
64 #define REGDIR _T("Software\\Thingamahoochie\\WinMerge")
65 static const TCHAR f_RegDir[] = REGDIR;
66 static const TCHAR f_RegLocaleDir[] = REGDIR _T("\\Locale");
67 static const TCHAR f_RegSettingsDir[] = REGDIR _T("\\Settings");
68
69 /**
70  * @name Registry valuenames.
71  */
72 /*@{*/
73 /** Shell context menuitem enabled/disabled */
74 static const TCHAR f_RegValueEnabled[] = _T("ContextMenuEnabled");
75 /** 'Saved' path in advanced mode */
76 static const TCHAR f_FirstSelection[] = _T("FirstSelection");
77 /** Path to WinMergeU.exe */
78 static const TCHAR f_RegValuePath[] = _T("Executable");
79 /** Path to WinMergeU.exe, overwrites f_RegValuePath if present. */
80 static const TCHAR f_RegValuePriPath[] = _T("PriExecutable");
81 /** LanguageId */
82 static const TCHAR f_LanguageId[] = _T("LanguageId");
83 /** Recurse */
84 static const TCHAR f_Recurse[] = _T("Recurse");
85 /*@}*/
86
87 /**
88  * @brief The states in which the menu can be.
89  * These states define what items are added to the menu and how those
90  * items work.
91  */
92 enum
93 {
94         MENU_SIMPLE = 0,  /**< Simple menu, only "Compare item" is shown. */
95         MENU_ONESEL_NOPREV,  /**< One item selected, no previous selections. */
96         MENU_ONESEL_PREV,  /**< One item selected, previous selection exists. */
97         MENU_TWOSEL,  /**< Two items are selected. */
98         MENU_THREESEL
99 };
100
101 static String GetResourceString(UINT resourceID);
102
103 // GreyMerlin (03 Sept 2017) - The following Version Info checking code is a 
104 // short extract from the Microsoft <versionhelpers.h> file.  Unfortunatly, 
105 // that file is not available for WinXP-compatible Platform Toolsets (e.g. 
106 // v141_xp for VS2017).  Fortunatly, all the actual API interfaces do exist 
107 // in WinXP (actually, in all Windows products since Win2000).  Use of this 
108 // <versionhelpers.h> code avoids the unpleasant deprecation of the GetVersionEx()
109 // API begining with Win 8.1.  This Version Info checking code is also fully 
110 // compatible with all non-XP-compatible Toolsets as well (e.g. v141).
111
112 #ifndef _WIN32_WINNT_VISTA
113 #define _WIN32_WINNT_VISTA      0x0600
114 #endif
115 #ifndef _WIN32_WINNT_WIN8
116 #define _WIN32_WINNT_WIN8       0x0602
117 #endif
118
119 #ifndef VERSIONHELPERAPI
120 #define VERSIONHELPERAPI inline bool
121
122 VERSIONHELPERAPI
123 IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor)
124 {
125     OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 };
126     DWORDLONG        const dwlConditionMask = VerSetConditionMask(
127         VerSetConditionMask(
128         VerSetConditionMask(
129             0, VER_MAJORVERSION, VER_GREATER_EQUAL),
130                VER_MINORVERSION, VER_GREATER_EQUAL),
131                VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
132
133     osvi.dwMajorVersion = wMajorVersion;
134     osvi.dwMinorVersion = wMinorVersion;
135     osvi.wServicePackMajor = wServicePackMajor;
136
137     return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;
138 }
139
140
141 VERSIONHELPERAPI
142 IsWindows8OrGreater()
143 {
144     return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0);
145 }
146 #endif // VERSIONHELPERAPI
147
148 /**
149  * @brief Load a string from resource.
150  * @param [in] Resource string ID.
151  * @return String loaded from resource.
152  */
153 static String GetResourceString(UINT resourceID)
154 {
155         TCHAR resStr[1024] = {0};
156         int res = LoadString(_AtlComModule.m_hInstTypeLib, resourceID, resStr, 1024);
157         ATLASSERT(res != 0);
158         String strResource = resStr;
159         return strResource;
160 }
161
162 HBITMAP ConvertHICONtoHBITMAP(HICON hIcon, int cx, int cy)
163 {
164         LPVOID lpBits;
165         BITMAPINFO bmi = { { sizeof(BITMAPINFOHEADER), cx, cy, 1, IsWindows8OrGreater() ? 32u : 24u } };
166         HBITMAP hbmp = CreateDIBSection(NULL, (BITMAPINFO*)&bmi, DIB_RGB_COLORS, &lpBits, NULL, 0);
167         HDC hdcMem = CreateCompatibleDC(NULL);
168         HBITMAP hbmpPrev = (HBITMAP)SelectObject(hdcMem, hbmp);
169         RECT rc = { 0, 0, cx, cy };
170         if (bmi.bmiHeader.biBitCount <= 24)
171         {
172                 SetBkColor(hdcMem, GetSysColor(COLOR_MENU));
173                 ExtTextOut(hdcMem, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
174         }
175         DrawIconEx(hdcMem, 0, 0, hIcon, cx, cy, 0, NULL, DI_NORMAL);
176         SelectObject(hdcMem, hbmpPrev);
177         DeleteDC(hdcMem);
178         return hbmp;
179 }
180
181 /////////////////////////////////////////////////////////////////////////////
182 // CWinMergeShell
183
184 /// Default constructor, loads icon bitmap
185 CWinMergeShell::CWinMergeShell()
186 {
187         m_dwMenuState = 0;
188         int cx = GetSystemMetrics(SM_CXMENUCHECK);
189         int cy = GetSystemMetrics(SM_CYMENUCHECK);
190
191         // compress or stretch icon bitmap according to menu item height
192         HICON hMergeIcon = (HICON)LoadImage(_AtlComModule.m_hInstTypeLib, MAKEINTRESOURCE(IDI_WINMERGE), IMAGE_ICON,
193                 cx, cy, LR_DEFAULTCOLOR);
194         HICON hMergeDirIcon = (HICON)LoadImage(_AtlComModule.m_hInstTypeLib, MAKEINTRESOURCE(IDI_WINMERGEDIR), IMAGE_ICON,
195                 cx, cy, LR_DEFAULTCOLOR);
196
197         m_MergeBmp = ConvertHICONtoHBITMAP(hMergeIcon, cx, cy);
198         m_MergeDirBmp = ConvertHICONtoHBITMAP(hMergeDirIcon, cx, cy);
199
200         DestroyIcon(hMergeIcon);
201         DestroyIcon(hMergeDirIcon);
202
203 }
204
205 /// Default destructor, unloads bitmap
206 CWinMergeShell::~CWinMergeShell()
207 {
208         DeleteObject(m_MergeDirBmp);
209         DeleteObject(m_MergeBmp);
210 }
211
212 /// Reads selected paths
213 HRESULT CWinMergeShell::Initialize(LPCITEMIDLIST pidlFolder,
214                 LPDATAOBJECT pDataObj, HKEY hProgID)
215 {
216         HRESULT hr = E_INVALIDARG;
217
218         for (auto& path: m_strPaths)
219                 path.erase();
220
221         // Files/folders selected normally from the explorer
222         if (pDataObj)
223         {
224                 FORMATETC fmt = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
225                 STGMEDIUM stg = {TYMED_HGLOBAL};
226                 HDROP hDropInfo;
227
228                 // Look for CF_HDROP data in the data object.
229                 if (FAILED(pDataObj->GetData(&fmt, &stg)))
230                         // Nope! Return an "invalid argument" error back to Explorer.
231                         return E_INVALIDARG;
232
233                 // Get a pointer to the actual data.
234                 hDropInfo = (HDROP) GlobalLock(stg.hGlobal);
235
236                 // Make sure it worked.
237                 if (NULL == hDropInfo)
238                         return E_INVALIDARG;
239
240                 // Sanity check & make sure there is at least one filename.
241                 UINT uNumFilesDropped = DragQueryFile(hDropInfo, 0xFFFFFFFF, NULL, 0);
242                 m_nSelectedItems = uNumFilesDropped;
243
244                 if (uNumFilesDropped == 0)
245                 {
246                         GlobalUnlock(stg.hGlobal);
247                         ReleaseStgMedium(&stg);
248                         return E_INVALIDARG;
249                 }
250
251                 hr = S_OK;
252
253                 // Get all file names.
254                 for (WORD x = 0 ; x < uNumFilesDropped; x++)
255                 {
256                         // Get the number of bytes required by the file's full pathname
257                         UINT wPathnameSize = DragQueryFile(hDropInfo, x, NULL, 0);
258
259                         // Allocate memory to contain full pathname & zero byte
260                         wPathnameSize += 1;
261                         LPTSTR npszFile = (TCHAR *) new TCHAR[wPathnameSize];
262
263                         // If not enough memory, skip this one
264                         if (npszFile == NULL)
265                                 continue;
266
267                         // Copy the pathname into the buffer
268                         DragQueryFile(hDropInfo, x, npszFile, wPathnameSize);
269
270                         if (x < MaxFileCount)
271                                 m_strPaths[x] = npszFile;
272
273                         delete[] npszFile;
274                 }
275                 GlobalUnlock(stg.hGlobal);
276                 ReleaseStgMedium(&stg);
277         }
278         else
279         {
280                 m_nSelectedItems = 0;
281         }
282
283                 // No item selected - selection is the folder background
284         if (pidlFolder)
285         {
286                 TCHAR szPath[MAX_PATH] = {0};
287
288                 if (SHGetPathFromIDList(pidlFolder, szPath))
289                 {
290                         if (m_nSelectedItems < MaxFileCount)
291                                 m_strPaths[m_nSelectedItems++] = szPath;
292                         hr = S_OK;
293                 }
294                 else
295                 {
296                         hr = E_INVALIDARG;
297                 }
298         }
299         return hr;
300 }
301
302 /// Adds context menu item
303 HRESULT CWinMergeShell::QueryContextMenu(HMENU hmenu, UINT uMenuIndex,
304                 UINT uidFirstCmd, UINT uidLastCmd, UINT uFlags)
305 {
306         int nItemsAdded = 0;
307
308         // If the flags include CMF_DEFAULTONLY then we shouldn't do anything.
309         if (uFlags & CMF_DEFAULTONLY)
310                 return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
311
312         // Check if user wants to use context menu
313         CRegKeyEx reg;
314         if (reg.Open(HKEY_CURRENT_USER, f_RegDir) != ERROR_SUCCESS)
315                 return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
316
317         m_dwContextMenuEnabled = reg.ReadDword(f_RegValueEnabled, 0);
318         m_strPreviousPath = reg.ReadString(f_FirstSelection, _T("")).c_str();
319
320         if (m_dwContextMenuEnabled & EXT_ENABLED) // Context menu enabled
321         {
322                 // Check if advanced menuitems enabled
323                 if ((m_dwContextMenuEnabled & EXT_ADVANCED) == 0)
324                 {
325                         m_dwMenuState = MENU_SIMPLE;
326                         nItemsAdded = DrawSimpleMenu(hmenu, uMenuIndex, uidFirstCmd);
327                 }
328                 else
329                 {
330                         if (m_nSelectedItems == 1 && m_strPreviousPath.empty())
331                                 m_dwMenuState = MENU_ONESEL_NOPREV;
332                         else if (m_nSelectedItems == 1 && !m_strPreviousPath.empty())
333                                 m_dwMenuState = MENU_ONESEL_PREV;
334                         else if (m_nSelectedItems == 2)
335                                 m_dwMenuState = MENU_TWOSEL;
336                         else if (m_nSelectedItems == 3)
337                                 m_dwMenuState = MENU_THREESEL;
338
339                         nItemsAdded = DrawAdvancedMenu(hmenu, uMenuIndex, uidFirstCmd);
340                 }
341
342                 return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, nItemsAdded);
343         }
344         return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
345 }
346
347 /// Gets string shown explorer's status bar when menuitem selected
348 HRESULT CWinMergeShell::GetCommandString(UINT_PTR idCmd, UINT uFlags,
349                 UINT* pwReserved, LPSTR pszName, UINT  cchMax)
350 {
351         USES_CONVERSION;
352
353         // Check idCmd, it must be 0 in simple mode and 0 or 1 in advanced mode.
354         if ((m_dwMenuState & EXT_ADVANCED) == 0)
355         {
356                 if (idCmd > 0)
357                         return E_INVALIDARG;
358         }
359         else
360         {
361                 if (idCmd > 2)
362                         return E_INVALIDARG;
363         }
364
365         // If Explorer is asking for a help string, copy our string into the
366         // supplied buffer.
367         if (uFlags & GCS_HELPTEXT)
368         {
369                 String strHelp;
370
371                 strHelp = GetHelpText(idCmd);
372
373                 if (uFlags & GCS_UNICODE)
374                         // We need to cast pszName to a Unicode string, and then use the
375                         // Unicode string copy API.
376                         lstrcpynW((LPWSTR) pszName, T2CW(strHelp.c_str()), cchMax);
377                 else
378                         // Use the ANSI string copy API to return the help string.
379                         lstrcpynA(pszName, T2CA(strHelp.c_str()), cchMax);
380
381                 return S_OK;
382         }
383         return E_INVALIDARG;
384 }
385
386 /// Runs WinMerge with given paths
387 HRESULT CWinMergeShell::InvokeCommand(LPCMINVOKECOMMANDINFO pCmdInfo)
388 {
389         CRegKeyEx reg;
390         String strWinMergePath;
391         BOOL bCompare = FALSE;
392         BOOL bAlterSubFolders = FALSE;
393
394         // If lpVerb really points to a string, ignore this function call and bail out.
395         if (HIWORD(pCmdInfo->lpVerb) != 0)
396                 return E_INVALIDARG;
397
398         // Read WinMerge location from registry
399         if (!GetWinMergeDir(strWinMergePath))
400                 return S_FALSE;
401
402         // Check that file we are trying to execute exists
403         if (!PathFileExists(strWinMergePath.c_str()))
404                 return S_FALSE;
405
406         if (LOWORD(pCmdInfo->lpVerb) == 0)
407         {
408                 switch (m_dwMenuState)
409                 {
410                 case MENU_SIMPLE:
411                         bCompare = TRUE;
412                         break;
413
414                 case MENU_ONESEL_NOPREV:
415                         m_strPreviousPath = m_strPaths[0];
416                         if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
417                                 reg.WriteString(f_FirstSelection, m_strPreviousPath.c_str());
418                         break;
419
420                 case MENU_ONESEL_PREV:
421                         m_strPaths[1] = m_strPaths[0];
422                         m_strPaths[0] = m_strPreviousPath;
423                         bCompare = TRUE;
424
425                         // Forget previous selection
426                         if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
427                                 reg.WriteString(f_FirstSelection, _T(""));
428                         break;
429
430                 case MENU_TWOSEL:
431                 case MENU_THREESEL:
432                         // "Compare" - compare paths
433                         bCompare = TRUE;
434                         m_strPreviousPath.erase();
435                         break;
436                 }
437         }
438         else if (LOWORD(pCmdInfo->lpVerb) == 1)
439         {
440                 switch (m_dwMenuState)
441                 {
442                 case MENU_ONESEL_PREV:
443                         m_strPreviousPath = m_strPaths[0];
444                         if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
445                                 reg.WriteString(f_FirstSelection, m_strPreviousPath.c_str());
446                         bCompare = FALSE;
447                         break;
448                 default:
449                         // "Compare..." - user wants to compare this single item and open WinMerge
450                         m_strPaths[1].erase();
451                         m_strPaths[2].erase();
452                         bCompare = TRUE;
453                         break;
454                 }
455         }
456         else
457                 return E_INVALIDARG;
458
459         if (bCompare == FALSE)
460                 return S_FALSE;
461
462         if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0)
463                 bAlterSubFolders = TRUE;
464
465         String strCommandLine = FormatCmdLine(strWinMergePath,
466                 m_strPaths[0], m_strPaths[1], m_strPaths[2], bAlterSubFolders);
467
468         // Finally start a new WinMerge process
469         BOOL retVal = FALSE;
470         STARTUPINFO stInfo = {0};
471         stInfo.cb = sizeof(STARTUPINFO);
472         PROCESS_INFORMATION processInfo = {0};
473
474         retVal = CreateProcess(NULL, (LPTSTR)strCommandLine.c_str(),
475                         NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
476                         &stInfo, &processInfo);
477
478         if (retVal)
479         {
480                 CloseHandle(processInfo.hThread);
481                 CloseHandle(processInfo.hProcess);
482         }
483         else if (GetLastError() == ERROR_ELEVATION_REQUIRED)
484         {
485                 String strCommandLine = FormatCmdLine(_T(""),
486                         m_strPaths[0], m_strPaths[1], m_strPaths[2], bAlterSubFolders);
487                 HINSTANCE hInstance = ShellExecute(nullptr, _T("runas"), strWinMergePath.c_str(), strCommandLine.c_str(), 0, SW_SHOWNORMAL);
488                 if (reinterpret_cast<intptr_t>(hInstance) < 32)
489                         return S_FALSE;
490         }
491         else
492         {
493                 return S_FALSE;
494         }
495
496         return S_OK;
497 }
498
499 /// Reads WinMerge path from registry
500 BOOL CWinMergeShell::GetWinMergeDir(String &strDir)
501 {
502         CRegKeyEx reg;
503         if (!reg.QueryRegUser(f_RegDir))
504                 return FALSE;
505
506         // Try first reading debug/test value
507         strDir = reg.ReadString(f_RegValuePriPath, _T(""));
508         if (strDir.empty())
509         {
510                 strDir = reg.ReadString(f_RegValuePath, _T(""));
511                 if (strDir.empty())
512                         return FALSE;
513         }
514
515         return TRUE;
516 }
517
518 /// Create menu for simple mode
519 int CWinMergeShell::DrawSimpleMenu(HMENU hmenu, UINT uMenuIndex,
520                 UINT uidFirstCmd)
521 {
522         String strMenu = GetResourceString(IDS_CONTEXT_MENU);
523         InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd, strMenu.c_str());
524
525         // Add bitmap
526         HBITMAP hBitmap = PathIsDirectory(m_strPaths[0].c_str()) ? m_MergeDirBmp : m_MergeBmp;
527         if (hBitmap != NULL)
528                 SetMenuItemBitmaps(hmenu, uMenuIndex, MF_BYPOSITION, hBitmap, NULL);
529
530         // Show menu item as grayed if more than two items selected
531         if (m_nSelectedItems > MaxFileCount)
532                 EnableMenuItem(hmenu, uMenuIndex, MF_BYPOSITION | MF_GRAYED);
533
534         return 1;
535 }
536
537 /// Create menu for advanced mode
538 int CWinMergeShell::DrawAdvancedMenu(HMENU hmenu, UINT uMenuIndex,
539                 UINT uidFirstCmd)
540 {
541         String strCompare = GetResourceString(IDS_COMPARE);
542         String strCompareEllipsis = GetResourceString(IDS_COMPARE_ELLIPSIS);
543         String strCompareTo = GetResourceString(IDS_COMPARE_TO);
544         String strReselect = GetResourceString(IDS_RESELECT_FIRST);
545         int nItemsAdded = 0;
546
547         switch (m_dwMenuState)
548         {
549                 // No items selected earlier
550                 // Select item as first item to compare
551         case MENU_ONESEL_NOPREV:
552                 InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd,
553                                 strCompareTo.c_str());
554                 uMenuIndex++;
555                 uidFirstCmd++;
556                 InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd,
557                                 strCompareEllipsis.c_str());
558                 nItemsAdded = 2;
559                 break;
560
561                 // One item selected earlier:
562                 // Allow re-selecting first item or selecting second item
563         case MENU_ONESEL_PREV:
564                 InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd,
565                                 strCompare.c_str());
566                 uMenuIndex++;
567                 uidFirstCmd++;
568                 InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd,
569                                 strReselect.c_str());
570                 nItemsAdded = 2;
571                 break;
572
573                 // Two items selected
574                 // Select both items for compare
575         case MENU_TWOSEL:
576         case MENU_THREESEL:
577                 InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd,
578                                 strCompare.c_str());
579                 nItemsAdded = 1;
580                 break;
581
582         default:
583                 InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd,
584                                 strCompare.c_str());
585                 nItemsAdded = 1;
586                 break;
587         }
588
589         // Add bitmap
590         HBITMAP hBitmap = PathIsDirectory(m_strPaths[0].c_str()) ? m_MergeDirBmp : m_MergeBmp;
591         if (hBitmap != NULL)
592         {
593                 if (nItemsAdded == 2)
594                         SetMenuItemBitmaps(hmenu, uMenuIndex - 1, MF_BYPOSITION, hBitmap, NULL);
595                 SetMenuItemBitmaps(hmenu, uMenuIndex, MF_BYPOSITION, hBitmap, NULL);
596         }
597
598         // Show menu item as grayed if more than two items selected
599         if (m_nSelectedItems > MaxFileCount)
600         {
601                 if (nItemsAdded == 2)
602                         EnableMenuItem(hmenu, uMenuIndex - 1, MF_BYPOSITION | MF_GRAYED);
603                 EnableMenuItem(hmenu, uMenuIndex, MF_BYPOSITION | MF_GRAYED);
604         }
605
606         return nItemsAdded;
607 }
608
609 /// Determine help text shown in explorer's statusbar
610 String CWinMergeShell::GetHelpText(UINT_PTR idCmd)
611 {
612         String strHelp;
613
614         // More than two items selected, advice user
615         if (m_nSelectedItems > MaxFileCount)
616         {
617                 strHelp = GetResourceString(IDS_CONTEXT_HELP_MANYITEMS);
618                 return strHelp;
619         }
620
621         if (idCmd == 0)
622         {
623                 switch (m_dwMenuState)
624                 {
625                 case MENU_SIMPLE:
626                         strHelp = GetResourceString(IDS_CONTEXT_HELP);;
627                         break;
628
629                 case MENU_ONESEL_NOPREV:
630                         strHelp = GetResourceString(IDS_HELP_SAVETHIS);
631                         break;
632
633                 case MENU_ONESEL_PREV:
634                         strHelp = GetResourceString(IDS_HELP_COMPARESAVED);
635                         strutils::replace(strHelp, _T("%1"), m_strPreviousPath);
636                         break;
637
638                 case MENU_TWOSEL:
639                 case MENU_THREESEL:
640                         strHelp = GetResourceString(IDS_CONTEXT_HELP);
641                         break;
642                 }
643         }
644         else if (idCmd == 1)
645         {
646                 switch (m_dwMenuState)
647                 {
648                 case MENU_ONESEL_PREV:
649                         strHelp = GetResourceString(IDS_HELP_SAVETHIS);
650                         break;
651                 default:
652                         strHelp = GetResourceString(IDS_CONTEXT_HELP);
653                         break;
654                 }
655         }
656         return strHelp;
657 }
658
659 /// Format commandline used to start WinMerge
660 String CWinMergeShell::FormatCmdLine(const String &winmergePath,
661                 const String &path1, const String &path2, const String &path3, BOOL bAlterSubFolders)
662 {
663         String strCommandline = winmergePath.empty() ? _T("") : _T("\"") + winmergePath + _T("\"");
664
665         // Check if user wants to use context menu
666         BOOL bSubfoldersByDefault = FALSE;
667         CRegKeyEx reg;
668         if (reg.Open(HKEY_CURRENT_USER, f_RegSettingsDir) == ERROR_SUCCESS)
669                 bSubfoldersByDefault = reg.ReadBool(f_Recurse, FALSE);
670
671         if (bAlterSubFolders && !bSubfoldersByDefault)
672                 strCommandline += _T(" /r");
673         else if (!bAlterSubFolders && bSubfoldersByDefault)
674                 strCommandline += _T(" /r");
675
676         strCommandline += _T(" \"") + path1 + _T("\"");
677
678         if (!m_strPaths[1].empty())
679                 strCommandline += _T(" \"") + path2 + _T("\"");
680
681         if (!m_strPaths[2].empty())
682                 strCommandline += _T(" \"") + path3 + _T("\"");
683
684         return strCommandline;
685 }