OSDN Git Service

ShellExtension: As with WinMergeU.exe, do not embed translation strings in ShellExten...
[winmerge-jp/winmerge-jp.git] / ShellExtension / WinMergeShell.cpp
index bcba445..f2e8356 100644 (file)
@@ -4,7 +4,7 @@
 //    it under the terms of the GNU General Public License as published by
 //    the Free Software Foundation; either version 2 of the License, or (at
 //    your option) any later version.
-//    
+//
 //    This program is distributed in the hope that it will be useful, but
 //    WITHOUT ANY WARRANTY; without even the implied warranty of
 //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
@@ -16,7 +16,7 @@
 /////////////////////////////////////////////////////////////////////////////
 // Look at http://www.codeproject.com/shell/ for excellent guide
 // to Windows Shell programming by Michael Dunn.
-// 
+//
 // This extension needs two registry values to be defined:
 //  HKEY_CURRENT_USER\Software\Thingamahoochie\WinMerge\ContextMenuEnabled
 //   defines if context menu is shown (extension enabled) and if
 //   overwrites 'Executable' if defined. Useful to overwrite
 //   option set from UI when debugging/testing.
 /////////////////////////////////////////////////////////////////////////////
-/** 
+/**
  * @file  WinMergeShell.cpp
  *
  * @brief Implementation of the ShellExtension class
  */
 // ID line follows -- this is updated by SVN
-// $Id$
+// $Id: WinMergeShell.cpp 6933 2009-07-26 14:07:03Z kimmov $
 
 #include "stdafx.h"
 #include "ShellExtension.h"
 #include "WinMergeShell.h"
 #include "UnicodeString.h"
 #include "RegKey.h"
-#include "coretools.h"
 #include <sys/types.h>
 #include <sys/stat.h>
 
-/** 
+OBJECT_ENTRY_AUTO(CLSID_WinMergeShell, CWinMergeShell)
+
+/**
  * @brief Flags for enabling and other settings of context menu.
  */
 enum ExtensionFlags
 {
        EXT_ENABLED = 0x01, /**< ShellExtension enabled/disabled. */
        EXT_ADVANCED = 0x02, /**< Advanced menuitems enabled/disabled. */
-       EXT_SUBFOLDERS = 0x04, /**< Subfolders included by default? */
+};
+
+enum
+{
+       CMD_COMPARE = 0,
+       CMD_COMPARE_ELLIPSE,
+       CMD_SELECT_LEFT,
+       CMD_SELECT_MIDDLE,
+       CMD_RESELECT_LEFT,
+       CMD_LAST = CMD_RESELECT_LEFT,
 };
 
 /// Max. filecount to select
-static const int MaxFileCount = 2;
-/// Registry path to WinMerge 
+static const int MaxFileCount = 3;
+/// Registry path to WinMerge
 #define REGDIR _T("Software\\Thingamahoochie\\WinMerge")
 static const TCHAR f_RegDir[] = REGDIR;
 static const TCHAR f_RegLocaleDir[] = REGDIR _T("\\Locale");
+static const TCHAR f_RegSettingsDir[] = REGDIR _T("\\Settings");
 
 /**
  * @name Registry valuenames.
  */
-/*@{*/ 
+/*@{*/
 /** Shell context menuitem enabled/disabled */
 static const TCHAR f_RegValueEnabled[] = _T("ContextMenuEnabled");
 /** 'Saved' path in advanced mode */
 static const TCHAR f_FirstSelection[] = _T("FirstSelection");
-/** Path to WinMerge[U].exe */
+/** 'Saved' path in advanced mode */
+static const TCHAR f_SecondSelection[] = _T("SecondSelection");
+/** Path to WinMergeU.exe */
 static const TCHAR f_RegValuePath[] = _T("Executable");
-/** Path to WinMerge[U].exe, overwrites f_RegValuePath if present. */
+/** Path to WinMergeU.exe, overwrites f_RegValuePath if present. */
 static const TCHAR f_RegValuePriPath[] = _T("PriExecutable");
 /** LanguageId */
 static const TCHAR f_LanguageId[] = _T("LanguageId");
+/** Recurse */
+static const TCHAR f_Recurse[] = _T("Recurse");
 /*@}*/
 
-/// Shown menustate
+/**
+ * @brief The states in which the menu can be.
+ * These states define what items are added to the menu and how those
+ * items work.
+ */
 enum
 {
-       MENU_SIMPLE = 0,
-       MENU_ONESEL_NOPREV,
-       MENU_ONESEL_PREV,
-       MENU_TWOSEL,
+       MENU_SIMPLE = 0,  /**< Simple menu, only "Compare item" is shown. */
+       MENU_ONESEL_NOPREV,  /**< One item selected, no previous selections. */
+       MENU_ONESEL_PREV,  /**< One item selected, previous selection exists. */
+       MENU_ONESEL_TWO_PREV,  /**< One item selected, two previous selections exist. */
+       MENU_TWOSEL,  /**< Two items are selected. */
+       MENU_THREESEL
 };
 
