OSDN Git Service

ShellExtension: As with WinMergeU.exe, do not embed translation strings in ShellExten...
[winmerge-jp/winmerge-jp.git] / ShellExtension / WinMergeShell.cpp
index 7648b98..f2e8356 100644 (file)
@@ -47,6 +47,8 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 
+OBJECT_ENTRY_AUTO(CLSID_WinMergeShell, CWinMergeShell)
+
 /**
  * @brief Flags for enabling and other settings of context menu.
  */
@@ -56,6 +58,16 @@ enum ExtensionFlags
        EXT_ADVANCED = 0x02, /**< Advanced menuitems enabled/disabled. */
 };
 
+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 = 3;
 /// Registry path to WinMerge
@@ -72,9 +84,11 @@ static const TCHAR f_RegSettingsDir[] = REGDIR _T("\\Settings");
 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");
@@ -92,130 +106,107 @@ enum
        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__
-
-static String GetResourceString(UINT resourceID);
-
-class CWinMergeTempLocale
+// 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)
 {
-private:
-       LCID m_lcidOld;
-public:
-       CWinMergeTempLocale()
-       {
-               CRegKeyEx reg;
-               if (reg.Open(HKEY_CURRENT_USER, f_RegLocaleDir) != ERROR_SUCCESS)
-                       return;
-
-               m_lcidOld = GetThreadLocale();
+    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;
+}
 
-               int iLangId = reg.ReadDword(f_LanguageId, (DWORD)-1);
-               if (iLangId != -1)
-               {
-                       SetThreadLocale(MAKELCID(iLangId, SORT_DEFAULT));
-                       SetThreadUILanguage(iLangId);
-               }
-       }
-       ~CWinMergeTempLocale()
-       {
-               SetThreadLocale(m_lcidOld);
-               SetThreadUILanguage(LANGIDFROMLCID(m_lcidOld));
-       }
-};
 
-/**
- * @brief Load a string from resource.
- * @param [in] Resource string ID.
- * @return String loaded from resource.
- */
-static String GetResourceString(UINT resourceID)
+VERSIONHELPERAPI
+IsWindows8OrGreater()
 {
-       TCHAR resStr[1024] = {0};
-       int res = LoadString(_Module.GetModuleInstance(), resourceID, resStr, 1024);
-       ATLASSERT(res != 0);
-       String strResource = resStr;
-       return strResource;
+    return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0);
 }
+#endif // VERSIONHELPERAPI
 
-static HBITMAP MakeBitmapBackColorTransparent(HBITMAP hbmSrc)
+HBITMAP ConvertHICONtoHBITMAP(HICON hIcon, int cx, int cy)
 {
-       HDC hdcSrc = CreateCompatibleDC(NULL);
-       BITMAP bm;
-       GetObject(hbmSrc, sizeof(bm), &bm);
-       HBITMAP hbmSrcOld = (HBITMAP)SelectObject(hdcSrc, hbmSrc);
-       BITMAPINFO bi = {0};
-       bi.bmiHeader.biSize = sizeof BITMAPINFOHEADER;
-       bi.bmiHeader.biPlanes = 1;
-       bi.bmiHeader.biBitCount = 32;
-       bi.bmiHeader.biCompression = BI_RGB;
-       bi.bmiHeader.biWidth = bm.bmWidth;
-       bi.bmiHeader.biHeight = -bm.bmHeight;
-       DWORD *pBits = NULL;
-       HBITMAP hbmNew = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, (void **)&pBits, NULL, 0);
-       if (pBits)
+       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)
        {
-               COLORREF clrTP = GetPixel(hdcSrc, 0, 0);
-               int bR = GetRValue(clrTP), bG = GetGValue(clrTP), bB = GetBValue(clrTP);
-       
-               for (int y = 0; y < bm.bmHeight; ++y)
+               HBITMAP hbmpPrev = (HBITMAP)SelectObject(hdcMem, hbmp);
+               RECT rc = { 0, 0, cx, cy };
+               if (bmi.bmiHeader.biBitCount <= 24)
                {
-                       for (int x = 0; x < bm.bmWidth; ++x)
-                       {
-                               COLORREF clrCur = GetPixel(hdcSrc, x, y);
-                               int cR = GetRValue(clrCur), cG = GetGValue(clrCur), cB = GetBValue(clrCur);
-                               if (!(abs(cR - bR) <= 1 && abs(cG - bG) <= 1 && abs(cB - bB) <= 1))
-                               {
-                                       pBits[y * bm.bmWidth + x] = cB | (cG << 8) | (cR << 16) | 0xFF000000;
-                               }
-                       }
+                       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);
        }
