Mercurial and Git clients can push and pull from this alias URL to interact with this repository. You can change to which repository an alias points by going to the Aliases link on the project page.
stable
cmenu: improve single directory shell context menu behavior
This reverts us mostly back to 0.7 behavior. When you right click on a single folder, you get a 'files selected' context menu. Commands launched from this menu will be "rooted" in that directory: commit, log, etc will only consider files within that directory.
The repository root gets special treatment. Opening a context menu on a repository root folder icon opens the same menu you would get if you had opened that folder and spawned a menu from the background.
Apps launched from context menus opened from the folder background (no files selected), are still "rooted" at the repository root. This also does not resolve issue #55, where the user would like to be able to change the root after it has been launched.
#include "stdafx.h"#include "ShellExt.h"#include "TortoiseUtils.h"#include "StringUtils.h"#include "Dirstatecache.h"#include "Thgstatus.h"#include "Winstat.h"#include "InitStatus.h"#include <map>structMenuDescription{std::stringname;std::stringmenuText;std::stringhelpText;std::stringiconName;UINTidCmd;};// According to http://msdn.microsoft.com/en-us/library/bb776094%28VS.85%29.aspx// the help texts for the commands should be reasonably short (under 40 characters)MenuDescriptionmenuDescList[]={{"commit","Commit...","Commit changes in repository","menucommit.ico",0},{"init","Create Repository Here","Create a new repository","menucreaterepos.ico",0},{"clone","Clone a Repository","Create clone here from source","menuclone.ico",0},{"status","View File Status","Repository status & changes","menushowchanged.ico",0},{"shelve","Shelve Changes","Shelve or unshelve file changes","shelve.ico",0},{"add","Add Files","Add files to version control","menuadd.ico",0},{"revert","Revert Files","Revert file changes","menurevert.ico",0},{"remove","Remove Files","Remove files from version control","menudelete.ico",0},{"rename","Rename File","Rename file or directory","general.ico",0},{"log","View Changelog","View change history in repository","menulog.ico",0},{"synch","Synchronize","Synchronize with remote repository","menusynch.ico",0},{"serve","Web Server","Start web server for this repository","proxy.ico",0},{"update","Update To Revision","Update working directory","menucheckout.ico",0},{"recover","Recovery...","Repair and recovery of repository","general.ico",0},{"thgstatus","Update Icons","Update icons for this repository","refresh_overlays.ico",0},{"userconf","Global Settings","Configure user wide settings","settings_user.ico",0},{"repoconf","Repository Settings","Configure repository settings","settings_repo.ico",0},{"about","About...","Show About Dialog","menuabout.ico",0},{"datamine","Annotate Files","Changeset information per file line","menublame.ico",0},{"vdiff","Visual Diff","View changes using GUI diff tool","TortoiseMerge.ico",0},{"hgignore","Edit Ignore Filter","Edit repository ignore filter","ignore.ico",0},{"guess","Guess Renames","Detect renames and copies","detect_rename.ico",0},{"grep","Search History","Search file revisions for patterns","menurepobrowse.ico",0},/* Add new items here */// template{"","","",".ico",0},};/* These enumerations must match the order of menuDescList */enummenuDescListEntries{Commit,Init,Clone,Status,Shelve,Add,Revert,Remove,Rename,Log,Synch,Serve,Update,Recover,Thgstatus,Userconf,Repoconf,About,Datamine,VDiff,Ignore,Guess,Grep,/* Add new items here */Separator,EndOfList};menuDescListEntriesRepoNoFilesMenu[]={Commit,Status,Shelve,VDiff,Separator,Log,Update,Grep,Thgstatus,Separator,Synch,Serve,Clone,Init,Separator,Ignore,Guess,Recover,Separator,Repoconf,Userconf,Separator,About,EndOfList};menuDescListEntriesRepoFilesMenu[]={Commit,Status,VDiff,Add,Revert,Rename,Remove,Separator,Log,Datamine,Separator,About,EndOfList};menuDescListEntriesNoRepoMenu[]={Clone,Init,Userconf,Thgstatus,Separator,About,EndOfList};typedefstd::map<std::string,MenuDescription>MenuDescriptionMap;typedefstd::map<UINT,MenuDescription>MenuIdCmdMap;MenuDescriptionMapMenuDescMap;MenuIdCmdMapMenuIdMap;voidAddMenuList(UINTidCmd,conststd::string&name){TDEBUG_TRACE("AddMenuList: idCmd = "<<idCmd<<" name = "<<name);MenuIdMap[idCmd]=MenuDescMap[name];}voidGetCMenuTranslation(conststd::string&lang,conststd::string&name,std::string&menuText,std::string&helpText){std::stringsubkey="Software\\TortoiseHg\\CMenu\\";subkey+=lang;subkey+="\\";subkey+=name;TDEBUG_TRACE("GetCMenuTranslation: "<<subkey);HKEYhkey=0;LONGrv=RegOpenKeyExA(HKEY_CURRENT_USER,subkey.c_str(),0,KEY_READ,&hkey);if(rv==ERROR_SUCCESS&&hkey){GetRegSZValue(hkey,"menuText",menuText);GetRegSZValue(hkey,"helpText",helpText);}else{TDEBUG_TRACE("GetCMenuTranslation: RegOpenKeyExA("<<subkey<<") failed");}if(hkey)RegCloseKey(hkey);}voidInitMenuMaps(){if(MenuDescMap.empty()){std::stringlang;GetRegistryConfig("CMenuLang",lang);std::size_tsz=sizeof(menuDescList)/sizeof(MenuDescription);for(std::size_ti=0;i<sz;i++){MenuDescriptionmd=menuDescList[i];TDEBUG_TRACE("InitMenuMaps: adding "<<md.name);// Look for translation of menu and help textif(lang.size())GetCMenuTranslation(lang,md.name,md.menuText,md.helpText);MenuDescMap[md.name]=md;}}MenuIdMap.clear();}voidInsertMenuItemWithIcon(HMENUhMenu,UINTindexMenu,UINTidCmd,conststd::string&menuText,conststd::string&iconName){MENUITEMINFOmi;mi.cbSize=sizeof(mi);mi.dwTypeData=const_cast<char*>(menuText.c_str());mi.cch=static_cast<UINT>(menuText.length());mi.wID=idCmd;mi.fType=MFT_STRING;HICONh=GetTortoiseIcon(iconName);if(h){mi.fMask=MIIM_STRING|MIIM_FTYPE|MIIM_ID|MIIM_BITMAP|MIIM_DATA;mi.dwItemData=(ULONG_PTR)h;mi.hbmpItem=HBMMENU_CALLBACK;}else{TDEBUG_TRACE(" InsertMenuItemWithIcon: can't find "+iconName);mi.fMask=MIIM_TYPE|MIIM_ID;}InsertMenuItem(hMenu,indexMenu,TRUE,&mi);}voidInsertSubMenuItemWithIcon(HMENUhMenu,HMENUhSubMenu,UINTindexMenu,UINTidCmd,conststd::string&menuText,conststd::string&iconName){MENUITEMINFOmi;mi.cbSize=sizeof(mi);mi.fMask=MIIM_SUBMENU|MIIM_STRING|MIIM_ID;mi.fType=MFT_STRING;mi.dwTypeData=const_cast<char*>(menuText.c_str());mi.cch=static_cast<UINT>(menuText.length());mi.wID=idCmd;mi.hSubMenu=hSubMenu;HICONh=GetTortoiseIcon(iconName);if(h){mi.fMask=MIIM_FTYPE|MIIM_STRING|MIIM_SUBMENU|MIIM_ID|MIIM_BITMAP|MIIM_DATA;mi.dwItemData=(ULONG_PTR)h;mi.hbmpItem=HBMMENU_CALLBACK;}else{TDEBUG_TRACE(" InsertSubMenuItemWithIcon: can't find "+iconName);}InsertMenuItem(hMenu,indexMenu,TRUE,&mi);}voidInsertMenuItemByName(HMENUhMenu,conststd::string&name,UINTindexMenu,UINTidCmd,UINTidCmdFirst,conststd::string&prefix){MenuDescriptionMap::iteratoriter=MenuDescMap.find(name);if(iter==MenuDescMap.end()){TDEBUG_TRACE("InsertMenuItemByName: can't find menu info for "<<name);return;}MenuDescriptionmd=iter->second;AddMenuList(idCmd-idCmdFirst,name);InsertMenuItemWithIcon(hMenu,indexMenu,idCmd,prefix+md.menuText,md.iconName);}#define ResultFromShort(i) ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(i)))// IContextMenuSTDMETHODIMPCShellExt::QueryContextMenu(HMENUhMenu,UINTindexMenu,UINTidCmdFirst,UINTidCmdLast,UINTuFlags){TDEBUG_TRACE("CShellExt::QueryContextMenu");InitMenuMaps();UINTidCmd=idCmdFirst;BOOLbAppendItems=TRUE;if((uFlags&0x000F)==CMF_NORMAL)bAppendItems=TRUE;elseif(uFlags&CMF_VERBSONLY)bAppendItems=TRUE;elseif(uFlags&CMF_EXPLORE)bAppendItems=TRUE;elsebAppendItems=FALSE;if(!bAppendItems)returnNOERROR;conststd::size_tsz=sizeof(menuDescList)/sizeof(MenuDescription);boolpromoted[sz];memset(&promoted,0,sizeof(promoted));std::stringcval="commit";// default value if key not foundGetRegistryConfig("PromotedItems",cval);size_tfound;do{if(cval.empty())break;found=cval.find_first_of(',');std::stringkey;if(found==std::string::npos)key=cval;else{key=cval.substr(0,found);cval=cval.substr(found+1);}for(UINTi=0;i<sz;i++){if(!key.compare(menuDescList[i].name)){promoted[i]=true;break;}} }
while (found != std::string::npos);
- // checkiftargetdirectoryisaMercurialrepository+ // Selectmenutoshow+bool fileMenu = myFiles.size() > 0;+ bool isHgrepo = false; std::string cwd;
if (!myFolder.empty())
{
cwd = myFolder;
}
- else if (myFiles.size() == 1 && IsDirectory(myFiles[0]))- {- myFolder = myFiles[0];- cwd = myFolder;- myFiles.clear();- } else if (!myFiles.empty())
{
cwd = IsDirectory(myFiles[0])? myFiles[0] : DirName(myFiles[0]);
}
- bool isHgrepo = false;+ if (!cwd.empty())
- isHgrepo = IsHgRepo(cwd);
+ {+ // check if target directory is a Mercurial repository+ std::string root = GetHgRepoRoot(cwd);+ isHgrepo = !root.empty();
+ if (cwd == root && myFiles.size() == 1)+ {+ fileMenu = false;+ myFolder = cwd;+ myFiles.clear();+ }+ } /* We have three menu types: files-selected, no-files-selected, no-repo */
menuDescListEntries *entries;
if (isHgrepo)
- if (myFiles.empty())
+ if (fileMenu)
+ entries = RepoFilesMenu;+ else entries = RepoNoFilesMenu;
- else- entries = RepoFilesMenu; else
entries = NoRepoMenu;
// start building TortoiseHg menus and submenusInsertMenu(hMenu,indexMenu++,MF_SEPARATOR|MF_BYPOSITION,0,NULL);menuDescListEntries*walk;for(walk=entries;*walk!=EndOfList;walk++){UINTidx=(UINT)*walk;if(promoted[idx]){InsertMenuItemByName(hMenu,menuDescList[idx].name,indexMenu++,idCmd++,idCmdFirst,"HG ");}}constHMENUhSubMenu=CreatePopupMenu();if(hSubMenu){UINTindexSubMenu=0;boolisSeparator=true;for(walk=entries;*walk!=EndOfList;walk++){if(*walk==Separator){if(!isSeparator){InsertMenu(hSubMenu,indexSubMenu++,MF_SEPARATOR|MF_BYPOSITION,0,NULL);isSeparator=true;}}else{UINTidx=(UINT)*walk;if(!promoted[idx]){InsertMenuItemByName(hSubMenu,menuDescList[idx].name,indexSubMenu++,idCmd++,idCmdFirst,"");isSeparator=false;}}}if(isSeparator&&indexSubMenu>0)RemoveMenu(hSubMenu,indexSubMenu-1,MF_BYPOSITION);}TDEBUG_TRACE(" CShellExt::QueryContextMenu: adding main THG menu");InsertSubMenuItemWithIcon(hMenu,hSubMenu,indexMenu++,idCmd++,"TortoiseHG...","hg.ico");InsertMenu(hMenu,indexMenu++,MF_SEPARATOR|MF_BYPOSITION,0,NULL);InitStatus::check();returnResultFromShort(idCmd-idCmdFirst);}STDMETHODIMPCShellExt::InvokeCommand(LPCMINVOKECOMMANDINFOlpcmi){TDEBUG_TRACE("CShellExt::InvokeCommand");HRESULThr=E_INVALIDARG;if(!HIWORD(lpcmi->lpVerb)){UINTidCmd=LOWORD(lpcmi->lpVerb);TDEBUG_TRACE("CShellExt::InvokeCommand: idCmd = "<<idCmd);MenuIdCmdMap::iteratoriter=MenuIdMap.find(idCmd);if(iter!=MenuIdMap.end()){DoHgtk(iter->second.name);hr=NOERROR;}else{TDEBUG_TRACE("CShellExt::InvokeCommand: action not found for idCmd "<<idCmd);}}returnhr;}STDMETHODIMPCShellExt::GetCommandString(UINT_PTRidCmd,UINTuFlags,UINTFAR*reserved,LPSTRpszName,UINTcchMax){// see http://msdn.microsoft.com/en-us/library/bb776094%28VS.85%29.aspxHRESULTres=S_FALSE;constchar*psz="";std::stringsflags="?";switch(uFlags){caseGCS_HELPTEXTW:
sflags="GCS_HELPTEXTW";break;caseGCS_HELPTEXTA:
sflags="GCS_HELPTEXTA";break;caseGCS_VALIDATEW:
sflags="GCS_VALIDATEW";break;caseGCS_VALIDATEA:
sflags="GCS_VALIDATEA";break;caseGCS_VERBW:
sflags="GCS_VERBW";break;caseGCS_VERBA:
sflags="GCS_VERBA";break;}TDEBUG_TRACE("CShellExt::GetCommandString: idCmd = "<<idCmd<<", uFlags = "<<uFlags<<" ("<<sflags<<")"<<", cchMax = "<<cchMax);MenuIdCmdMap::iteratoriter=MenuIdMap.find(static_cast<UINT>(idCmd));if(iter==MenuIdMap.end()){TDEBUG_TRACE("CShellExt::GetCommandString: idCmd not found");}else{TDEBUG_TRACE("CShellExt::GetCommandString: name = \""<<iter->second.name<<"\"");if(uFlags==GCS_HELPTEXTW||uFlags==GCS_HELPTEXTA){psz=iter->second.helpText.c_str();res=S_OK;size_tsize=iter->second.helpText.size();if(size>=40){TDEBUG_TRACE("CShellExt::GetCommandString: warning:"<<" length of help text is "<<size<<", which is not reasonably short (<40)");}}elseif(uFlags==GCS_VERBW||uFlags==GCS_VERBA){#if 0 psz = iter->second.name.c_str();#else// bugfix: don't provide verbs ("rename" conflicted with rename of explorer)psz="";#endifres=S_OK;}elseif(uFlags==GCS_VALIDATEW||uFlags==GCS_VALIDATEA){res=S_OK;}}if(cchMax<1){TDEBUG_TRACE("CShellExt::GetCommandString: cchMax = "<<cchMax<<" (is <1)");returnres;}size_tsize=0;if(uFlags&GCS_UNICODE){wchar_t*constdest=reinterpret_cast<wchar_t*>(pszName);constwchar_t*constsrc=_WCSTR(psz);wcsncpy(dest,src,cchMax-1);*(dest+cchMax-1)=0;size=wcslen(src);}else{strncpy(pszName,psz,cchMax-1);*(pszName+cchMax-1)=0;size=strlen(psz);}TDEBUG_TRACE("CShellExt::GetCommandString: res = "<<res<<", pszName = \""<<psz<<"\"");if(size>cchMax-1){TDEBUG_TRACE("CShellExt::GetCommandString: string was truncated: size = "<<size<<", cchMax = "<<cchMax);}returnres;}STDMETHODIMPCShellExt::HandleMenuMsg(UINTuMsg,WPARAMwParam,LPARAMlParam){LRESULTres;returnHandleMenuMsg2(uMsg,wParam,lParam,&res);}STDMETHODIMPCShellExt::HandleMenuMsg2(UINTuMsg,WPARAMwParam,LPARAMlParam,LRESULT*pResult){// A great tutorial on owner drawn menus in shell extension can be found// here: http://www.codeproject.com/shell/shellextguide7.aspLRESULTres;if(!pResult)pResult=&res;*pResult=FALSE;switch(uMsg){caseWM_MEASUREITEM:
{MEASUREITEMSTRUCT*lpmis=(MEASUREITEMSTRUCT*)lParam;if(lpmis==NULL)break;lpmis->itemWidth+=2;if(lpmis->itemHeight<16)lpmis->itemHeight=16;*pResult=TRUE;}break;caseWM_DRAWITEM:
{DRAWITEMSTRUCT*lpdis=(DRAWITEMSTRUCT*)lParam;if(!lpdis||(lpdis->CtlType!=ODT_MENU)||!lpdis->itemData)break;//not for a menuDrawIconEx(lpdis->hDC,lpdis->rcItem.left-16,lpdis->rcItem.top+(lpdis->rcItem.bottom-lpdis->rcItem.top-16)/2,(HICON)lpdis->itemData,16,16,0,0,DI_NORMAL);*pResult=TRUE;}break;default:returnNOERROR;}returnNOERROR;}voidCShellExt::DoHgtk(conststd::string&cmd){std::stringdir=GetTHgProgRoot();if(dir.empty()){TDEBUG_TRACE("DoHgtk: THG root is empty");return;}std::stringhgcmd=dir+"\\hgtk.exe";WIN32_FIND_DATAAdata;HANDLEhfind=FindFirstFileA(hgcmd.c_str(),&data);if(hfind==INVALID_HANDLE_VALUE)hgcmd=dir+"\\hgtk.cmd";elseFindClose(hfind);hgcmd=Quote(hgcmd)+" --nofork "+cmd;std::stringcwd;if(!myFolder.empty()) {
cwd = myFolder;
}
- else if (myFiles.size() == 1 && IsDirectory(myFiles[0]))- {- // Treat single selected directory as if cmenu were opened- // within that directory without files selected- cwd = myFiles[0];- } else if (!myFiles.empty())
{
- cwd = IsDirectory(myFiles[0])? myFiles[0] : DirName(myFiles[0]);
+ cwd = IsDirectory(myFiles[0])? myFiles[0] : DirName(myFiles[0]);
+ }+ else+ {+ TDEBUG_TRACE("DoHgtk: can't get cwd");+ return;+ }++ if (!myFiles.empty())+ { const std::string tempfile = GetTemporaryFile();
if (tempfile.empty())
{
TDEBUG_TRACE("DoHgtk: error: GetTemporaryFile returned empty string");return;}TDEBUG_TRACE("DoHgtk: temp file = "<<tempfile);HANDLEtempfileHandle=CreateFileA(tempfile.c_str(),GENERIC_WRITE,FILE_SHARE_READ,0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0);if(tempfileHandle==INVALID_HANDLE_VALUE){TDEBUG_TRACE("DoHgtk: error: failed to create file "<<tempfile);return;}typedefstd::vector<std::string>::size_typeST;for(STi=0;i<myFiles.size();i++){DWORDdwWritten;TDEBUG_TRACE("DoHgtk: temp file adding "<<myFiles[i]);WriteFile(tempfileHandle,myFiles[i].c_str(),static_cast<DWORD>(myFiles[i].size()),&dwWritten,0);WriteFile(tempfileHandle,"\n",1,&dwWritten,0);} CloseHandle(tempfileHandle);
hgcmd += " --listfile " + Quote(tempfile);
}
- else- {- TDEBUG_TRACE("DoHgtk: can't get cwd");- return;- } if (cmd == "thgstatus")
{
Thgstatus::remove(cwd);InitStatus::check();return;}LaunchCommand(hgcmd,cwd);InitStatus::check();}
Attach a Trello Card
Add a tag
Your session has expired
You are no longer logged in. Please log in and try your request again.
Filter RSS Feed
This RSS feed URL allows you to see the contents of your current filter using any feed reader.
This link includes a special authentication token. If you share the URL with anyone else, they can see this RSS feed's activity. You can disable these tokens when needed.
Your current filter is unsaved; changing it won't affect this RSS feed.