-#define USES_WINMERGELOCALE CWinMergeTempLocale __wmtl__
+// GreyMerlin (03 Sept 2017) - The following Version Info checking code is a 
+// short extract from the Microsoft <versionhelpers.h> file.  Unfortunatly, 
+// that file is not available for WinXP-compatible Platform Toolsets (e.g. 
+// v141_xp for VS2017).  Fortunatly, all the actual API interfaces do exist 
+// in WinXP (actually, in all Windows products since Win2000).  Use of this 
+// <versionhelpers.h> code avoids the unpleasant deprecation of the GetVersionEx()
+// API begining with Win 8.1.  This Version Info checking code is also fully 
+// compatible with all non-XP-compatible Toolsets as well (e.g. v141).
+
+#ifndef _WIN32_WINNT_VISTA
+#define _WIN32_WINNT_VISTA     0x0600
+#endif
+#ifndef _WIN32_WINNT_WIN8
+#define _WIN32_WINNT_WIN8      0x0602
+#endif
+
+#ifndef VERSIONHELPERAPI
+#define VERSIONHELPERAPI inline bool
+
+VERSIONHELPERAPI
+IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor)
+{
+    OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 };
+    DWORDLONG        const dwlConditionMask = VerSetConditionMask(
+        VerSetConditionMask(
+        VerSetConditionMask(
+            0, VER_MAJORVERSION, VER_GREATER_EQUAL),
+               VER_MINORVERSION, VER_GREATER_EQUAL),
+               VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
+
+    osvi.dwMajorVersion = wMajorVersion;
+    osvi.dwMinorVersion = wMinorVersion;
+    osvi.wServicePackMajor = wServicePackMajor;
+
+    return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;
+}
 
-static String GetResourceString(UINT resourceID);
 
-class CWinMergeTempLocale
+VERSIONHELPERAPI
+IsWindows8OrGreater()
 {
-private:
-       LCID m_lcidOld;
-public:
-       CWinMergeTempLocale() {
-               CRegKeyEx reg;
-               if (reg.Open(HKEY_CURRENT_USER, f_RegLocaleDir) != ERROR_SUCCESS)
-                       return;
-
-               m_lcidOld = GetThreadLocale();
-
-               int iLangId = reg.ReadDword(f_LanguageId, (DWORD)-1);
-               if (iLangId != -1)
-                       SetThreadLocale(MAKELCID(iLangId, SORT_DEFAULT));
-       }
-       ~CWinMergeTempLocale() {
-               SetThreadLocale(m_lcidOld);
-       }
-};
+    return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0);
+}
+#endif // VERSIONHELPERAPI
 
-/**
- * @brief Load a string from resource.
- * @param [in] Resource string ID.
- * @return String loaded from resource.
- */
-static String GetResourceString(UINT resourceID)
+HBITMAP ConvertHICONtoHBITMAP(HICON hIcon, int cx, int cy)
 {
-       TCHAR resStr[1024] = {0};
-       int res = LoadString(_Module.GetModuleInstance(), resourceID, resStr, 1024);
-       ATLASSERT(res!= 0);
-       String strResource = resStr;
-       return strResource;
+       LPVOID lpBits;
+       BITMAPINFO bmi = { { sizeof(BITMAPINFOHEADER), cx, cy, 1, IsWindows8OrGreater() ? 32u : 24u } };
+       HDC hdcMem = CreateCompatibleDC(NULL);
+       HBITMAP hbmp = CreateDIBSection(NULL, (BITMAPINFO*)&bmi, DIB_RGB_COLORS, &lpBits, NULL, 0);
+       if (hbmp)
+       {
+               HBITMAP hbmpPrev = (HBITMAP)SelectObject(hdcMem, hbmp);
+               RECT rc = { 0, 0, cx, cy };
+               if (bmi.bmiHeader.biBitCount <= 24)
+               {
+                       SetBkColor(hdcMem, GetSysColor(COLOR_MENU));
+                       ExtTextOut(hdcMem, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
+               }
+               DrawIconEx(hdcMem, 0, 0, hIcon, cx, cy, 0, NULL, DI_NORMAL);
+               SelectObject(hdcMem, hbmpPrev);
+       }
+       DeleteDC(hdcMem);
+       return hbmp;
 }
 
 /////////////////////////////////////////////////////////////////////////////
@@ -134,17 +183,36 @@ static String GetResourceString(UINT resourceID)
 
 /// Default constructor, loads icon bitmap
 CWinMergeShell::CWinMergeShell()
+       : m_dwContextMenuEnabled(false)
+       , m_nSelectedItems(0)
+       , m_dwMenuState(0)
+       , m_langID(GetUserDefaultUILanguage())
 {
-       m_dwMenuState = 0;
+       int cx = GetSystemMetrics(SM_CXMENUCHECK);
+       int cy = GetSystemMetrics(SM_CYMENUCHECK);
 
        // compress or stretch icon bitmap according to menu item height
-       m_MergeBmp = (HBITMAP)LoadImage(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDB_WINMERGE), IMAGE_BITMAP, 
-               GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK), LR_DEFAULTCOLOR);
+       HICON hMergeIcon = (HICON)LoadImage(_AtlComModule.m_hInstTypeLib, MAKEINTRESOURCE(IDI_WINMERGE), IMAGE_ICON,
+               cx, cy, LR_DEFAULTCOLOR);
+       HICON hMergeDirIcon = (HICON)LoadImage(_AtlComModule.m_hInstTypeLib, MAKEINTRESOURCE(IDI_WINMERGEDIR), IMAGE_ICON,
+               cx, cy, LR_DEFAULTCOLOR);
+
+       m_MergeBmp = ConvertHICONtoHBITMAP(hMergeIcon, cx, cy);
+       m_MergeDirBmp = ConvertHICONtoHBITMAP(hMergeDirIcon, cx, cy);
+
+       DestroyIcon(hMergeIcon);
+       DestroyIcon(hMergeDirIcon);
+
+       CRegKeyEx reg;
+       if (reg.Open(HKEY_CURRENT_USER, f_RegLocaleDir) == ERROR_SUCCESS)
+               m_langID = static_cast<LANGID>(reg.ReadDword(f_LanguageId, m_langID));
+
 }
 
 /// Default destructor, unloads bitmap
 CWinMergeShell::~CWinMergeShell()
 {
+       DeleteObject(m_MergeDirBmp);
        DeleteObject(m_MergeBmp);
 }
 