-
-       SelectObject(hdcSrc, hbmSrcOld);
-       DeleteDC(hdcSrc);
-
-       return hbmNew;
+       DeleteDC(hdcMem);
+       return hbmp;
 }
 
-
 /////////////////////////////////////////////////////////////////////////////
 // CWinMergeShell
 
 /// 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);
-       int id_fileicon = cx > 16 ? IDB_WINMERGE32 : IDB_WINMERGE;
-       int id_diricon = cx > 16 ? IDB_WINMERGEDIR32 : IDB_WINMERGEDIR;
 
        // compress or stretch icon bitmap according to menu item height
-       HBITMAP hMergeBmp = (HBITMAP)LoadImage(_Module.GetModuleInstance(), MAKEINTRESOURCE(id_fileicon), IMAGE_BITMAP,
-                       cx, cy, LR_DEFAULTCOLOR);
-       HBITMAP hMergeDirBmp = (HBITMAP)LoadImage(_Module.GetModuleInstance(), MAKEINTRESOURCE(id_diricon), IMAGE_BITMAP,
-                       cx, cy, LR_DEFAULTCOLOR);
-
-       OSVERSIONINFO osvi;
-       osvi.dwOSVersionInfoSize = sizeof OSVERSIONINFO;
-       GetVersionEx(&osvi);
-       if (osvi.dwMajorVersion >= 6)
-       {
-               m_MergeBmp = MakeBitmapBackColorTransparent(hMergeBmp);
-               DeleteObject(hMergeBmp);
+       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));
 
-               m_MergeDirBmp = MakeBitmapBackColorTransparent(hMergeDirBmp);
-               DeleteObject(hMergeDirBmp);
-       }
-       else
-       {
-               m_MergeBmp = hMergeBmp;
-               m_MergeDirBmp = hMergeDirBmp;
-       }
 }
 
 /// Default destructor, unloads bitmap
@@ -229,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)
        {
@@ -317,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)
@@ -330,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
        {
@@ -338,23 +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);
 }
@@ -364,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)
@@ -382,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
@@ -406,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)
@@ -420,7 +426,7 @@ HRESULT CWinMergeShell::InvokeCommand(LPCMINVOKECOMMANDINFO pCmdInfo)
        if (!PathFileExists(strWinMergePath.c_str()))
                return S_FALSE;
 
