OSDN Git Service

ShellExtension: As with WinMergeU.exe, do not embed translation strings in ShellExten...
[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 enum
62 {
63         CMD_COMPARE = 0,
64         CMD_COMPARE_ELLIPSE,
65         CMD_SELECT_LEFT,
66         CMD_SELECT_MIDDLE,
67         CMD_RESELECT_LEFT,
68         CMD_LAST = CMD_RESELECT_LEFT,
69 };
70
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");
78
79 /**
80  * @name Registry valuenames.
81  */
82 /*@{*/
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");
93 /** LanguageId */
94 static const TCHAR f_LanguageId[] = _T("LanguageId");
95 /** Recurse */
96 static const TCHAR f_Recurse[] = _T("Recurse");
97 /*@}*/
98
99 /**
100  * @brief The states in which the menu can be.
101  * These states define what items are added to the menu and how those
102  * items work.
103  */
104 enum
105 {
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. */
111         MENU_THREESEL
112 };
113
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).
122
123 #ifndef _WIN32_WINNT_VISTA
124 #define _WIN32_WINNT_VISTA      0x0600
125 #endif
126 #ifndef _WIN32_WINNT_WIN8
127 #define _WIN32_WINNT_WIN8       0x0602
128 #endif
129
130 #ifndef VERSIONHELPERAPI
131 #define VERSIONHELPERAPI inline bool
132
133 VERSIONHELPERAPI
134 IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor)
135 {
136     OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 };
137     DWORDLONG        const dwlConditionMask = VerSetConditionMask(
138         VerSetConditionMask(
139         VerSetConditionMask(
140             0, VER_MAJORVERSION, VER_GREATER_EQUAL),
141                VER_MINORVERSION, VER_GREATER_EQUAL),
142                VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
143
144     osvi.dwMajorVersion = wMajorVersion;
145     osvi.dwMinorVersion = wMinorVersion;
146     osvi.wServicePackMajor = wServicePackMajor;
147
148     return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;
149 }
150
151
152 VERSIONHELPERAPI
153 IsWindows8OrGreater()
154 {
155     return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0);
156 }
157 #endif // VERSIONHELPERAPI
158
159 HBITMAP ConvertHICONtoHBITMAP(HICON hIcon, int cx, int cy)
160 {
161         LPVOID lpBits;
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);
165         if (hbmp)
166         {
167                 HBITMAP hbmpPrev = (HBITMAP)SelectObject(hdcMem, hbmp);
168                 RECT rc = { 0, 0, cx, cy };
169                 if (bmi.bmiHeader.biBitCount <= 24)
170                 {
171                         SetBkColor(hdcMem, GetSysColor(COLOR_MENU));
172                         ExtTextOut(hdcMem, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
173                 }
174                 DrawIconEx(hdcMem, 0, 0, hIcon, cx, cy, 0, NULL, DI_NORMAL);
175                 SelectObject(hdcMem, hbmpPrev);
176         }
177         DeleteDC(hdcMem);
178         return hbmp;
179 }
180
181 /////////////////////////////////////////////////////////////////////////////
182 // CWinMergeShell
183
184 /// Default constructor, loads icon bitmap
185 CWinMergeShell::CWinMergeShell()
186         : m_dwContextMenuEnabled(false)
187         , m_nSelectedItems(0)
188         , m_dwMenuState(0)
189         , m_langID(GetUserDefaultUILanguage())
190 {
191         int cx = GetSystemMetrics(SM_CXMENUCHECK);
192         int cy = GetSystemMetrics(SM_CYMENUCHECK);
193
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);
199
200         m_MergeBmp = ConvertHICONtoHBITMAP(hMergeIcon, cx, cy);
201         m_MergeDirBmp = ConvertHICONtoHBITMAP(hMergeDirIcon, cx, cy);
202
203         DestroyIcon(hMergeIcon);
204         DestroyIcon(hMergeDirIcon);
205
206         CRegKeyEx reg;
207         if (reg.Open(HKEY_CURRENT_USER, f_RegLocaleDir) == ERROR_SUCCESS)
208                 m_langID = static_cast<LANGID>(reg.ReadDword(f_LanguageId, m_langID));
209
210 }
211
212 /// Default destructor, unloads bitmap
213 CWinMergeShell::~CWinMergeShell()
214 {
215         DeleteObject(m_MergeDirBmp);
216         DeleteObject(m_MergeBmp);
217 }
218
219 /// Reads selected paths
220 HRESULT CWinMergeShell::Initialize(LPCITEMIDLIST pidlFolder,
221                 LPDATAOBJECT pDataObj, HKEY hProgID)
222 {
223         HRESULT hr = E_INVALIDARG;
224
225         for (auto& path: m_strPaths)
226                 path.erase();
227
228         // Files/folders selected normally from the explorer
229         if (pDataObj)
230         {
231                 FORMATETC fmt = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
232                 STGMEDIUM stg = {TYMED_HGLOBAL};
233                 HDROP hDropInfo;
234
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.
238                         return E_INVALIDARG;
239
240                 // Get a pointer to the actual data.
241                 hDropInfo = (HDROP) GlobalLock(stg.hGlobal);
242
243                 // Make sure it worked.
244                 if (NULL == hDropInfo)
245                         return E_INVALIDARG;
246
247                 // Sanity check & make sure there is at least one filename.
248                 UINT uNumFilesDropped = DragQueryFile(hDropInfo, 0xFFFFFFFF, NULL, 0);
249                 m_nSelectedItems = uNumFilesDropped;
250
251                 if (uNumFilesDropped == 0)
252                 {
253                         GlobalUnlock(stg.hGlobal);
254                         ReleaseStgMedium(&stg);
255                         return E_INVALIDARG;
256                 }
257
258                 hr = S_OK;
259
260                 // Get all file names.
261                 for (WORD x = 0 ; x < uNumFilesDropped; x++)
262                 {
263                         // Get the number of bytes required by the file's full pathname
264                         UINT wPathnameSize = DragQueryFile(hDropInfo, x, NULL, 0);
265
266                         // Allocate memory to contain full pathname & zero byte
267                         wPathnameSize += 1;
268                         LPTSTR npszFile = (TCHAR *) new TCHAR[wPathnameSize];
269
270                         // If not enough memory, skip this one
271                         if (npszFile == NULL)
272                                 continue;
273
274                         // Copy the pathname into the buffer
275                         DragQueryFile(hDropInfo, x, npszFile, wPathnameSize);
276
277                         if (x < MaxFileCount)
278                                 m_strPaths[x] = npszFile;
279
280                         delete[] npszFile;
281                 }
282                 GlobalUnlock(stg.hGlobal);
283                 ReleaseStgMedium(&stg);
284         }
285         else
286         {
287                 m_nSelectedItems = 0;
288         }
289
290                 // No item selected - selection is the folder background
291         if (pidlFolder)
292         {
293                 TCHAR szPath[MAX_PATH] = {0};
294
295                 if (SHGetPathFromIDList(pidlFolder, szPath))
296                 {
297                         if (m_nSelectedItems < MaxFileCount)
298                                 m_strPaths[m_nSelectedItems++] = szPath;
299                         hr = S_OK;
300                 }
301                 else
302                 {
303                         hr = E_INVALIDARG;
304                 }
305         }
306         return hr;
307 }
308
309 /// Adds context menu item
310 HRESULT CWinMergeShell::QueryContextMenu(HMENU hmenu, UINT uMenuIndex,
311                 UINT uidFirstCmd, UINT uidLastCmd, UINT uFlags)
312 {
313         // check whether menu items are already added
314         if (hmenu == s_hMenuLastAdded)
315         {
316                 MENUITEMINFO mii{ sizeof mii };
317                 mii.fMask = MIIM_DATA;
318                 if (GetMenuItemInfo(hmenu, s_uidCmdLastAdded, FALSE, &mii))
319                 {
320                         if (mii.dwItemData >= IDS_COMPARE && mii.dwItemData <= IDS_RESELECT_LEFT)
321                                 return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
322                 }
323         }
324
325         s_hMenuLastAdded = hmenu;
326         s_uidCmdLastAdded = uidFirstCmd;
327
328         int uidUserLastCmd = 0;
329
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);
333
334         // Check if user wants to use context menu
335         CRegKeyEx reg;
336         if (reg.Open(HKEY_CURRENT_USER, f_RegDir) != ERROR_SUCCESS)
337                 return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
338
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();
342
343         if (m_dwContextMenuEnabled & EXT_ENABLED) // Context menu enabled
344         {
345                 // Check if advanced menuitems enabled
346                 if ((m_dwContextMenuEnabled & EXT_ADVANCED) == 0)
347                 {
348                         m_dwMenuState = MENU_SIMPLE;
349                         uidUserLastCmd = DrawSimpleMenu(hmenu, uMenuIndex, uidFirstCmd);
350                 }
351                 else
352                 {
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;
363
364                         uidUserLastCmd = DrawAdvancedMenu(hmenu, uMenuIndex, uidFirstCmd);
365                 }
366
367                 return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, (uidUserLastCmd - uidFirstCmd) + 1);
368         }
369         return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
370 }
371
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)
375 {
376         USES_CONVERSION;
377
378         // Check idCmd, it must be 0 in simple mode and 0 or 1 in advanced mode.
379         if ((m_dwMenuState & EXT_ADVANCED) == 0)
380         {
381                 if (idCmd > 0)
382                         return E_INVALIDARG;
383         }
384         else
385         {
386                 if (idCmd > 2)
387                         return E_INVALIDARG;
388         }
389
390         // If Explorer is asking for a help string, copy our string into the
391         // supplied buffer.
392         if (uFlags & GCS_HELPTEXT)
393         {
394                 String strHelp = GetHelpText(idCmd);
395
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);
400                 else
401                         // Use the ANSI string copy API to return the help string.
402                         lstrcpynA(pszName, T2CA(strHelp.c_str()), cchMax);
403
404                 return S_OK;
405         }
406         return E_INVALIDARG;
407 }
408
409 /// Runs WinMerge with given paths
410 HRESULT CWinMergeShell::InvokeCommand(LPCMINVOKECOMMANDINFO pCmdInfo)
411 {
412         CRegKeyEx reg;
413         String strWinMergePath;
414         BOOL bCompare = FALSE;
415         BOOL bAlterSubFolders = FALSE;
416
417         // If lpVerb really points to a string, ignore this function call and bail out.
418         if (HIWORD(pCmdInfo->lpVerb) != 0)
419                 return E_INVALIDARG;
420
421         // Read WinMerge location from registry
422         if (!GetWinMergeDir(strWinMergePath))
423                 return S_FALSE;
424
425         // Check that file we are trying to execute exists
426         if (!PathFileExists(strWinMergePath.c_str()))
427                 return S_FALSE;
428
429         if (LOWORD(pCmdInfo->lpVerb) == CMD_COMPARE)
430         {
431                 switch (m_dwMenuState)
432                 {
433                 case MENU_SIMPLE:
434                         bCompare = TRUE;
435                         break;
436
437                 case MENU_ONESEL_PREV:
438                         m_strPaths[1] = m_strPaths[0];
439                         m_strPaths[0] = m_strPreviousPath[0];
440                         bCompare = TRUE;
441
442                         // Forget previous selection
443                         if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
444                         {
445                                 reg.WriteString(f_FirstSelection, _T(""));
446                                 reg.WriteString(f_SecondSelection, _T(""));
447                         }
448                         break;
449
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];
454                         bCompare = TRUE;
455
456                         // Forget previous selection
457                         if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
458                         {
459                                 reg.WriteString(f_FirstSelection, _T(""));
460                                 reg.WriteString(f_SecondSelection, _T(""));
461                         }
462                         break;
463
464                 case MENU_TWOSEL:
465                 case MENU_THREESEL:
466                         // "Compare" - compare paths
467                         bCompare = TRUE;
468                         m_strPreviousPath[0].erase();
469                         m_strPreviousPath[1].erase();
470                         break;
471                 }
472         }
473         else if (LOWORD(pCmdInfo->lpVerb) == CMD_COMPARE_ELLIPSE)
474         {
475                 // "Compare..." - user wants to compare this single item and open WinMerge
476                 m_strPaths[1].erase();
477                 m_strPaths[2].erase();
478                 bCompare = TRUE;
479         }
480         else if (LOWORD(pCmdInfo->lpVerb) == CMD_SELECT_LEFT)
481         {
482                 // 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());
486         }
487         else if (LOWORD(pCmdInfo->lpVerb) == CMD_SELECT_MIDDLE)
488         {
489                 // 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());
493         }
494         else if (LOWORD(pCmdInfo->lpVerb) == CMD_RESELECT_LEFT)
495         {
496                 // Re-select Left
497                 m_strPreviousPath[0] = m_strPaths[0];
498                 m_strPreviousPath[1].clear();
499                 if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
500                 {
501                         reg.WriteString(f_FirstSelection, m_strPreviousPath[0].c_str());
502                         reg.WriteString(f_SecondSelection, _T(""));
503                 }
504         }
505         else
506                 return E_INVALIDARG;
507
508         if (bCompare == FALSE)
509                 return S_FALSE;
510
511         if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0)
512                 bAlterSubFolders = TRUE;
513
514         String strCommandLine = FormatCmdLine(strWinMergePath,
515                 m_strPaths[0], m_strPaths[1], m_strPaths[2], bAlterSubFolders);
516
517         // Finally start a new WinMerge process
518         BOOL retVal = FALSE;
519         STARTUPINFO stInfo = {0};
520         stInfo.cb = sizeof(STARTUPINFO);
521         PROCESS_INFORMATION processInfo = {0};
522
523         retVal = CreateProcess(NULL, (LPTSTR)strCommandLine.c_str(),
524                         NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
525                         &stInfo, &processInfo);
526
527         if (retVal)
528         {
529                 CloseHandle(processInfo.hThread);
530                 CloseHandle(processInfo.hProcess);
531         }
532         else if (GetLastError() == ERROR_ELEVATION_REQUIRED)
533         {
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)
538                         return S_FALSE;
539         }
540         else
541         {
542                 return S_FALSE;
543         }
544
545         return S_OK;
546 }
547
548 /**
549  * @brief Load a string from resource.
550  * @param [in] Resource string ID.
551  * @return String loaded from resource.
552  */
553 String CWinMergeShell::GetResourceString(UINT resourceID)
554 {
555         if (!s_pLang)
556                 s_pLang = new CLanguageSelect();
557         if (s_pLang->GetLangId() != m_langID)
558         {
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);
564         }
565         TCHAR resStr[1024] = {0};
566         int res = LoadString(_AtlComModule.m_hInstTypeLib, resourceID, resStr, 1024);
567         ATLASSERT(res != 0);
568         String strResource;
569         s_pLang->TranslateString(resStr, strResource);
570         return strResource;
571 }
572
573 BOOL CWinMergeShell::InsertMenuString(HMENU hMenu, UINT uPosition, UINT uIDNewItem, UINT uStringId)
574 {
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);
582 }
583
584 /// Reads WinMerge path from registry
585 BOOL CWinMergeShell::GetWinMergeDir(String &strDir)
586 {
587         CRegKeyEx reg;
588         if (!reg.QueryRegUser(f_RegDir))
589                 return FALSE;
590
591         // Try first reading debug/test value
592         strDir = reg.ReadString(f_RegValuePriPath, _T(""));
593         if (strDir.empty())
594         {
595                 strDir = reg.ReadString(f_RegValuePath, _T(""));
596                 if (strDir.empty())
597                         return FALSE;
598         }
599
600         return TRUE;
601 }
602
603 /// Create menu for simple mode
604 int CWinMergeShell::DrawSimpleMenu(HMENU hmenu, UINT uMenuIndex,
605                 UINT uidFirstCmd)
606 {
607         InsertMenuString(hmenu, uMenuIndex, uidFirstCmd, IDS_CONTEXT_MENU);
608
609         // Add bitmap
610         HBITMAP hBitmap = PathIsDirectory(m_strPaths[0].c_str()) ? m_MergeDirBmp : m_MergeBmp;
611         if (hBitmap != NULL)
612                 SetMenuItemBitmaps(hmenu, uMenuIndex, MF_BYPOSITION, hBitmap, NULL);
613
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);
617
618         return uidFirstCmd + CMD_LAST;
619 }
620
621 /// Create menu for advanced mode
622 int CWinMergeShell::DrawAdvancedMenu(HMENU hmenu, UINT uMenuIndex,
623                 UINT uidFirstCmd)
624 {
625         int nItemsAdded = 0;
626
627         switch (m_dwMenuState)
628         {
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);
634                 nItemsAdded = 2;
635                 break;
636
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);
643                 nItemsAdded = 3;
644                 break;
645
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);
651                 nItemsAdded = 2;
652                 break;
653
654                 // Two items selected
655                 // Select both items for compare
656         case MENU_TWOSEL:
657         case MENU_THREESEL:
658                 InsertMenuString(hmenu, uMenuIndex, uidFirstCmd + CMD_COMPARE, IDS_COMPARE);
659                 nItemsAdded = 1;
660                 break;
661
662         default:
663                 InsertMenuString(hmenu, uMenuIndex, uidFirstCmd + CMD_COMPARE, IDS_COMPARE);
664                 nItemsAdded = 1;
665                 break;
666         }
667
668         // Add bitmap
669         HBITMAP hBitmap = PathIsDirectory(m_strPaths[0].c_str()) ? m_MergeDirBmp : m_MergeBmp;
670         if (hBitmap != NULL)
671         {
672                 for (int i = 0; i < nItemsAdded; i++)
673                         SetMenuItemBitmaps(hmenu, uMenuIndex - (nItemsAdded - 1 - i), MF_BYPOSITION, hBitmap, NULL);
674         }
675
676         // Show menu item as grayed if more than two items selected
677         if (m_nSelectedItems > MaxFileCount)
678         {
679                 for (int i = 0; i < nItemsAdded; i++)
680                         EnableMenuItem(hmenu, uMenuIndex - (nItemsAdded - 1 - i), MF_BYPOSITION | MF_GRAYED);
681         }
682
683         return uidFirstCmd + CMD_LAST;
684 }
685
686 /// Determine help text shown in explorer's statusbar
687 String CWinMergeShell::GetHelpText(UINT_PTR idCmd)
688 {
689         String strHelp;
690
691         // More than two items selected, advice user
692         if (m_nSelectedItems > MaxFileCount)
693         {
694                 strHelp = GetResourceString(IDS_CONTEXT_HELP_MANYITEMS);
695                 return strHelp;
696         }
697
698         if (idCmd == CMD_COMPARE)
699         {
700                 switch (m_dwMenuState)
701                 {
702                 case MENU_ONESEL_PREV:
703                         strHelp = GetResourceString(IDS_HELP_COMPARESAVED);
704                         strutils::replace(strHelp, _T("%1"), m_strPreviousPath[0]);
705                         break;
706
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]);
710                         break;
711                 default:
712                         strHelp = GetResourceString(IDS_CONTEXT_HELP);
713                         break;
714                 }
715         }
716         else if (idCmd == CMD_COMPARE_ELLIPSE)
717         {
718                 strHelp = GetResourceString(IDS_CONTEXT_HELP);
719         }
720         else if (idCmd == CMD_SELECT_LEFT)
721         {
722                 strHelp = GetResourceString(IDS_HELP_SAVETHIS);
723         }
724         else if (idCmd == CMD_SELECT_MIDDLE)
725         {
726                 strHelp = GetResourceString(IDS_HELP_SAVETHIS);
727         }
728         else if (idCmd == CMD_RESELECT_LEFT)
729         {
730                 strHelp = GetResourceString(IDS_HELP_SAVETHIS);
731         }
732         return strHelp;
733 }
734
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)
738 {
739         String strCommandline = winmergePath.empty() ? _T("") : _T("\"") + winmergePath + _T("\"");
740
741         // Check if user wants to use context menu
742         BOOL bSubfoldersByDefault = FALSE;
743         CRegKeyEx reg;
744         if (reg.Open(HKEY_CURRENT_USER, f_RegSettingsDir) == ERROR_SUCCESS)
745                 bSubfoldersByDefault = reg.ReadBool(f_Recurse, FALSE);
746
747         if (bAlterSubFolders && !bSubfoldersByDefault)
748                 strCommandline += _T(" /r");
749         else if (!bAlterSubFolders && bSubfoldersByDefault)
750                 strCommandline += _T(" /r");
751
752         strCommandline += _T(" \"") + path1 + _T("\"");
753
754         if (!m_strPaths[1].empty())
755                 strCommandline += _T(" \"") + path2 + _T("\"");
756
757         if (!m_strPaths[2].empty())
758                 strCommandline += _T(" \"") + path3 + _T("\"");
759
760         return strCommandline;
761 }