@@ -152,9 +220,11 @@ CWinMergeShell::~CWinMergeShell()
 HRESULT CWinMergeShell::Initialize(LPCITEMIDLIST pidlFolder,
                LPDATAOBJECT pDataObj, HKEY hProgID)
 {
-       USES_WINMERGELOCALE;
        HRESULT hr = E_INVALIDARG;
 
+       for (auto& path: m_strPaths)
+               path.erase();
+
        // Files/folders selected normally from the explorer
        if (pDataObj)
        {
@@ -175,7 +245,7 @@ HRESULT CWinMergeShell::Initialize(LPCITEMIDLIST pidlFolder,
                        return E_INVALIDARG;
 
                // Sanity check & make sure there is at least one filename.
-               UINT uNumFilesDropped = DragQueryFile (hDropInfo, 0xFFFFFFFF, NULL, 0);
+               UINT uNumFilesDropped = DragQueryFile(hDropInfo, 0xFFFFFFFF, NULL, 0);
                m_nSelectedItems = uNumFilesDropped;
 
                if (uNumFilesDropped == 0)
@@ -212,20 +282,26 @@ HRESULT CWinMergeShell::Initialize(LPCITEMIDLIST pidlFolder,
                GlobalUnlock(stg.hGlobal);
                ReleaseStgMedium(&stg);
        }
+       else
+       {
+               m_nSelectedItems = 0;
+       }
 
-       // No item selected - selection is the folder background
+               // No item selected - selection is the folder background
        if (pidlFolder)
        {
                TCHAR szPath[MAX_PATH] = {0};
 
                if (SHGetPathFromIDList(pidlFolder, szPath))
                {
-                       m_strPaths[0] = szPath;
-                       m_nSelectedItems = 1;
+                       if (m_nSelectedItems < MaxFileCount)
+                               m_strPaths[m_nSelectedItems++] = szPath;
                        hr = S_OK;
                }
                else
+               {
                        hr = E_INVALIDARG;
+               }
        }
        return hr;
 }
@@ -234,8 +310,22 @@ HRESULT CWinMergeShell::Initialize(LPCITEMIDLIST pidlFolder,
 HRESULT CWinMergeShell::QueryContextMenu(HMENU hmenu, UINT uMenuIndex,
                UINT uidFirstCmd, UINT uidLastCmd, UINT uFlags)
 {
-       int nItemsAdded = 0;
-       USES_WINMERGELOCALE;
+       // check whether menu items are already added
+       if (hmenu == s_hMenuLastAdded)
+       {
+               MENUITEMINFO mii{ sizeof mii };
+               mii.fMask = MIIM_DATA;
+               if (GetMenuItemInfo(hmenu, s_uidCmdLastAdded, FALSE, &mii))
+               {
+                       if (mii.dwItemData >= IDS_COMPARE && mii.dwItemData <= IDS_RESELECT_LEFT)
+                               return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
+               }
+       }
+
+       s_hMenuLastAdded = hmenu;
+       s_uidCmdLastAdded = uidFirstCmd;
+
+       int uidUserLastCmd = 0;
 
        // If the flags include CMF_DEFAULTONLY then we shouldn't do anything.
        if (uFlags & CMF_DEFAULTONLY)
@@ -247,7 +337,8 @@ HRESULT CWinMergeShell::QueryContextMenu(HMENU hmenu, UINT uMenuIndex,
                return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
 
        m_dwContextMenuEnabled = reg.ReadDword(f_RegValueEnabled, 0);
-       m_strPreviousPath = reg.ReadString(f_FirstSelection, _T("")).c_str();
+       m_strPreviousPath[0] = reg.ReadString(f_FirstSelection, _T("")).c_str();
+       m_strPreviousPath[1] = reg.ReadString(f_SecondSelection, _T("")).c_str();
 
        if (m_dwContextMenuEnabled & EXT_ENABLED) // Context menu enabled
        {
@@ -255,21 +346,25 @@ HRESULT CWinMergeShell::QueryContextMenu(HMENU hmenu, UINT uMenuIndex,
                if ((m_dwContextMenuEnabled & EXT_ADVANCED) == 0)
                {
                        m_dwMenuState = MENU_SIMPLE;
-                       nItemsAdded = DrawSimpleMenu(hmenu, uMenuIndex, uidFirstCmd);
+                       uidUserLastCmd = DrawSimpleMenu(hmenu, uMenuIndex, uidFirstCmd);
                }
                else
                {
-                       if (m_nSelectedItems == 1 && m_strPreviousPath.empty())
+                       if (m_nSelectedItems == 1 && m_strPreviousPath[0].empty())
                                m_dwMenuState = MENU_ONESEL_NOPREV;
-                       else if (m_nSelectedItems == 1 && !m_strPreviousPath.empty())
+                       else if (m_nSelectedItems == 1 && !m_strPreviousPath[0].empty() && m_strPreviousPath[1].empty())
                                m_dwMenuState = MENU_ONESEL_PREV;
+                       else if (m_nSelectedItems == 1 && !m_strPreviousPath[0].empty() && !m_strPreviousPath[1].empty())
+                               m_dwMenuState = MENU_ONESEL_TWO_PREV;
                        else if (m_nSelectedItems == 2)
                                m_dwMenuState = MENU_TWOSEL;
+                       else if (m_nSelectedItems == 3)
+                               m_dwMenuState = MENU_THREESEL;
 
-                       nItemsAdded = DrawAdvancedMenu(hmenu, uMenuIndex, uidFirstCmd);
+                       uidUserLastCmd = DrawAdvancedMenu(hmenu, uMenuIndex, uidFirstCmd);
                }
 
-               return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, nItemsAdded);
+               return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, (uidUserLastCmd - uidFirstCmd) + 1);
        }
        return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
 }
@@ -279,7 +374,6 @@ HRESULT CWinMergeShell::GetCommandString(UINT_PTR idCmd, UINT uFlags,
                UINT* pwReserved, LPSTR pszName, UINT  cchMax)
 {
        USES_CONVERSION;
-       USES_WINMERGELOCALE;
 
        // Check idCmd, it must be 0 in simple mode and 0 or 1 in advanced mode.
        if ((m_dwMenuState & EXT_ADVANCED) == 0)
@@ -289,7 +383,7 @@ HRESULT CWinMergeShell::GetCommandString(UINT_PTR idCmd, UINT uFlags,
        }
        else
        {
-               if (idCmd > 1)
+               if (idCmd > 2)
                        return E_INVALIDARG;
        }
 
@@ -297,9 +391,7 @@ HRESULT CWinMergeShell::GetCommandString(UINT_PTR idCmd, UINT uFlags,
        // supplied buffer.
        if (uFlags & GCS_HELPTEXT)
        {
-               String strHelp;
-
-               strHelp = GetHelpText(idCmd);
+               String strHelp = GetHelpText(idCmd);
 
                if (uFlags & GCS_UNICODE)
                        // We need to cast pszName to a Unicode string, and then use the
@@ -321,7 +413,6 @@ HRESULT CWinMergeShell::InvokeCommand(LPCMINVOKECOMMANDINFO pCmdInfo)
        String strWinMergePath;
        BOOL bCompare = FALSE;
        BOOL bAlterSubFolders = FALSE;
-       USES_WINMERGELOCALE;
 
        // If lpVerb really points to a string, ignore this function call and bail out.
        if (HIWORD(pCmdInfo->lpVerb) != 0)
@@ -331,11 +422,11 @@ HRESULT CWinMergeShell::InvokeCommand(LPCMINVOKECOMMANDINFO pCmdInfo)
        if (!GetWinMergeDir(strWinMergePath))
                return S_FALSE;
 
-       // Check that file we are trying to execute exists and is executable
-       if (!CheckExecutable(strWinMergePath))
+       // Check that file we are trying to execute exists
+       if (!PathFileExists(strWinMergePath.c_str()))
                return S_FALSE;
 
-       if (LOWORD(pCmdInfo->lpVerb) == 0)
+       if (LOWORD(pCmdInfo->lpVerb) == CMD_COMPARE)
        {
                switch (m_dwMenuState)
                {
@@ -343,44 +434,72 @@ HRESULT CWinMergeShell::InvokeCommand(LPCMINVOKECOMMANDINFO pCmdInfo)
                        bCompare = TRUE;
                        break;
 
-               case MENU_ONESEL_NOPREV:
-                       m_strPreviousPath = m_strPaths[0];
+               case MENU_ONESEL_PREV:
+                       m_strPaths[1] = m_strPaths[0];
+                       m_strPaths[0] = m_strPreviousPath[0];
+                       bCompare = TRUE;
+
+                       // Forget previous selection
                        if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
-                               reg.WriteString(f_FirstSelection, m_strPreviousPath.c_str());
+                       {
+                               reg.WriteString(f_FirstSelection, _T(""));
+                               reg.WriteString(f_SecondSelection, _T(""));
+                       }
                        break;
 
-               case MENU_ONESEL_PREV:
-                       m_strPaths[1] = m_strPaths[0];
-                       m_strPaths[0] = m_strPreviousPath;
+               case MENU_ONESEL_TWO_PREV:
+                       m_strPaths[2] = m_strPaths[0];
+                       m_strPaths[0] = m_strPreviousPath[0];
+                       m_strPaths[1] = m_strPreviousPath[1];
                        bCompare = TRUE;
-                       
+
                        // Forget previous selection
                        if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
+                       {
                                reg.WriteString(f_FirstSelection, _T(""));
+                               reg.WriteString(f_SecondSelection, _T(""));
+                       }
                        break;
 
                case MENU_TWOSEL:
+               case MENU_THREESEL:
                        // "Compare" - compare paths
                        bCompare = TRUE;
-                       m_strPreviousPath.erase();
+                       m_strPreviousPath[0].erase();
+                       m_strPreviousPath[1].erase();
                        break;
                }
        }
-       else if (LOWORD(pCmdInfo->lpVerb) == 1)
+       else if (LOWORD(pCmdInfo->lpVerb) == CMD_COMPARE_ELLIPSE)
        {
-               switch (m_dwMenuState)
+               // "Compare..." - user wants to compare this single item and open WinMerge
+               m_strPaths[1].erase();
+               m_strPaths[2].erase();
+               bCompare = TRUE;
+       }
+       else if (LOWORD(pCmdInfo->lpVerb) == CMD_SELECT_LEFT)
+       {
+               // Select Left
+               m_strPreviousPath[0] = m_strPaths[0];
+               if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
+                       reg.WriteString(f_FirstSelection, m_strPreviousPath[0].c_str());
+       }
+       else if (LOWORD(pCmdInfo->lpVerb) == CMD_SELECT_MIDDLE)
+       {
+               // Select Middle
+               m_strPreviousPath[1] = m_strPaths[0];
+               if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
+                       reg.WriteString(f_SecondSelection, m_strPreviousPath[1].c_str());
+       }
+       else if (LOWORD(pCmdInfo->lpVerb) == CMD_RESELECT_LEFT)
+       {
+               // Re-select Left
+               m_strPreviousPath[0] = m_strPaths[0];
+               m_strPreviousPath[1].clear();
+               if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
                {
-               case MENU_ONESEL_PREV:
-                       m_strPreviousPath = m_strPaths[0];
-                       if (reg.Open(HKEY_CURRENT_USER, f_RegDir) == ERROR_SUCCESS)
-                               reg.WriteString(f_FirstSelection, m_strPreviousPath.c_str());
-                       bCompare = FALSE;
-                       break;
-               default:
-                       // "Compare..." - user wants to compare this single item and open WinMerge
-                       m_strPaths[1].erase();
-                       bCompare = TRUE;
-                       break;
+                       reg.WriteString(f_FirstSelection, m_strPreviousPath[0].c_str());
+                       reg.WriteString(f_SecondSelection, _T(""));
                }
        }
        else
@@ -392,34 +511,83 @@ HRESULT CWinMergeShell::InvokeCommand(LPCMINVOKECOMMANDINFO pCmdInfo)
        if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0)
                bAlterSubFolders = TRUE;
 
-       String strCommandLine = FormatCmdLine(strWinMergePath, m_strPaths[0],
-               m_strPaths[1], bAlterSubFolders);
+       String strCommandLine = FormatCmdLine(strWinMergePath,
+               m_strPaths[0], m_strPaths[1], m_strPaths[2], bAlterSubFolders);
 
        // Finally start a new WinMerge process
        BOOL retVal = FALSE;
        STARTUPINFO stInfo = {0};
        stInfo.cb = sizeof(STARTUPINFO);
        PROCESS_INFORMATION processInfo = {0};
-       
+
        retVal = CreateProcess(NULL, (LPTSTR)strCommandLine.c_str(),
-               NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
-               &stInfo, &processInfo);
+                       NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
+                       &stInfo, &processInfo);
 
-       if (!retVal)
+       if (retVal)
+       {
+               CloseHandle(processInfo.hThread);
+               CloseHandle(processInfo.hProcess);
+       }
+       else if (GetLastError() == ERROR_ELEVATION_REQUIRED)
+       {
+               String strCommandLine = FormatCmdLine(_T(""),
+                       m_strPaths[0], m_strPaths[1], m_strPaths[2], bAlterSubFolders);
+               HINSTANCE hInstance = ShellExecute(nullptr, _T("runas"), strWinMergePath.c_str(), strCommandLine.c_str(), 0, SW_SHOWNORMAL);
+               if (reinterpret_cast<intptr_t>(hInstance) < 32)
+                       return S_FALSE;
+       }
+       else
+       {
                return S_FALSE;
+       }
 
-       CloseHandle(processInfo.hThread);
-       CloseHandle(processInfo.hProcess);
        return S_OK;
 }
 
+/**
+ * @brief Load a string from resource.
+ * @param [in] Resource string ID.
+ * @return String loaded from resource.
+ */
+String CWinMergeShell::GetResourceString(UINT resourceID)
+{
+       if (!s_pLang)
+               s_pLang = new CLanguageSelect();
+       if (s_pLang->GetLangId() != m_langID)
+       {
+               TCHAR szFileName[1024] = {0};
+               GetModuleFileName(_AtlComModule.m_hInstTypeLib, szFileName, sizeof(szFileName) / sizeof(TCHAR));
+               PathRemoveFileSpec(szFileName);
+               String languagesFolder = String(szFileName) + _T("\\Languages\\ShellExtension");
+               s_pLang->LoadLanguageFile(m_langID, languagesFolder);
+       }
+       TCHAR resStr[1024] = {0};
+       int res = LoadString(_AtlComModule.m_hInstTypeLib, resourceID, resStr, 1024);
+       ATLASSERT(res != 0);
+       String strResource;
+       s_pLang->TranslateString(resStr, strResource);
+       return strResource;
+}
+
+BOOL CWinMergeShell::InsertMenuString(HMENU hMenu, UINT uPosition, UINT uIDNewItem, UINT uStringId)
+{
+       String str = GetResourceString(uStringId);
+       MENUITEMINFO mii{sizeof mii};
+       mii.fMask = MIIM_ID | MIIM_STRING | MIIM_DATA;
+       mii.wID = uIDNewItem;
+       mii.dwTypeData = const_cast<LPTSTR>(str.c_str());
+       mii.dwItemData = uStringId;
+       return InsertMenuItem(hMenu, uPosition, TRUE, &mii);
+}
+
 /// Reads WinMerge path from registry
 BOOL CWinMergeShell::GetWinMergeDir(String &strDir)
 {
        CRegKeyEx reg;
        if (!reg.QueryRegUser(f_RegDir))
                return FALSE;
-       
+
        // Try first reading debug/test value
        strDir = reg.ReadString(f_RegValuePriPath, _T(""));
        if (strDir.empty())
@@ -427,117 +595,92 @@ BOOL CWinMergeShell::GetWinMergeDir(String &strDir)
                strDir = reg.ReadString(f_RegValuePath, _T(""));
                if (strDir.empty())
                        return FALSE;
-       }       
+       }
 
        return TRUE;
 }
 
-/// Checks if given file exists and is executable
-BOOL CWinMergeShell::CheckExecutable(String path)
-{
-       String sExt;
-       SplitFilename(path.c_str(), NULL, NULL, &sExt);
-
-       // Check extension
-       if (_tcsicmp(sExt.c_str(), _T("exe")) == 0 ||
-               _tcsicmp(sExt.c_str(), _T("cmd")) == 0 ||
-               _tcsicmp(sExt.c_str(), _T("bat")) == 0)
-       {
-               // Check if file exists
-               struct _stati64 statBuffer;
-               int nRetVal = _tstati64(path.c_str(), &statBuffer);
-               if (nRetVal > -1)
-                       return TRUE;
-       }
-       return FALSE;
-}
-
 /// Create menu for simple mode
 int CWinMergeShell::DrawSimpleMenu(HMENU hmenu, UINT uMenuIndex,
                UINT uidFirstCmd)
 {
-       String strMenu = GetResourceString(IDS_CONTEXT_MENU);
-       InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd, strMenu.c_str());
-       
+       InsertMenuString(hmenu, uMenuIndex, uidFirstCmd, IDS_CONTEXT_MENU);
+
        // Add bitmap
-       if (m_MergeBmp != NULL)
-               SetMenuItemBitmaps(hmenu, uMenuIndex, MF_BYPOSITION, m_MergeBmp, NULL);
-       
+       HBITMAP hBitmap = PathIsDirectory(m_strPaths[0].c_str()) ? m_MergeDirBmp : m_MergeBmp;
+       if (hBitmap != NULL)
+               SetMenuItemBitmaps(hmenu, uMenuIndex, MF_BYPOSITION, hBitmap, NULL);
+
        // Show menu item as grayed if more than two items selected
        if (m_nSelectedItems > MaxFileCount)
                EnableMenuItem(hmenu, uMenuIndex, MF_BYPOSITION | MF_GRAYED);
-       
-       return 1;
+
+       return uidFirstCmd + CMD_LAST;
 }
 
 /// Create menu for advanced mode
 int CWinMergeShell::DrawAdvancedMenu(HMENU hmenu, UINT uMenuIndex,
                UINT uidFirstCmd)
 {
-       String strCompare = GetResourceString(IDS_COMPARE);
-       String strCompareEllipsis = GetResourceString(IDS_COMPARE_ELLIPSIS);
-       String strCompareTo = GetResourceString(IDS_COMPARE_TO);
-       String strReselect = GetResourceString(IDS_RESELECT_FIRST);
        int nItemsAdded = 0;
-       
+
        switch (m_dwMenuState)
        {
-       // No items selected earlier
-       // Select item as first item to compare
+               // No items selected earlier
+               // Select item as first item to compare
        case MENU_ONESEL_NOPREV:
-               InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd,
-                       strCompareTo.c_str());
-               uMenuIndex++;
-               uidFirstCmd++;
-               InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd,
-                       strCompareEllipsis.c_str());
+               InsertMenuString(hmenu, uMenuIndex++, uidFirstCmd + CMD_SELECT_LEFT, IDS_SELECT_LEFT);
+               InsertMenuString(hmenu, uMenuIndex,   uidFirstCmd + CMD_COMPARE_ELLIPSE, IDS_COMPARE_ELLIPSIS);
                nItemsAdded = 2;
                break;
 