-       if (LOWORD(pCmdInfo->lpVerb) == 0)
+       if (LOWORD(pCmdInfo->lpVerb) == CMD_COMPARE)
        {
                switch (m_dwMenuState)
                {
@@ -428,45 +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
@@ -478,11 +511,8 @@ HRESULT CWinMergeShell::InvokeCommand(LPCMINVOKECOMMANDINFO pCmdInfo)
        if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0)
                bAlterSubFolders = TRUE;
 
-       String strCommandLine = FormatCmdLine(strWinMergePath, m_strPaths[0],
-                       m_strPaths[1], bAlterSubFolders);
-
-       if (!m_strPaths[2].empty())
-               strCommandLine += _T(" \"") + m_strPaths[2] + _T("\"");
+       String strCommandLine = FormatCmdLine(strWinMergePath,
+               m_strPaths[0], m_strPaths[1], m_strPaths[2], bAlterSubFolders);
 
        // Finally start a new WinMerge process
        BOOL retVal = FALSE;
@@ -494,14 +524,63 @@ HRESULT CWinMergeShell::InvokeCommand(LPCMINVOKECOMMANDINFO pCmdInfo)
                        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)
 {
@@ -525,8 +604,7 @@ BOOL CWinMergeShell::GetWinMergeDir(String &strDir)
 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
        HBITMAP hBitmap = PathIsDirectory(m_strPaths[0].c_str()) ? m_MergeDirBmp : m_MergeBmp;
@@ -537,17 +615,13 @@ int CWinMergeShell::DrawSimpleMenu(HMENU hmenu, UINT uMenuIndex,
        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)
@@ -555,24 +629,25 @@ int CWinMergeShell::DrawAdvancedMenu(HMENU hmenu, UINT uMenuIndex,
                // 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
        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;
 
@@ -580,14 +655,12 @@ int CWinMergeShell::DrawAdvancedMenu(HMENU hmenu, UINT uMenuIndex,
                // Select both items for compare
        case MENU_TWOSEL:
        case MENU_THREESEL:
-               InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd,
-                               strCompare.c_str());
+               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;
        }
@@ -596,20 +669,18 @@ int CWinMergeShell::DrawAdvancedMenu(HMENU hmenu, UINT uMenuIndex,
        HBITMAP hBitmap = PathIsDirectory(m_strPaths[0].c_str()) ? m_MergeDirBmp : m_MergeBmp;
        if (hBitmap != NULL)
        {
-               if (nItemsAdded == 2)
-                       SetMenuItemBitmaps(hmenu, uMenuIndex - 1, MF_BYPOSITION, hBitmap, NULL);
-               SetMenuItemBitmaps(hmenu, uMenuIndex, MF_BYPOSITION, hBitmap, 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
@@ -624,49 +695,48 @@ 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);;
-                       break;
-
-               case MENU_ONESEL_NOPREV:
-                       strHelp = GetResourceString(IDS_HELP_SAVETHIS);
-                       break;
-
                case MENU_ONESEL_PREV:
                        strHelp = GetResourceString(IDS_HELP_COMPARESAVED);
-                       string_replace(strHelp, _T("%1"), m_strPreviousPath);
+                       strutils::replace(strHelp, _T("%1"), m_strPreviousPath[0]);
                        break;
 
-               case MENU_TWOSEL:
-               case MENU_THREESEL:
-                       strHelp = GetResourceString(IDS_CONTEXT_HELP);
-                       break;
-               }
-       }
-       else if (idCmd == 1)
-       {
-               switch (m_dwMenuState)
-               {
-               case MENU_ONESEL_PREV:
-                       strHelp = GetResourceString(IDS_HELP_SAVETHIS);
+               case MENU_ONESEL_TWO_PREV:
+                       strHelp = GetResourceString(IDS_HELP_COMPARESAVED);
+                       strutils::replace(strHelp, _T("%1"), m_strPreviousPath[0] + _T(" - ") + m_strPreviousPath[1]);
                        break;
                default:
                        strHelp = GetResourceString(IDS_CONTEXT_HELP);
                        break;
                }
        }
+       else if (idCmd == CMD_COMPARE_ELLIPSE)
+       {
+               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 = _T("\"") + winmergePath + _T("\"");
+       String strCommandline = winmergePath.empty() ? _T("") : _T("\"") + winmergePath + _T("\"");
 
        // Check if user wants to use context menu
        BOOL bSubfoldersByDefault = FALSE;
@@ -684,5 +754,8 @@ String CWinMergeShell::FormatCmdLine(const String &winmergePath,
        if (!m_strPaths[1].empty())
                strCommandline += _T(" \"") + path2 + _T("\"");
 
+       if (!m_strPaths[2].empty())
+               strCommandline += _T(" \"") + path3 + _T("\"");
+
        return strCommandline;
 }