PageRenderTime 118ms CodeModel.GetById 15ms app.highlight 95ms RepoModel.GetById 1ms app.codeStats 0ms

/win32/shellext/CShellExtCMenu.cpp

https://bitbucket.org/tortoisehg/hgtk/
C++ | 1056 lines | 867 code | 156 blank | 33 comment | 145 complexity | 4fd9fafab7810d6b62d9de7497f0e134 MD5 | raw file
   1#include "stdafx.h"
   2#include "TortoiseUtils.h"
   3#include "StringUtils.h"
   4#include "Thgstatus.h"
   5#include "Winstat.h"
   6#include "InitStatus.h"
   7#include "SysInfo.h"
   8#include "ShellExt.h"
   9#include "RegistryConfig.h"
  10#include "TortoiseIconBitmap.h"
  11#include "ThgVersion.h"
  12
  13#include "Msi.h"
  14
  15#include <map>
  16
  17#include "CShellExtCMenu.h"
  18
  19
  20struct MenuDescription
  21{
  22    std::string name;
  23    std::wstring menuText;
  24    std::wstring helpText;
  25    std::string iconName;
  26    UINT idCmd;
  27};
  28
  29// According to http://msdn.microsoft.com/en-us/library/bb776094%28VS.85%29.aspx
  30// the help texts for the commands should be reasonably short (under 40 characters)
  31
  32MenuDescription menuDescList[] =
  33{
  34    {"commit",      L"Commit...",
  35                    L"Commit changes in repository",
  36                    "menucommit.ico", 0},
  37    {"init",        L"Create Repository Here",
  38                    L"Create a new repository",
  39                    "menucreaterepos.ico", 0},
  40    {"clone",       L"Clone...",
  41                    L"Create clone here from source",
  42                    "menuclone.ico", 0},
  43    {"shelve",      L"Shelve Changes",
  44                    L"Shelve or unshelve file changes",
  45                    "shelve.ico", 0},
  46    {"status",      L"View File Status",
  47                    L"Repository status & changes",
  48                    "menushowchanged.ico", 0},
  49    {"add",         L"Add Files...",
  50                    L"Add files to version control",
  51                    "menuadd.ico", 0},
  52    {"revert",      L"Revert Files...",
  53                    L"Revert file changes",
  54                    "menurevert.ico", 0},
  55    {"remove",      L"Remove Files...",
  56                    L"Remove files from version control",
  57                    "menudelete.ico", 0},
  58    {"rename",      L"Rename File...",
  59                    L"Rename file or directory",
  60                    "general.ico", 0},
  61    {"workbench",   L"Workbench",
  62                    L"View change history of repository",
  63                    "menulog.ico", 0},
  64    {"log",         L"Revision History",
  65                    L"View change history of selected files",
  66                    "menulog.ico", 0},
  67    {"synch",       L"Synchronize",
  68                    L"Synchronize with remote repository",
  69                    "menusynch.ico", 0},
  70    {"serve",       L"Web Server",
  71                    L"Start web server for this repository",
  72                    "proxy.ico", 0},
  73    {"update",      L"Update...",
  74                    L"Update working directory",
  75                    "menucheckout.ico", 0},
  76    {"thgstatus",   L"Update Icons",
  77                    L"Update icons for this repository",
  78                    "refresh_overlays.ico", 0},
  79    {"userconf",    L"Global Settings",
  80                    L"Configure user wide settings",
  81                    "settings_user.ico", 0},
  82    {"repoconf",    L"Repository Settings",
  83                    L"Configure repository settings",
  84                    "settings_repo.ico", 0},
  85    {"about",       L"About TortoiseHg",
  86                    L"Show About Dialog",
  87                    "menuabout.ico", 0},
  88    {"vdiff",       L"Visual Diff",
  89                    L"View changes using GUI diff tool",
  90                    "TortoiseMerge.ico", 0},
  91    {"hgignore",    L"Edit Ignore Filter",
  92                    L"Edit repository ignore filter",
  93                    "ignore.ico", 0},
  94    {"guess",       L"Guess Renames",
  95                    L"Detect renames and copies",
  96                    "detect_rename.ico", 0},
  97    {"grep",        L"Search History",
  98                    L"Search file revisions for patterns",
  99                    "menurepobrowse.ico", 0},
 100    {"forget",      L"Forget Files...",
 101                    L"Remove files from version control",
 102                    "menudelete.ico", 0},
 103    {"shellconf",   L"Explorer Extension Settings",
 104                    L"Configure Explorer extension",
 105                    "settings_repo.ico", 0},
 106
 107    /* Add new items here */
 108
 109    // template
 110    //{"", L"", L"", ".ico", 0},
 111};
 112
 113const char* const RepoNoFilesMenu =
 114    "commit status shelve vdiff sep"
 115    " add revert rename forget remove sep"
 116    " workbench update grep sep"
 117    " synch serve clone init thgstatus sep"
 118    " hgignore guess sep"
 119    " shellconf repoconf userconf sep"
 120    " about"
 121;
 122
 123const char* const RepoFilesMenu =
 124    "commit status vdiff sep"
 125    " add revert rename forget remove sep"
 126    " log sep"
 127    " about"
 128;
 129
 130const char* const NoRepoMenu =
 131    "clone init shellconf userconf thgstatus sep"
 132    " workbench sep"
 133    " about"
 134;
 135
 136
 137typedef std::map<std::string, MenuDescription> MenuDescriptionMap;
 138typedef std::map<UINT, MenuDescription> MenuIdCmdMap;
 139
 140MenuDescriptionMap MenuDescMap;
 141MenuIdCmdMap MenuIdMap;
 142
 143
 144void AddMenuList(UINT idCmd, const std::string& name)
 145{
 146    TDEBUG_TRACE("AddMenuList: idCmd = " << idCmd << " name = " << name);
 147    MenuIdMap[idCmd] = MenuDescMap[name];
 148}
 149
 150
 151void GetCMenuTranslation(
 152    const std::string& lang,
 153    const std::string& name,
 154    std::wstring& menuText,
 155    std::wstring& helpText
 156)
 157{
 158    std::wstring subkey = L"Software\\TortoiseHg\\CMenu\\";
 159    subkey += _WCSTR(lang.c_str());
 160    subkey += L"\\";
 161    subkey += _WCSTR(name.c_str());
 162
 163    TDEBUG_TRACEW(L"GetCMenuTranslation: " << subkey);
 164
 165    HKEY hkey = 0;
 166    LONG rv = RegOpenKeyExW(
 167        HKEY_CURRENT_USER, subkey.c_str(), 0, KEY_READ, &hkey);
 168
 169    if (rv == ERROR_SUCCESS && hkey)
 170    {
 171        GetRegSZValueW(hkey, L"menuText", menuText);
 172        GetRegSZValueW(hkey, L"helpText", helpText);
 173    }
 174    else
 175    {
 176        TDEBUG_TRACEW(
 177            L"GetCMenuTranslation: RegOpenKeyExW(\"" 
 178            << subkey << "\") failed"
 179        );
 180    }
 181
 182    if (hkey)
 183        RegCloseKey(hkey);
 184}
 185
 186
 187void InitMenuMaps()
 188{
 189    if (MenuDescMap.empty())
 190    {
 191        std::string lang;
 192        GetRegistryConfig("CMenuLang", lang);
 193
 194        std::size_t sz = sizeof(menuDescList) / sizeof(MenuDescription);
 195        for (std::size_t i = 0; i < sz; i++)
 196        {
 197            MenuDescription md = menuDescList[i];
 198
 199            if (md.name.size() == 0)
 200            {
 201                TDEBUG_TRACE("**** InitMenuMaps: ignoring entry with empty name");
 202                break;
 203            }
 204
 205            TDEBUG_TRACE("InitMenuMaps: adding " << md.name);
 206
 207            // Look for translation of menu and help text
 208            if (lang.size())
 209                GetCMenuTranslation(lang, md.name, md.menuText, md.helpText);
 210
 211            MenuDescMap[md.name] = md;
 212        }
 213    }
 214
 215    MenuIdMap.clear();
 216}
 217
 218
 219void InsertMenuItemWithIcon1(
 220    HMENU hMenu, UINT indexMenu, UINT idCmd,
 221    const std::wstring& menuText, const std::string& iconName)
 222{
 223    // MFT_STRING is obsolete and should not be used (replaced by MIIM_STRING
 224    // from Win2K onward)
 225    MENUITEMINFOW mi;
 226    memset(&mi, 0, sizeof(mi));
 227    mi.cbSize = sizeof(mi);
 228    mi.fMask = MIIM_ID | MIIM_STRING;
 229    mi.dwTypeData = const_cast<wchar_t*>(menuText.c_str());
 230    mi.cch = static_cast<UINT>(menuText.length());
 231    mi.wID = idCmd;
 232
 233    if (SysInfo::Instance().IsVistaOrLater())
 234    {
 235        HBITMAP hBmp = GetTortoiseIconBitmap(iconName);
 236        if (hBmp)
 237        {
 238            mi.fMask |= MIIM_BITMAP;
 239            mi.hbmpItem = hBmp;
 240        }
 241        else
 242        {
 243            TDEBUG_TRACE("    ***** InsertMenuItemWithIcon1: can't find " + iconName);
 244        }
 245    }
 246    else
 247    {
 248        HICON h = GetTortoiseIcon(iconName);
 249        if (h)
 250        {
 251            mi.fMask |= MIIM_BITMAP | MIIM_DATA;
 252            mi.dwItemData = (ULONG_PTR) h;
 253            mi.hbmpItem = HBMMENU_CALLBACK;
 254        }
 255        else
 256        {
 257            TDEBUG_TRACE("    ***** InsertMenuItemWithIcon1: can't find " + iconName);
 258        }
 259    }
 260    InsertMenuItemW(hMenu, indexMenu, TRUE, &mi);
 261
 262    TDEBUG_TRACEW(
 263        L"InsertMenuItemWithIcon1(\"" << menuText << L"\") finished");
 264}
 265
 266
 267void InsertSubMenuItemWithIcon2(
 268    HMENU hMenu, HMENU hSubMenu, UINT indexMenu, UINT idCmd,
 269    const std::wstring& menuText, const std::string& iconName)
 270{
 271    // MFT_STRING is obsolete and should not be used (replaced by MIIM_STRING
 272    // from Win2K onward)
 273    MENUITEMINFOW mi;
 274    memset(&mi, 0, sizeof(mi));
 275    mi.cbSize = sizeof(mi);
 276    mi.fMask = MIIM_SUBMENU | MIIM_ID | MIIM_STRING;
 277    mi.dwTypeData = const_cast<wchar_t*>(menuText.c_str());
 278    mi.cch = static_cast<UINT>(menuText.length());
 279    mi.wID = idCmd;
 280    mi.hSubMenu = hSubMenu;
 281
 282    if (SysInfo::Instance().IsVistaOrLater())
 283    {
 284        HBITMAP hBmp = GetTortoiseIconBitmap(iconName);
 285        if (hBmp)
 286        {
 287            mi.fMask |= MIIM_BITMAP;
 288            mi.hbmpItem = hBmp;
 289        }
 290        else
 291        {
 292            TDEBUG_TRACE("    ***** InsertSubMenuItemWithIcon2: can't find " + iconName);
 293        }
 294    }
 295    else
 296    {
 297        HICON h = GetTortoiseIcon(iconName);
 298        if (h)
 299        {
 300            mi.fMask |= MIIM_BITMAP | MIIM_DATA;
 301            mi.dwItemData = (ULONG_PTR) h;
 302            mi.hbmpItem = HBMMENU_CALLBACK;
 303        }
 304        else
 305        {
 306            TDEBUG_TRACE("    ***** InsertSubMenuItemWithIcon2: can't find " + iconName);
 307        }
 308    }
 309
 310    InsertMenuItemW(hMenu, indexMenu, TRUE, &mi);
 311
 312    TDEBUG_TRACEW(
 313        L"InsertMenuItemWithIcon2(\"" << menuText << L"\") finished");
 314}
 315
 316
 317void InsertMenuItemByName(
 318    HMENU hMenu, const std::string& name, UINT indexMenu,
 319    UINT idCmd, UINT idCmdFirst, const std::wstring& prefix)
 320{
 321    MenuDescriptionMap::iterator iter = MenuDescMap.find(name);
 322    if (iter == MenuDescMap.end())
 323    {
 324        TDEBUG_TRACE("***** InsertMenuItemByName: can't find menu info for " << name);
 325        return;
 326    }
 327
 328    MenuDescription md = iter->second;
 329    AddMenuList(idCmd - idCmdFirst, name);
 330    InsertMenuItemWithIcon1(
 331        hMenu, indexMenu, idCmd, prefix + md.menuText, md.iconName);
 332}
 333
 334
 335const std::wstring TortoiseHgMenuEntryString = L"TortoiseHg";
 336
 337int HasTortoiseMenu(HMENU hMenu, bool& hasmenu)
 338// returns -1 on error, 0 otherwise
 339{
 340    hasmenu = false;
 341
 342    const int count = ::GetMenuItemCount(hMenu);
 343    if (count == -1)
 344    {
 345        TDEBUG_TRACE("***** HasTortoiseMenu: GetMenuItemCount returned -1");
 346        return -1;
 347    }
 348
 349    MENUITEMINFOW mii;
 350    for (int i = 0; i < count; ++i)
 351    {
 352        memset(&mii, 0, sizeof(MENUITEMINFOW));
 353        mii.cbSize = sizeof(MENUITEMINFOW);
 354        
 355        // first GetMenuItemInfoW call: get size of menu item string
 356        mii.fMask = MIIM_STRING;
 357        BOOL res = ::GetMenuItemInfoW(hMenu, i, true, &mii);
 358        if (res == 0) {
 359            TDEBUG_TRACE("HasTortoiseMenu: "
 360                << "first GetMenuItemInfo returned 0");
 361            continue;
 362        }
 363
 364        if (mii.dwTypeData != MFT_STRING)
 365        {
 366            // not a string
 367            continue;
 368        }
 369
 370        // allocate buffer for the string
 371        std::vector<wchar_t> text(mii.cch + 1);
 372
 373        // second GetMenuItemInfoW call: get string into buffer
 374        mii.dwTypeData = &text[0];
 375        ++mii.cch; // size of buffer is one more than length of string
 376        res = ::GetMenuItemInfoW(hMenu, i, true, &mii);
 377        if (res == 0) {
 378            TDEBUG_TRACE("HasTortoiseMenu: "
 379                << "second GetMenuItemInfo returned 0");
 380            continue;
 381        }
 382
 383        const std::wstring menuitemtext(&text[0]);
 384        //TDEBUG_TRACEW(L"HasTortoiseMenu: "
 385        //    << L"menuitemtext is '" << menuitemtext << L"'");
 386
 387        if (menuitemtext == TortoiseHgMenuEntryString)
 388        {
 389            TDEBUG_TRACE("HasTortoiseMenu: FOUND TortoiseHg menu entry");
 390            hasmenu = true;
 391            return 0;
 392        }
 393    }
 394
 395    TDEBUG_TRACE("HasTortoiseMenu: TortoiseHg menu entry NOT found");
 396    return 0;
 397}
 398
 399#define ResultFromShort(i)  ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(i)))
 400
 401// IContextMenu
 402STDMETHODIMP
 403CShellExtCMenu::QueryContextMenu(
 404    HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
 405{
 406    TDEBUG_TRACE("CShellExtCMenu::QueryContextMenu");
 407
 408    UINT idCmd = idCmdFirst;
 409    BOOL bAppendItems = TRUE;
 410
 411    if ((uFlags & 0x000F) == CMF_NORMAL)
 412        bAppendItems = TRUE;
 413    else if (uFlags & CMF_VERBSONLY)
 414        bAppendItems = TRUE;
 415    else if (uFlags & CMF_EXPLORE)
 416        bAppendItems = TRUE;
 417    else
 418        bAppendItems = FALSE;
 419
 420    if (!bAppendItems)
 421        return S_OK;
 422
 423    bool hasthgmenu = false;
 424    if (HasTortoiseMenu(hMenu, hasthgmenu) == 0 && hasthgmenu)
 425    {
 426        TDEBUG_TRACE("CShellExtCMenu::QueryContextMenu: "
 427            << "TortoiseHg menu entry already in menu -> skipping");
 428        return S_OK;
 429    }
 430
 431    InitMenuMaps();
 432
 433    typedef std::vector<std::string> entriesT;
 434    typedef entriesT::const_iterator entriesIter;
 435
 436    std::string promoted_string = "commit,workbench"; // default value if key not found
 437    GetRegistryConfig("PromotedItems", promoted_string);
 438
 439    entriesT promoted;
 440    Tokenize(promoted_string, promoted, ",");
 441
 442    // Select menu to show
 443    bool fileMenu = myFiles.size() > 0;
 444    bool isHgrepo = false;
 445    std::string cwd;
 446    if (!myFolder.empty())
 447    {
 448        cwd = myFolder;
 449    }
 450    else if (!myFiles.empty())
 451    {
 452        cwd = IsDirectory(myFiles[0])? myFiles[0] : DirName(myFiles[0]);
 453    }
 454
 455    if (!cwd.empty())
 456    {
 457        // check if target directory is a Mercurial repository
 458        std::string root = GetHgRepoRoot(cwd);
 459        isHgrepo = !root.empty();
 460        if (myFiles.size() == 1 && root == myFiles[0])
 461        {
 462            fileMenu = false;
 463            myFolder = cwd;
 464            myFiles.clear();
 465        }
 466    }
 467
 468    if ((uFlags & CMF_EXTENDEDVERBS) == 0)
 469    {
 470        // shift key is not down
 471        if (!isHgrepo)
 472        {
 473            // we are not inside a repo
 474            std::string cval;
 475            if (GetRegistryConfig("HideMenuOutsideRepo", cval) != 0 && cval == "1")
 476            {
 477                return S_OK; // don't show thg cmenu entries
 478            }
 479        }
 480    }
 481
 482    TDEBUG_TRACE(
 483        "CShellExtCMenu::QueryContextMenu: isHgrepo = " 
 484        << isHgrepo << ", fileMenu = " << fileMenu
 485    );
 486
 487    /* We have three menu types: files-selected, no-files-selected, no-repo */
 488    const char* entries_string = 0;
 489    if (isHgrepo)
 490        if (fileMenu)
 491            entries_string = RepoFilesMenu;
 492        else
 493            entries_string = RepoNoFilesMenu;
 494    else
 495        entries_string = NoRepoMenu;
 496
 497    // start building TortoiseHg menus and submenus
 498    InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, NULL);
 499
 500    entriesT entries;
 501    Tokenize(entries_string, entries, " ");
 502
 503    for (entriesIter i = entries.begin(); i != entries.end(); i++)
 504    {
 505        std::string name = *i;
 506        if (contains(promoted, name))
 507        {
 508            InsertMenuItemByName(
 509                hMenu, name, indexMenu++,
 510                idCmd++, idCmdFirst, L"Hg "
 511            );
 512        }
 513    }
 514
 515    const HMENU hSubMenu = CreatePopupMenu();
 516    if (hSubMenu)
 517    {
 518        UINT indexSubMenu = 0;
 519        bool isSeparator = true;
 520        for (entriesIter i = entries.begin(); i != entries.end(); i++)
 521        {
 522            std::string name = *i;
 523            if (name == "sep")
 524            {
 525                if (!isSeparator)
 526                {
 527                    InsertMenu(
 528                        hSubMenu, indexSubMenu++,
 529                        MF_SEPARATOR | MF_BYPOSITION, 0, NULL
 530                    );
 531                    isSeparator = true;
 532                }
 533            }
 534            else
 535            {
 536                if (!contains(promoted, name))
 537                {
 538                    InsertMenuItemByName(
 539                        hSubMenu, name,
 540                        indexSubMenu++, idCmd++, idCmdFirst, L""
 541                    );
 542                    isSeparator = false;
 543                }
 544            }
 545        }
 546        if (isSeparator && indexSubMenu > 0)
 547            RemoveMenu(hSubMenu, indexSubMenu - 1, MF_BYPOSITION);
 548
 549        if (SysInfo::Instance().IsVistaOrLater())
 550        {
 551            MENUINFO MenuInfo;
 552            
 553            memset(&MenuInfo, 0, sizeof(MenuInfo));
 554
 555            MenuInfo.cbSize  = sizeof(MenuInfo);
 556            MenuInfo.fMask   = MIM_STYLE | MIM_APPLYTOSUBMENUS;
 557            MenuInfo.dwStyle = MNS_CHECKORBMP;
 558            
 559            SetMenuInfo(hSubMenu, &MenuInfo);
 560        }
 561    }
 562
 563    TDEBUG_TRACE("  CShellExtCMenu::QueryContextMenu: adding main THG menu");
 564    InsertSubMenuItemWithIcon2(hMenu, hSubMenu, indexMenu++, idCmd++,
 565            TortoiseHgMenuEntryString, "hg.ico");
 566
 567    InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, NULL);
 568
 569    InitStatus::check();
 570
 571    if (SysInfo::Instance().IsVistaOrLater())
 572    {
 573        MENUINFO MenuInfo;
 574
 575        memset(&MenuInfo, 0, sizeof(MenuInfo));
 576
 577        MenuInfo.cbSize  = sizeof(MenuInfo);
 578        MenuInfo.fMask   = MIM_STYLE | MIM_APPLYTOSUBMENUS;
 579        MenuInfo.dwStyle = MNS_CHECKORBMP;
 580
 581        SetMenuInfo(hMenu, &MenuInfo);
 582    }
 583
 584    return ResultFromShort(idCmd - idCmdFirst);
 585}
 586
 587
 588STDMETHODIMP
 589CShellExtCMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
 590{
 591    TDEBUG_TRACE("CShellExtCMenu::InvokeCommand");
 592
 593    HRESULT hr = E_INVALIDARG;
 594    if (!HIWORD(lpcmi->lpVerb))
 595    {
 596        UINT idCmd = LOWORD(lpcmi->lpVerb);
 597        TDEBUG_TRACE("CShellExtCMenu::InvokeCommand: idCmd = " << idCmd);
 598        MenuIdCmdMap::iterator iter = MenuIdMap.find(idCmd);
 599        if (iter != MenuIdMap.end())
 600        {
 601            RunDialog(iter->second.name);
 602            hr = S_OK;
 603        }
 604        else
 605        {
 606            TDEBUG_TRACE(
 607                "***** CShellExtCMenu::InvokeCommand: action not found for idCmd "
 608                << idCmd
 609            );
 610        }
 611    }
 612    return hr;
 613}
 614
 615
 616STDMETHODIMP
 617CShellExtCMenu::GetCommandString(
 618    UINT_PTR idCmd, UINT uFlags, UINT FAR *reserved,
 619    LPSTR pszName, UINT cchMax)
 620{
 621    // see http://msdn.microsoft.com/en-us/library/bb776094%28VS.85%29.aspx
 622
 623    HRESULT res = S_FALSE;
 624
 625    const char* psz = "";
 626    const wchar_t* pszw = 0;
 627
 628    std::string sflags = "?";
 629    switch (uFlags)
 630    {
 631    case GCS_HELPTEXTW:
 632        sflags = "GCS_HELPTEXTW"; break;
 633    case GCS_HELPTEXTA:
 634        sflags = "GCS_HELPTEXTA"; break;
 635    case GCS_VALIDATEW:
 636        sflags = "GCS_VALIDATEW"; break;
 637    case GCS_VALIDATEA:
 638        sflags = "GCS_VALIDATEA"; break;
 639    case GCS_VERBW:
 640        sflags = "GCS_VERBW"; break;
 641    case GCS_VERBA:
 642        sflags = "GCS_VERBA"; break;
 643    }
 644
 645    TDEBUG_TRACE(
 646        "CShellExtCMenu::GetCommandString: idCmd = " << idCmd 
 647        << ", uFlags = " << uFlags << " (" << sflags << ")"
 648        << ", cchMax = " << cchMax
 649    );
 650
 651    MenuIdCmdMap::iterator iter = MenuIdMap.find(static_cast<UINT>(idCmd));
 652    if (iter == MenuIdMap.end())
 653    {
 654        TDEBUG_TRACE("***** CShellExtCMenu::GetCommandString: idCmd not found");
 655    }
 656    else
 657    {
 658        TDEBUG_TRACE(
 659            "CShellExtCMenu::GetCommandString: name = \"" << iter->second.name << "\"");
 660
 661        if (uFlags == GCS_HELPTEXTW)
 662        {
 663            pszw = iter->second.helpText.c_str();
 664            res = S_OK;
 665            
 666            size_t size = iter->second.helpText.size();
 667            if (size >= 40)
 668            {
 669                TDEBUG_TRACE(
 670                    "***** CShellExtCMenu::GetCommandString: warning:" 
 671                    << " length of help text is " << size
 672                    << ", which is not reasonably short (<40)");
 673            }
 674        }
 675        else if (uFlags == GCS_HELPTEXTA)
 676        {
 677            // we don't provide ansi help texts
 678            psz = "";
 679            res = S_OK;
 680        }
 681        else if (uFlags == GCS_VERBW || uFlags == GCS_VERBA)
 682        {
 683#if 0
 684            psz = iter->second.name.c_str();
 685#else
 686            // bugfix: don't provide verbs ("rename" conflicted with rename of explorer)
 687            psz = "";
 688#endif
 689            res = S_OK;
 690        }
 691        else if (uFlags == GCS_VALIDATEW || uFlags == GCS_VALIDATEA)
 692        {
 693            res = S_OK;
 694        }
 695    }
 696
 697    if (cchMax < 1)
 698    {
 699        TDEBUG_TRACE("CShellExtCMenu::GetCommandString: cchMax = " 
 700            << cchMax << " (is <1)");
 701        return res;
 702    }
 703
 704    size_t size = 0;
 705
 706    if (uFlags & GCS_UNICODE)
 707    {
 708        wchar_t* const dest = reinterpret_cast<wchar_t*>(pszName);
 709        const wchar_t* const src = pszw ? pszw : _WCSTR(psz);
 710
 711        wcsncpy(dest, src, cchMax-1);
 712        *(dest + cchMax-1) = 0;
 713
 714        size = wcslen(src);
 715
 716        TDEBUG_TRACEW(L"CShellExtCMenu::GetCommandString: res = " << int(res)
 717            << L", pszName (wide) = \"" << dest << L"\"");
 718    }
 719    else
 720    {
 721        strncpy(pszName, psz, cchMax-1);
 722        *(pszName + cchMax-1) = 0;
 723
 724        size = strlen(psz);
 725
 726        TDEBUG_TRACE("CShellExtCMenu::GetCommandString: res = " << int(res)
 727            << ", pszName = \"" << psz << "\"");
 728    }
 729
 730    if (size > cchMax-1)
 731    {
 732        TDEBUG_TRACE(
 733            "***** CShellExtCMenu::GetCommandString: string was truncated: size = "
 734                << size << ", cchMax = " << cchMax);
 735    }
 736
 737    return res;
 738}
 739
 740
 741STDMETHODIMP
 742CShellExtCMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
 743{
 744    LRESULT res;
 745    return HandleMenuMsg2(uMsg, wParam, lParam, &res);
 746}
 747
 748
 749STDMETHODIMP
 750CShellExtCMenu::HandleMenuMsg2(
 751    UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
 752{
 753    // A great tutorial on owner drawn menus in shell extension can be found
 754    // here: http://www.codeproject.com/shell/shellextguide7.asp
 755
 756    LRESULT res;
 757    if (!pResult)
 758        pResult = &res;
 759    *pResult = FALSE;
 760
 761    switch (uMsg)
 762    {
 763    case WM_MEASUREITEM:
 764        {
 765            MEASUREITEMSTRUCT* lpmis = (MEASUREITEMSTRUCT*)lParam;
 766            if (lpmis==NULL)
 767                break;
 768            lpmis->itemWidth += 2;
 769            if(lpmis->itemHeight < 16)
 770                lpmis->itemHeight = 16;
 771            *pResult = TRUE;
 772        }
 773        break;
 774
 775    case WM_DRAWITEM:
 776        {
 777            DRAWITEMSTRUCT* lpdis = (DRAWITEMSTRUCT*)lParam;
 778            if (!lpdis || (lpdis->CtlType != ODT_MENU) || !lpdis->itemData)
 779                break; //not for a menu
 780            DrawIconEx(
 781                lpdis->hDC,
 782                lpdis->rcItem.left - 16,
 783                lpdis->rcItem.top
 784                    + (lpdis->rcItem.bottom - lpdis->rcItem.top - 16) / 2,
 785                (HICON) lpdis->itemData, 16, 16,
 786                0, 0, DI_NORMAL
 787            );
 788            *pResult = TRUE;
 789        }
 790        break;
 791
 792    default:
 793        return S_OK;
 794    }
 795
 796    return S_OK;
 797}
 798
 799
 800void CShellExtCMenu::RunDialog(const std::string &cmd)
 801{
 802    std::string dir = GetTHgProgRoot();
 803    if (dir.empty())
 804    {
 805        TDEBUG_TRACE("RunDialog: THG root is empty");
 806        return;
 807    }
 808    std::string hgcmd = dir + "\\thg.exe";
 809
 810    WIN32_FIND_DATAA data;
 811    HANDLE hfind = FindFirstFileA(hgcmd.c_str(), &data);
 812    if (hfind == INVALID_HANDLE_VALUE)
 813    {
 814        hgcmd = dir + "\\hgtk.exe";
 815        hfind = FindFirstFileA(hgcmd.c_str(), &data);
 816        if (hfind == INVALID_HANDLE_VALUE)
 817            hgcmd = dir + "\\thg.cmd";
 818        else
 819            FindClose(hfind);
 820    }
 821    else
 822        FindClose(hfind);
 823
 824    hgcmd = Quote(hgcmd) + " --nofork " + cmd;
 825
 826    std::string cwd;
 827    if (!myFolder.empty())
 828    {
 829        cwd = myFolder;
 830    }
 831    else if (!myFiles.empty())
 832    {
 833        cwd = IsDirectory(myFiles[0]) ? myFiles[0] : DirName(myFiles[0]);
 834    }
 835    else
 836    {
 837        TDEBUG_TRACE("***** RunDialog: can't get cwd");
 838        return;
 839    }
 840
 841
 842    if (!myFiles.empty())
 843    {
 844        const std::string tempfile = GetTemporaryFile();
 845        if (tempfile.empty())
 846        {
 847            TDEBUG_TRACE("***** RunDialog: error: GetTemporaryFile returned empty string");
 848            return;
 849        }
 850
 851        TDEBUG_TRACE("RunDialog: temp file = " << tempfile);
 852        HANDLE tempfileHandle = CreateFileA(
 853            tempfile.c_str(), GENERIC_WRITE,
 854            FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0
 855        );
 856
 857        if (tempfileHandle == INVALID_HANDLE_VALUE)
 858        {
 859            TDEBUG_TRACE("***** RunDialog: error: failed to create file " << tempfile);
 860            return;
 861        }
 862
 863        typedef std::vector<std::string>::size_type ST;
 864        for (ST i = 0; i < myFiles.size(); i++)
 865        {
 866            DWORD dwWritten;
 867            TDEBUG_TRACE("RunDialog: temp file adding " << myFiles[i]);
 868            WriteFile(
 869                tempfileHandle, myFiles[i].c_str(),
 870                static_cast<DWORD>(myFiles[i].size()), &dwWritten, 0
 871            );
 872            WriteFile(tempfileHandle, "\n", 1, &dwWritten, 0);
 873        }
 874        CloseHandle(tempfileHandle);
 875        hgcmd += " --listfile " + Quote(tempfile);
 876    }
 877
 878    if (cmd == "thgstatus")
 879    {
 880        if (Thgstatus::remove(cwd) != 0)
 881        {
 882            std::string p = dir + "\\TortoiseHgOverlayServer.exe";
 883            LaunchCommand(Quote(p), dir);
 884        }
 885        InitStatus::check();
 886        return;
 887    }
 888
 889    LaunchCommand(hgcmd, cwd);
 890    InitStatus::check();
 891}
 892
 893
 894STDMETHODIMP CShellExtCMenu::Initialize(
 895    LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObj, HKEY hRegKey)
 896{
 897    TCHAR name[MAX_PATH+1];
 898
 899    TDEBUG_TRACE("CShellExtCMenu::Initialize");
 900
 901    // get installed MSI product id (for debugging purposes for now)
 902#ifdef _M_X64
 903    const char* shellexid = "{D5D1E532-CDAD-4FFD-9695-757B8A29B4BA}";
 904#else
 905    const char* shellexid = "{728E8840-5878-4EA7-918F-281C2697ABB1}";
 906#endif
 907    std::vector<char> product_id(50, 0);
 908    UINT msires = ::MsiGetProductCodeA(shellexid, &product_id[0]);
 909    TDEBUG_TRACE("MSI shellexid: " << shellexid);
 910    TDEBUG_TRACE("MSI msires: " << msires);
 911    TDEBUG_TRACE("MSI installed product id: " << &product_id[0]);
 912
 913    DWORD busize = 300;
 914    std::vector<char> buf(busize, 0);
 915    msires = ::MsiGetProductInfoA(
 916        &product_id[0], INSTALLPROPERTY_INSTALLLOCATION, &buf[0], &busize);
 917    if (msires == ERROR_SUCCESS)
 918    {
 919        TDEBUG_TRACE("MSI install location: " << &buf[0]);
 920    }
 921    else
 922    {
 923        TDEBUG_TRACE("MSI install location: error " << msires);
 924    }
 925
 926    TDEBUG_TRACEW(
 927        L"---- TortoiseHg shell extension version " 
 928        << ThgVersion::get() << L"----"
 929    );
 930
 931    TDEBUG_TRACE("  pIDFolder: " << pIDFolder);
 932    TDEBUG_TRACE("  pDataObj: " << pDataObj);
 933
 934    myFolder.clear();
 935    myFiles.clear();
 936
 937    if (pDataObj)
 938    {
 939        FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
 940        STGMEDIUM stg = { TYMED_HGLOBAL };
 941        if (SUCCEEDED(pDataObj->GetData(&fmt, &stg)) && stg.hGlobal)
 942        {
 943            HDROP hDrop = (HDROP) GlobalLock(stg.hGlobal);
 944            
 945            if (hDrop)
 946            {
 947                UINT uNumFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
 948                TDEBUG_TRACE("  hDrop uNumFiles = " << uNumFiles);
 949                for (UINT i = 0; i < uNumFiles; ++i) {
 950                    if (DragQueryFile(hDrop, i, name, MAX_PATH) > 0)
 951                    {
 952                        TDEBUG_TRACE("  DragQueryFile [" << i << "] = " << name);
 953                        myFiles.push_back(name);
 954                    }   
 955                }
 956            }
 957            else 
 958            {
 959                TDEBUG_TRACE("  hDrop is NULL ");
 960            }
 961
 962            GlobalUnlock(stg.hGlobal);
 963            if (stg.pUnkForRelease)
 964            {
 965                IUnknown* relInterface = (IUnknown*) stg.pUnkForRelease;
 966                relInterface->Release();
 967            }
 968        }
 969        else
 970        {
 971            TDEBUG_TRACE("  pDataObj->GetData failed");
 972        }
 973    }
 974
 975    // if a directory background
 976    if (pIDFolder) 
 977    {
 978        SHGetPathFromIDList(pIDFolder, name);
 979        TDEBUG_TRACE("  Folder " << name);
 980        myFolder = name;
 981    }
 982
 983    // disable context menu if neither the folder nor the files
 984    // have been found
 985    if (myFolder.empty() && myFiles.empty()) {
 986        TDEBUG_TRACE("  shell extension not available on this object");
 987        return E_FAIL;
 988    } else {
 989        return S_OK;
 990    }
 991}
 992
 993
 994CShellExtCMenu::CShellExtCMenu(char dummy) :
 995    m_ppszFileUserClickedOn(0)
 996{
 997    m_cRef = 0L;
 998    CShellExt::IncDllRef();    
 999}
1000
1001
1002CShellExtCMenu::~CShellExtCMenu()
1003{
1004    CShellExt::DecDllRef();
1005}
1006
1007
1008STDMETHODIMP_(ULONG) CShellExtCMenu::AddRef()
1009{
1010    ThgCriticalSection cs(CShellExt::GetCriticalSection());
1011    return ++m_cRef;
1012}
1013
1014
1015STDMETHODIMP_(ULONG) CShellExtCMenu::Release()
1016{
1017    ThgCriticalSection cs(CShellExt::GetCriticalSection());
1018    if(--m_cRef)
1019        return m_cRef;
1020    delete this;
1021    return 0L;
1022}
1023
1024
1025STDMETHODIMP CShellExtCMenu::QueryInterface(REFIID riid, LPVOID FAR* ppv)
1026{    
1027    if (ppv == 0)
1028        return E_POINTER;
1029
1030    *ppv = NULL;
1031
1032    if (IsEqualIID(riid, IID_IShellExtInit) || IsEqualIID(riid, IID_IUnknown))
1033    {
1034        *ppv = (LPSHELLEXTINIT) this;
1035    }
1036    else if (IsEqualIID(riid, IID_IContextMenu))
1037    {
1038        *ppv = (LPCONTEXTMENU) this;
1039    }
1040    else if (IsEqualIID(riid, IID_IContextMenu2))
1041    {
1042        *ppv = (IContextMenu2*) this;
1043    }
1044    else if (IsEqualIID(riid, IID_IContextMenu3))
1045    {
1046        *ppv = (IContextMenu3*) this;
1047    }
1048    
1049    if (*ppv)
1050    {
1051        AddRef();
1052        return S_OK;
1053    }
1054
1055    return E_NOINTERFACE;
1056}