-       // One item selected earlier:
-       // Allow re-selecting first item or selecting second item
+               // One item selected earlier:
+               // Allow re-selecting first item or selecting second item
        case MENU_ONESEL_PREV:
-               InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd,
-                       strCompare.c_str());
-               uMenuIndex++;
-               uidFirstCmd++;
-               InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd,
-                       strReselect.c_str());
+               InsertMenuString(hmenu, uMenuIndex++, uidFirstCmd + CMD_COMPARE, IDS_COMPARE);
+               InsertMenuString(hmenu, uMenuIndex++, uidFirstCmd + CMD_SELECT_MIDDLE, IDS_SELECT_MIDDLE);
+               InsertMenuString(hmenu, uMenuIndex,   uidFirstCmd + CMD_RESELECT_LEFT, IDS_RESELECT_LEFT);
+               nItemsAdded = 3;
+               break;
+
+               // Two items are selected earlier:
+               // Allow re-selecting first item or selecting second item
+       case MENU_ONESEL_TWO_PREV:
+               InsertMenuString(hmenu, uMenuIndex++, uidFirstCmd + CMD_COMPARE, IDS_COMPARE);
+               InsertMenuString(hmenu, uMenuIndex,   uidFirstCmd + CMD_RESELECT_LEFT, IDS_RESELECT_LEFT);
                nItemsAdded = 2;
                break;
 
-       // Two items selected
-       // Select both items for compare
+               // Two items selected
+               // Select both items for compare
        case MENU_TWOSEL:
-               InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd,
-                       strCompare.c_str());
+       case MENU_THREESEL:
+               InsertMenuString(hmenu, uMenuIndex, uidFirstCmd + CMD_COMPARE, IDS_COMPARE);
                nItemsAdded = 1;
                break;
 
        default:
-               InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd,
-                       strCompare.c_str());
+               InsertMenuString(hmenu, uMenuIndex, uidFirstCmd + CMD_COMPARE, IDS_COMPARE);
                nItemsAdded = 1;
                break;
        }
-       
+
        // Add bitmap
-       if (m_MergeBmp != NULL)
+       HBITMAP hBitmap = PathIsDirectory(m_strPaths[0].c_str()) ? m_MergeDirBmp : m_MergeBmp;
+       if (hBitmap != NULL)
        {
-               if (nItemsAdded == 2)
-                       SetMenuItemBitmaps(hmenu, uMenuIndex - 1, MF_BYPOSITION, m_MergeBmp, NULL);
-               SetMenuItemBitmaps(hmenu, uMenuIndex, MF_BYPOSITION, m_MergeBmp, NULL);
+               for (int i = 0; i < nItemsAdded; i++)
+                       SetMenuItemBitmaps(hmenu, uMenuIndex - (nItemsAdded - 1 - i), MF_BYPOSITION, hBitmap, NULL);
        }
-       
+
        // Show menu item as grayed if more than two items selected
        if (m_nSelectedItems > MaxFileCount)
        {
-               if (nItemsAdded == 2)
-                       EnableMenuItem(hmenu, uMenuIndex - 1, MF_BYPOSITION | MF_GRAYED);
-               EnableMenuItem(hmenu, uMenuIndex, MF_BYPOSITION | MF_GRAYED);
+               for (int i = 0; i < nItemsAdded; i++)
+                       EnableMenuItem(hmenu, uMenuIndex - (nItemsAdded - 1 - i), MF_BYPOSITION | MF_GRAYED);
        }
 
-       return nItemsAdded;
+       return uidFirstCmd + CMD_LAST;
 }
 
 /// Determine help text shown in explorer's statusbar
@@ -552,63 +695,67 @@ String CWinMergeShell::GetHelpText(UINT_PTR idCmd)
                return strHelp;
        }
 
-       if (idCmd == 0)
+       if (idCmd == CMD_COMPARE)
        {
                switch (m_dwMenuState)
                {
-               case MENU_SIMPLE:
-                       strHelp = GetResourceString(IDS_CONTEXT_HELP);;
+               case MENU_ONESEL_PREV:
+                       strHelp = GetResourceString(IDS_HELP_COMPARESAVED);
+                       strutils::replace(strHelp, _T("%1"), m_strPreviousPath[0]);
                        break;
 
-               case MENU_ONESEL_NOPREV:
-                       strHelp = GetResourceString(IDS_HELP_SAVETHIS);
-                       break;
-               
-               case MENU_ONESEL_PREV:
+               case MENU_ONESEL_TWO_PREV:
                        strHelp = GetResourceString(IDS_HELP_COMPARESAVED);
-                       string_replace(strHelp, _T("%1"), m_strPreviousPath);
+                       strutils::replace(strHelp, _T("%1"), m_strPreviousPath[0] + _T(" - ") + m_strPreviousPath[1]);
                        break;
-               
-               case MENU_TWOSEL:
+               default:
                        strHelp = GetResourceString(IDS_CONTEXT_HELP);
                        break;
                }
        }
-       else if (idCmd == 1)
+       else if (idCmd == CMD_COMPARE_ELLIPSE)
        {
-               switch (m_dwMenuState)
-               {
-               case MENU_ONESEL_PREV:
-                       strHelp = GetResourceString(IDS_HELP_SAVETHIS);
-                       break;
-               default:
-                       strHelp = GetResourceString(IDS_CONTEXT_HELP);
-                       break;
-               }
+               strHelp = GetResourceString(IDS_CONTEXT_HELP);
+       }
+       else if (idCmd == CMD_SELECT_LEFT)
+       {
+               strHelp = GetResourceString(IDS_HELP_SAVETHIS);
+       }
+       else if (idCmd == CMD_SELECT_MIDDLE)
+       {
+               strHelp = GetResourceString(IDS_HELP_SAVETHIS);
+       }
+       else if (idCmd == CMD_RESELECT_LEFT)
+       {
+               strHelp = GetResourceString(IDS_HELP_SAVETHIS);
        }
        return strHelp;
 }
 
 /// Format commandline used to start WinMerge
 String CWinMergeShell::FormatCmdLine(const String &winmergePath,
-       const String &path1, const String &path2, BOOL bAlterSubFolders)
+               const String &path1, const String &path2, const String &path3, BOOL bAlterSubFolders)
 {
-       String strCommandline(winmergePath);
+       String strCommandline = winmergePath.empty() ? _T("") : _T("\"") + winmergePath + _T("\"");
 
        // Check if user wants to use context menu
        BOOL bSubfoldersByDefault = FALSE;
-       if (m_dwContextMenuEnabled & EXT_SUBFOLDERS) // User wants subfolders by def
-               bSubfoldersByDefault = TRUE;
+       CRegKeyEx reg;
+       if (reg.Open(HKEY_CURRENT_USER, f_RegSettingsDir) == ERROR_SUCCESS)
+               bSubfoldersByDefault = reg.ReadBool(f_Recurse, FALSE);
 
        if (bAlterSubFolders && !bSubfoldersByDefault)
                strCommandline += _T(" /r");
        else if (!bAlterSubFolders && bSubfoldersByDefault)
                strCommandline += _T(" /r");
-       
+
        strCommandline += _T(" \"") + path1 + _T("\"");
-       
+
        if (!m_strPaths[1].empty())
                strCommandline += _T(" \"") + path2 + _T("\"");
 
+       if (!m_strPaths[2].empty())
+               strCommandline += _T(" \"") + path3 + _T("\"");
+
        return strCommandline;
 }