Refactored: Replace CGitLogListBase::m_From/m_To with the member variable of CFilterData
[TortoiseGit.git] / src / TortoiseShell / ContextMenu.cpp
blobe42b16344fcdd2f4f477a87e60d0840faa108cb5
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012, 2014-2015 - TortoiseSVN
4 // Copyright (C) 2008-2015 - TortoiseGit
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "ShellExt.h"
22 #include "ItemIDList.h"
23 #include "PreserveChdir.h"
24 #include "UnicodeUtils.h"
25 #include "GitStatus.h"
26 #include "TGitPath.h"
27 #include "PathUtils.h"
28 #include "CreateProcessHelper.h"
29 #include "FormatMessageWrapper.h"
30 #include "..\TGitCache\CacheInterface.h"
31 #include "resource.h"
33 #define GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
34 #define GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
36 int g_shellidlist=RegisterClipboardFormat(CFSTR_SHELLIDLIST);
38 extern MenuInfo menuInfo[];
39 static int g_syncSeq = 0;
41 STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder,
42 LPDATAOBJECT pDataObj,
43 HKEY hRegKey)
45 __try
47 return Initialize_Wrap(pIDFolder, pDataObj, hRegKey);
49 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
52 return E_FAIL;
55 STDMETHODIMP CShellExt::Initialize_Wrap(LPCITEMIDLIST pIDFolder,
56 LPDATAOBJECT pDataObj,
57 HKEY /* hRegKey */)
59 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Shell :: Initialize\n");
60 PreserveChdir preserveChdir;
61 files_.clear();
62 folder_.clear();
63 uuidSource.clear();
64 uuidTarget.clear();
65 itemStates = 0;
66 itemStatesFolder = 0;
67 stdstring statuspath;
68 git_wc_status_kind fetchedstatus = git_wc_status_none;
69 // get selected files/folders
70 if (pDataObj)
72 STGMEDIUM medium;
73 FORMATETC fmte = {(CLIPFORMAT)g_shellidlist,
74 (DVTARGETDEVICE FAR *)NULL,
75 DVASPECT_CONTENT,
76 -1,
77 TYMED_HGLOBAL};
78 HRESULT hres = pDataObj->GetData(&fmte, &medium);
80 if (SUCCEEDED(hres) && medium.hGlobal)
82 if (m_State == FileStateDropHandler)
85 FORMATETC etc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
86 STGMEDIUM stg = { TYMED_HGLOBAL };
87 if ( FAILED( pDataObj->GetData ( &etc, &stg )))
89 ReleaseStgMedium ( &medium );
90 return E_INVALIDARG;
94 HDROP drop = (HDROP)GlobalLock(stg.hGlobal);
95 if ( NULL == drop )
97 ReleaseStgMedium ( &stg );
98 ReleaseStgMedium ( &medium );
99 return E_INVALIDARG;
102 int count = DragQueryFile(drop, (UINT)-1, NULL, 0);
103 if (count == 1)
104 itemStates |= ITEMIS_ONLYONE;
105 for (int i = 0; i < count; i++)
107 // find the path length in chars
108 UINT len = DragQueryFile(drop, i, NULL, 0);
109 if (len == 0)
110 continue;
111 std::unique_ptr<TCHAR[]> szFileName(new TCHAR[len + 1]);
112 if (0 == DragQueryFile(drop, i, szFileName.get(), len + 1))
113 continue;
114 stdstring str = stdstring(szFileName.get());
115 if ((!str.empty()) && (g_ShellCache.IsContextPathAllowed(szFileName.get())))
117 if (itemStates & ITEMIS_ONLYONE)
119 CTGitPath strpath;
120 strpath.SetFromWin(str.c_str());
121 itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE : 0;
122 itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE : 0;
124 files_.push_back(str);
125 if (i == 0)
127 //get the git status of the item
128 git_wc_status_kind status = git_wc_status_none;
129 CTGitPath askedpath;
130 askedpath.SetFromWin(str.c_str());
131 CString workTreePath;
132 askedpath.HasAdminDir(&workTreePath);
133 uuidSource = workTreePath;
136 if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(str.c_str()))
138 CTGitPath tpath(str.c_str());
139 if (!tpath.HasAdminDir())
141 status = git_wc_status_none;
142 continue;
144 if (tpath.IsAdminDir())
146 status = git_wc_status_none;
147 continue;
149 TGITCacheResponse itemStatus;
150 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
151 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
153 fetchedstatus = status = GitStatus::GetMoreImportant(itemStatus.m_status.text_status, itemStatus.m_status.prop_status);
154 if (askedpath.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
156 itemStates |= ITEMIS_FOLDER;
157 if ((status != git_wc_status_unversioned) && (status != git_wc_status_ignored) && (status != git_wc_status_none))
158 itemStates |= ITEMIS_FOLDERINGIT;
162 else
164 GitStatus stat;
165 stat.GetStatus(CTGitPath(str.c_str()), false, false, true);
166 if (stat.status)
168 statuspath = str;
169 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
170 fetchedstatus = status;
171 if ( askedpath.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
173 itemStates |= ITEMIS_FOLDER;
174 if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
175 itemStates |= ITEMIS_FOLDERINGIT;
177 //if ((stat.status->entry)&&(stat.status->entry->uuid))
178 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
180 else
182 // sometimes, git_client_status() returns with an error.
183 // in that case, we have to check if the working copy is versioned
184 // anyway to show the 'correct' context menu
185 if (askedpath.HasAdminDir())
186 status = git_wc_status_normal;
190 catch ( ... )
192 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
195 // TODO: should we really assume any sub-directory to be versioned
196 // or only if it contains versioned files
197 itemStates |= askedpath.GetAdminDirMask();
199 if ((status == git_wc_status_unversioned) || (status == git_wc_status_ignored) || (status == git_wc_status_none))
200 itemStates &= ~ITEMIS_INGIT;
202 if (status == git_wc_status_ignored)
203 itemStates |= ITEMIS_IGNORED;
204 if (status == git_wc_status_normal)
205 itemStates |= ITEMIS_NORMAL;
206 if (status == git_wc_status_conflicted)
207 itemStates |= ITEMIS_CONFLICTED;
208 if (status == git_wc_status_added)
209 itemStates |= ITEMIS_ADDED;
210 if (status == git_wc_status_deleted)
211 itemStates |= ITEMIS_DELETED;
214 } // for (int i = 0; i < count; i++)
215 GlobalUnlock ( drop );
216 ReleaseStgMedium ( &stg );
218 } // if (m_State == FileStateDropHandler)
219 else
222 //Enumerate PIDLs which the user has selected
223 CIDA* cida = (CIDA*)GlobalLock(medium.hGlobal);
224 ItemIDList parent( GetPIDLFolder (cida));
226 int count = cida->cidl;
227 BOOL statfetched = FALSE;
228 for (int i = 0; i < count; ++i)
230 ItemIDList child (GetPIDLItem (cida, i), &parent);
231 stdstring str = child.toString();
232 if ((str.empty() == false)&&(g_ShellCache.IsContextPathAllowed(str.c_str())))
234 //check if our menu is requested for a git admin directory
235 if (GitAdminDir::IsAdminDirPath(str.c_str()))
236 continue;
238 files_.push_back(str);
239 CTGitPath strpath;
240 strpath.SetFromWin(str.c_str());
241 itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE : 0;
242 itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE : 0;
243 if (!statfetched)
245 //get the git status of the item
246 git_wc_status_kind status = git_wc_status_none;
247 if ((g_ShellCache.IsSimpleContext())&&(strpath.IsDirectory()))
249 if (strpath.HasAdminDir())
250 status = git_wc_status_normal;
252 else
256 if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(str.c_str()))
258 CTGitPath tpath(str.c_str());
259 if(!tpath.HasAdminDir())
261 status = git_wc_status_none;
262 continue;
264 if(tpath.IsAdminDir())
266 status = git_wc_status_none;
267 continue;
269 TGITCacheResponse itemStatus;
270 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
271 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
273 fetchedstatus = status = GitStatus::GetMoreImportant(itemStatus.m_status.text_status, itemStatus.m_status.prop_status);
274 if (strpath.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
276 itemStates |= ITEMIS_FOLDER;
277 if ((status != git_wc_status_unversioned) && (status != git_wc_status_ignored) && (status != git_wc_status_none))
278 itemStates |= ITEMIS_FOLDERINGIT;
280 if (status == git_wc_status_conflicted)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
281 itemStates |= ITEMIS_CONFLICTED;
284 else
286 GitStatus stat;
287 if (strpath.HasAdminDir())
288 stat.GetStatus(strpath, false, false, true);
289 statuspath = str;
290 if (stat.status)
292 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
293 fetchedstatus = status;
294 if ( strpath.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
296 itemStates |= ITEMIS_FOLDER;
297 if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
298 itemStates |= ITEMIS_FOLDERINGIT;
300 // TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
301 if (status == git_wc_status_conflicted)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
302 itemStates |= ITEMIS_CONFLICTED;
303 //if ((stat.status->entry)&&(stat.status->entry->uuid))
304 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
306 else
308 // sometimes, git_client_status() returns with an error.
309 // in that case, we have to check if the working copy is versioned
310 // anyway to show the 'correct' context menu
311 if (strpath.HasAdminDir())
313 status = git_wc_status_normal;
314 fetchedstatus = status;
318 statfetched = TRUE;
320 catch ( ... )
322 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
326 itemStates |= strpath.GetAdminDirMask();
328 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
329 itemStates &= ~ITEMIS_INGIT;
330 if (status == git_wc_status_ignored)
332 itemStates |= ITEMIS_IGNORED;
335 if (status == git_wc_status_normal)
336 itemStates |= ITEMIS_NORMAL;
337 if (status == git_wc_status_conflicted)
338 itemStates |= ITEMIS_CONFLICTED;
339 if (status == git_wc_status_added)
340 itemStates |= ITEMIS_ADDED;
341 if (status == git_wc_status_deleted)
342 itemStates |= ITEMIS_DELETED;
345 } // for (int i = 0; i < count; ++i)
346 ItemIDList child (GetPIDLItem (cida, 0), &parent);
347 if (g_ShellCache.HasGITAdminDir(child.toString().c_str(), FALSE))
348 itemStates |= ITEMIS_INVERSIONEDFOLDER;
350 if (GitAdminDir::IsBareRepo(child.toString().c_str()))
351 itemStates = ITEMIS_BAREREPO;
353 GlobalUnlock(medium.hGlobal);
355 // if the item is a versioned folder, check if there's a patch file
356 // in the clipboard to be used in "Apply Patch"
357 UINT cFormatDiff = RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
358 if (cFormatDiff)
360 if (IsClipboardFormatAvailable(cFormatDiff))
361 itemStates |= ITEMIS_PATCHINCLIPBOARD;
363 if (IsClipboardFormatAvailable(CF_HDROP))
364 itemStates |= ITEMIS_PATHINCLIPBOARD;
368 ReleaseStgMedium ( &medium );
369 if (medium.pUnkForRelease)
371 IUnknown* relInterface = (IUnknown*)medium.pUnkForRelease;
372 relInterface->Release();
377 // get folder background
378 if (pIDFolder)
381 ItemIDList list(pIDFolder);
382 folder_ = list.toString();
383 git_wc_status_kind status = git_wc_status_none;
384 if (IsClipboardFormatAvailable(CF_HDROP))
385 itemStatesFolder |= ITEMIS_PATHINCLIPBOARD;
387 CTGitPath askedpath;
388 askedpath.SetFromWin(folder_.c_str());
390 if (g_ShellCache.IsContextPathAllowed(folder_.c_str()))
392 if (folder_.compare(statuspath)!=0)
394 CString worktreePath;
395 askedpath.HasAdminDir(&worktreePath);
396 uuidTarget = worktreePath;
399 if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(folder_.c_str()))
401 CTGitPath tpath(folder_.c_str());
402 if(!tpath.HasAdminDir())
403 status = git_wc_status_none;
404 else if(tpath.IsAdminDir())
405 status = git_wc_status_none;
406 else
408 TGITCacheResponse itemStatus;
409 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
410 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
411 status = GitStatus::GetMoreImportant(itemStatus.m_status.text_status, itemStatus.m_status.prop_status);
414 else
416 GitStatus stat;
417 stat.GetStatus(CTGitPath(folder_.c_str()), false, false, true);
418 if (stat.status)
420 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
421 // if ((stat.status->entry)&&(stat.status->entry->uuid))
422 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
425 else
427 // sometimes, git_client_status() returns with an error.
428 // in that case, we have to check if the working copy is versioned
429 // anyway to show the 'correct' context menu
430 if (askedpath.HasAdminDir())
431 status = git_wc_status_normal;
435 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
436 itemStatesFolder |= askedpath.GetAdminDirMask();
438 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
439 itemStates &= ~ITEMIS_INGIT;
441 if (status == git_wc_status_normal)
442 itemStatesFolder |= ITEMIS_NORMAL;
443 if (status == git_wc_status_conflicted)
444 itemStatesFolder |= ITEMIS_CONFLICTED;
445 if (status == git_wc_status_added)
446 itemStatesFolder |= ITEMIS_ADDED;
447 if (status == git_wc_status_deleted)
448 itemStatesFolder |= ITEMIS_DELETED;
451 catch ( ... )
453 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
456 else
458 status = fetchedstatus;
460 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
461 itemStatesFolder |= askedpath.GetAdminDirMask();
463 if (status == git_wc_status_ignored)
464 itemStatesFolder |= ITEMIS_IGNORED;
465 itemStatesFolder |= ITEMIS_FOLDER;
466 if (files_.empty())
467 itemStates |= ITEMIS_ONLYONE;
468 if (m_State != FileStateDropHandler)
469 itemStates |= itemStatesFolder;
471 else
473 folder_.clear();
474 status = fetchedstatus;
477 if (files_.size() == 2)
478 itemStates |= ITEMIS_TWO;
479 if ((files_.size() == 1)&&(g_ShellCache.IsContextPathAllowed(files_.front().c_str())))
482 itemStates |= ITEMIS_ONLYONE;
483 if (m_State != FileStateDropHandler)
485 if (PathIsDirectory(files_.front().c_str()))
487 folder_ = files_.front();
488 git_wc_status_kind status = git_wc_status_none;
489 CTGitPath askedpath;
490 askedpath.SetFromWin(folder_.c_str());
492 if (folder_.compare(statuspath)!=0)
496 GitStatus stat;
497 stat.GetStatus(CTGitPath(folder_.c_str()), false, false, true);
498 if (stat.status)
500 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
501 // if ((stat.status->entry)&&(stat.status->entry->uuid))
502 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
505 catch ( ... )
507 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
510 else
512 status = fetchedstatus;
514 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
515 itemStates |= askedpath.GetAdminDirMask();
517 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
518 itemStates &= ~ITEMIS_INGIT;
520 if (status == git_wc_status_ignored)
521 itemStates |= ITEMIS_IGNORED;
522 itemStates |= ITEMIS_FOLDER;
523 if (status == git_wc_status_added)
524 itemStates |= ITEMIS_ADDED;
525 if (status == git_wc_status_deleted)
526 itemStates |= ITEMIS_DELETED;
533 return S_OK;
536 void CShellExt::InsertGitMenu(BOOL istop, HMENU menu, UINT pos, UINT_PTR id, UINT stringid, UINT icon, UINT idCmdFirst, GitCommands com, UINT /*uFlags*/)
538 TCHAR menutextbuffer[512] = {0};
539 TCHAR verbsbuffer[255] = {0};
540 MAKESTRING(stringid);
542 if (istop)
544 //menu entry for the top context menu, so append an "Git " before
545 //the menu text to indicate where the entry comes from
546 _tcscpy_s(menutextbuffer, 255, _T("Git "));
547 if (!g_ShellCache.HasShellMenuAccelerators())
549 // remove the accelerators
550 tstring temp = stringtablebuffer;
551 temp.erase(std::remove(temp.begin(), temp.end(), '&'), temp.end());
552 _tcscpy_s(stringtablebuffer, 255, temp.c_str());
555 _tcscat_s(menutextbuffer, 255, stringtablebuffer);
557 // insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
558 // so we have an easy and fast way to check the current branch
559 // (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
560 if (com == ShellMenuCommit)
562 // get branch name
563 CTGitPath path(folder_.empty() ? files_.front().c_str() : folder_.c_str());
564 CString sProjectRoot;
565 CString sBranchName;
567 if (path.GetAdminDirMask() & ITEMIS_SUBMODULE)
569 if (istop)
570 _tcscpy_s(menutextbuffer, 255, _T("Git "));
571 else
572 menutextbuffer[0] = '\0';
573 MAKESTRING(IDS_MENUCOMMITSUBMODULE);
574 _tcscat_s(menutextbuffer, 255, stringtablebuffer);
577 if (path.HasAdminDir(&sProjectRoot) && !CGit::GetCurrentBranchFromFile(sProjectRoot, sBranchName))
579 if (sBranchName.GetLength() == 40)
581 // if SHA1 only show 4 first bytes
582 BOOL bIsSha1 = TRUE;
583 for (int i=0; i<40; i++)
584 if ( !iswxdigit(sBranchName[i]) )
586 bIsSha1 = FALSE;
587 break;
589 if (bIsSha1)
590 sBranchName = sBranchName.Left(8) + _T("....");
593 // sanity check
594 if (sBranchName.GetLength() > 64)
595 sBranchName = sBranchName.Left(64) + _T("...");
597 // scan to before "..."
598 LPTSTR s = menutextbuffer + _tcslen(menutextbuffer)-1;
599 if (s > menutextbuffer)
601 while (s > menutextbuffer)
603 if (*s != _T('.'))
605 s++;
606 break;
608 s--;
611 else
613 s = menutextbuffer;
616 // append branch name and end with ...
617 _tcscpy_s(s, 255 - _tcslen(menutextbuffer) - 1, _T(" -> \"") + sBranchName + _T("\"..."));
621 if (com == ShellMenuDiffLater)
623 std::wstring sPath = regDiffLater;
624 if (!sPath.empty())
626 // add the path of the saved file
627 wchar_t compact[40] = {0};
628 PathCompactPathEx(compact, sPath.c_str(), _countof(compact) - 1, 0);
629 MAKESTRING(IDS_MENUDIFFNOW);
630 CString sMenu;
631 sMenu.Format(CString(stringtablebuffer), compact);
632 wcscpy_s(menutextbuffer, sMenu);
636 MENUITEMINFO menuiteminfo;
637 SecureZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
638 menuiteminfo.cbSize = sizeof(menuiteminfo);
639 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING;
640 menuiteminfo.fType = MFT_STRING;
641 menuiteminfo.dwTypeData = menutextbuffer;
642 if (icon)
644 menuiteminfo.fMask |= MIIM_BITMAP;
645 menuiteminfo.hbmpItem = SysInfo::Instance().IsVistaOrLater() ? m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, icon) : HBMMENU_CALLBACK;
647 menuiteminfo.wID = (UINT)id;
648 InsertMenuItem(menu, pos, TRUE, &menuiteminfo);
650 if (istop)
652 //menu entry for the top context menu, so append an "Git " before
653 //the menu text to indicate where the entry comes from
654 _tcscpy_s(menutextbuffer, 255, _T("Git "));
656 LoadString(g_hResInst, stringid, verbsbuffer, _countof(verbsbuffer));
657 _tcscat_s(menutextbuffer, 255, verbsbuffer);
658 stdstring verb = stdstring(menutextbuffer);
659 if (verb.find('&') != -1)
661 verb.erase(verb.find('&'),1);
663 myVerbsMap[verb] = id - idCmdFirst;
664 myVerbsMap[verb] = id;
665 myVerbsIDMap[id - idCmdFirst] = verb;
666 myVerbsIDMap[id] = verb;
667 // We store the relative and absolute diameter
668 // (drawitem callback uses absolute, others relative)
669 myIDMap[id - idCmdFirst] = com;
670 myIDMap[id] = com;
671 if (!istop)
672 mySubMenuMap[pos] = com;
675 bool CShellExt::WriteClipboardPathsToTempFile(stdstring& tempfile)
677 bool bRet = true;
678 tempfile = stdstring();
679 //write all selected files and paths to a temporary file
680 //for TortoiseGitProc.exe to read out again.
681 DWORD written = 0;
682 DWORD pathlength = GetTortoiseGitTempPath(0, NULL);
683 std::unique_ptr<TCHAR[]> path(new TCHAR[pathlength + 1]);
684 std::unique_ptr<TCHAR[]> tempFile(new TCHAR[pathlength + 100]);
685 GetTortoiseGitTempPath(pathlength+1, path.get());
686 GetTempFileName(path.get(), _T("git"), 0, tempFile.get());
687 tempfile = stdstring(tempFile.get());
689 CAutoFile file = ::CreateFile(tempFile.get(),
690 GENERIC_WRITE,
691 FILE_SHARE_READ,
693 CREATE_ALWAYS,
694 FILE_ATTRIBUTE_TEMPORARY,
697 if (!file)
698 return false;
700 if (!IsClipboardFormatAvailable(CF_HDROP))
701 return false;
702 if (!OpenClipboard(NULL))
703 return false;
705 stdstring sClipboardText;
706 HGLOBAL hglb = GetClipboardData(CF_HDROP);
707 HDROP hDrop = (HDROP)GlobalLock(hglb);
708 if(hDrop != NULL)
710 TCHAR szFileName[MAX_PATH] = {0};
711 UINT cFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
712 for(UINT i = 0; i < cFiles; ++i)
714 DragQueryFile(hDrop, i, szFileName, _countof(szFileName));
715 stdstring filename = szFileName;
716 ::WriteFile (file, filename.c_str(), (DWORD)filename.size()*sizeof(TCHAR), &written, 0);
717 ::WriteFile (file, _T("\n"), 2, &written, 0);
719 GlobalUnlock(hDrop);
721 else bRet = false;
722 GlobalUnlock(hglb);
724 CloseClipboard();
726 return bRet;
729 stdstring CShellExt::WriteFileListToTempFile()
731 //write all selected files and paths to a temporary file
732 //for TortoiseGitProc.exe to read out again.
733 DWORD pathlength = GetTortoiseGitTempPath(0, NULL);
734 std::unique_ptr<TCHAR[]> path(new TCHAR[pathlength + 1]);
735 std::unique_ptr<TCHAR[]> tempFile(new TCHAR[pathlength + 100]);
736 GetTortoiseGitTempPath(pathlength + 1, path.get());
737 GetTempFileName(path.get(), _T("git"), 0, tempFile.get());
738 stdstring retFilePath = stdstring(tempFile.get());
740 CAutoFile file = ::CreateFile (tempFile.get(),
741 GENERIC_WRITE,
742 FILE_SHARE_READ,
744 CREATE_ALWAYS,
745 FILE_ATTRIBUTE_TEMPORARY,
748 if (!file)
749 return stdstring();
751 DWORD written = 0;
752 if (files_.empty())
754 ::WriteFile (file, folder_.c_str(), (DWORD)folder_.size()*sizeof(TCHAR), &written, 0);
755 ::WriteFile (file, _T("\n"), 2, &written, 0);
758 for (std::vector<stdstring>::iterator I = files_.begin(); I != files_.end(); ++I)
760 ::WriteFile (file, I->c_str(), (DWORD)I->size()*sizeof(TCHAR), &written, 0);
761 ::WriteFile (file, _T("\n"), 2, &written, 0);
763 return retFilePath;
766 STDMETHODIMP CShellExt::QueryDropContext(UINT uFlags, UINT idCmdFirst, HMENU hMenu, UINT &indexMenu)
768 if (!CRegStdDWORD(L"Software\\TortoiseGit\\EnableDragContextMenu", TRUE))
769 return S_OK;
771 PreserveChdir preserveChdir;
772 LoadLangDll();
774 if ((uFlags & CMF_DEFAULTONLY)!=0)
775 return S_OK; //we don't change the default action
777 if (files_.empty() || folder_.empty())
778 return S_OK;
780 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
781 return S_OK;
783 bool bSourceAndTargetFromSameRepository = (uuidSource.compare(uuidTarget) == 0) || uuidSource.empty() || uuidTarget.empty();
785 //the drop handler only has eight commands, but not all are visible at the same time:
786 //if the source file(s) are under version control then those files can be moved
787 //to the new location or they can be moved with a rename,
788 //if they are unversioned then they can be added to the working copy
789 //if they are versioned, they also can be exported to an unversioned location
790 UINT idCmd = idCmdFirst;
792 bool moveAvailable = false;
793 // Git move here
794 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
795 if ((bSourceAndTargetFromSameRepository || (itemStatesFolder & ITEMIS_ADDED)) && (itemStatesFolder & ITEMIS_FOLDERINGIT) && ((itemStates & (ITEMIS_NORMAL | ITEMIS_INGIT | ITEMIS_FOLDERINGIT)) && ((~itemStates) & ITEMIS_ADDED)))
797 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVEMENU, 0, idCmdFirst, ShellMenuDropMove, uFlags);
798 moveAvailable = true;
801 // Git move and rename here
802 // available if source is a single, versioned but not added item, target is versioned, source and target from same repository or target folder is added
803 if ((bSourceAndTargetFromSameRepository || (itemStatesFolder & ITEMIS_ADDED)) && (itemStatesFolder & ITEMIS_FOLDERINGIT) && (itemStates & (ITEMIS_NORMAL | ITEMIS_INGIT | ITEMIS_FOLDERINGIT)) && (itemStates & ITEMIS_ONLYONE) && ((~itemStates) & ITEMIS_ADDED))
805 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVERENAMEMENU, 0, idCmdFirst, ShellMenuDropMoveRename, uFlags);
806 moveAvailable = true;
809 // Git copy here
810 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
811 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&((~itemStates) & ITEMIS_ADDED))
812 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYMENU, 0, idCmdFirst, ShellMenuDropCopy, uFlags);
814 // Git copy and rename here, source and target from same repository
815 // available if source is a single, versioned but not added item, target is versioned or target folder is added
816 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
817 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYRENAMEMENU, 0, idCmdFirst, ShellMenuDropCopyRename, uFlags);
819 // Git add here
820 // available if target is versioned and source is either unversioned or from another repository
821 if ((itemStatesFolder & ITEMIS_FOLDERINGIT) && (((~itemStates) & ITEMIS_INGIT) || !bSourceAndTargetFromSameRepository) && !moveAvailable)
822 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYADDMENU, 0, idCmdFirst, ShellMenuDropCopyAdd, uFlags);
824 // Git export here
825 // available if source is versioned and a folder
826 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
827 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
829 // Git export all here
830 // available if source is versioned and a folder
831 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
832 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
834 // apply patch
835 // available if source is a patchfile
836 if (itemStates & ITEMIS_PATCHFILE)
837 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_MENUAPPLYPATCH, 0, idCmdFirst, ShellMenuApplyPatch, uFlags);
839 // separator
840 if (idCmd != idCmdFirst)
841 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
843 TweakMenu(hMenu);
845 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
848 STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu,
849 UINT indexMenu,
850 UINT idCmdFirst,
851 UINT idCmdLast,
852 UINT uFlags)
854 __try
856 return QueryContextMenu_Wrap(hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
858 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
861 return E_FAIL;
864 STDMETHODIMP CShellExt::QueryContextMenu_Wrap(HMENU hMenu,
865 UINT indexMenu,
866 UINT idCmdFirst,
867 UINT /*idCmdLast*/,
868 UINT uFlags)
870 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Shell :: QueryContextMenu itemStates=%ld\n", itemStates);
871 PreserveChdir preserveChdir;
873 //first check if our drop handler is called
874 //and then (if true) provide the context menu for the
875 //drop handler
876 if (m_State == FileStateDropHandler)
878 return QueryDropContext(uFlags, idCmdFirst, hMenu, indexMenu);
881 if ((uFlags & CMF_DEFAULTONLY)!=0)
882 return S_OK; //we don't change the default action
884 if (files_.empty() && folder_.empty())
885 return S_OK;
887 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
888 return S_OK;
890 int csidlarray[] =
892 CSIDL_BITBUCKET,
893 CSIDL_CDBURN_AREA,
894 CSIDL_COMMON_FAVORITES,
895 CSIDL_COMMON_STARTMENU,
896 CSIDL_COMPUTERSNEARME,
897 CSIDL_CONNECTIONS,
898 CSIDL_CONTROLS,
899 CSIDL_COOKIES,
900 CSIDL_FAVORITES,
901 CSIDL_FONTS,
902 CSIDL_HISTORY,
903 CSIDL_INTERNET,
904 CSIDL_INTERNET_CACHE,
905 CSIDL_NETHOOD,
906 CSIDL_NETWORK,
907 CSIDL_PRINTERS,
908 CSIDL_PRINTHOOD,
909 CSIDL_RECENT,
910 CSIDL_SENDTO,
911 CSIDL_STARTMENU,
914 if (IsIllegalFolder(folder_, csidlarray))
915 return S_OK;
917 if (folder_.empty())
919 // folder is empty, but maybe files are selected
920 if (files_.empty())
921 return S_OK; // nothing selected - we don't have a menu to show
922 // check whether a selected entry is an UID - those are namespace extensions
923 // which we can't handle
924 for (std::vector<stdstring>::const_iterator it = files_.begin(); it != files_.end(); ++it)
926 if (_tcsncmp(it->c_str(), _T("::{"), 3)==0)
927 return S_OK;
930 else
932 if (_tcsncmp(folder_.c_str(), _T("::{"), 3) == 0)
933 return S_OK;
936 if (((uFlags & CMF_EXTENDEDVERBS) == 0) && g_ShellCache.HideMenusForUnversionedItems())
938 if ((itemStates & (ITEMIS_INGIT|ITEMIS_INVERSIONEDFOLDER|ITEMIS_FOLDERINGIT|ITEMIS_BAREREPO|ITEMIS_TWO))==0)
939 return S_OK;
942 //check if our menu is requested for a git admin directory
943 if (GitAdminDir::IsAdminDirPath(folder_.c_str()))
944 return S_OK;
946 if (uFlags & CMF_EXTENDEDVERBS)
947 itemStates |= ITEMIS_EXTENDED;
949 regDiffLater.read();
950 if (!std::wstring(regDiffLater).empty())
951 itemStates |= ITEMIS_HASDIFFLATER;
953 const BOOL bShortcut = !!(uFlags & CMF_VERBSONLY);
954 if ( bShortcut && (files_.size()==1))
956 // Don't show the context menu for a link if the
957 // destination is not part of a working copy.
958 // It would only show the standard menu items
959 // which are already shown for the lnk-file.
960 CString path = files_.front().c_str();
961 if (!GitAdminDir::HasAdminDir(path))
963 return S_OK;
967 //check if we already added our menu entry for a folder.
968 //we check that by iterating through all menu entries and check if
969 //the dwItemData member points to our global ID string. That string is set
970 //by our shell extension when the folder menu is inserted.
971 TCHAR menubuf[MAX_PATH] = {0};
972 int count = GetMenuItemCount(hMenu);
973 for (int i=0; i<count; ++i)
975 MENUITEMINFO miif;
976 SecureZeroMemory(&miif, sizeof(MENUITEMINFO));
977 miif.cbSize = sizeof(MENUITEMINFO);
978 miif.fMask = MIIM_DATA;
979 miif.dwTypeData = menubuf;
980 miif.cch = _countof(menubuf);
981 GetMenuItemInfo(hMenu, i, TRUE, &miif);
982 if (miif.dwItemData == (ULONG_PTR)g_MenuIDString)
983 return S_OK;
986 LoadLangDll();
987 UINT idCmd = idCmdFirst;
989 //create the sub menu
990 HMENU subMenu = CreateMenu();
991 int indexSubMenu = 0;
993 unsigned __int64 topmenu = g_ShellCache.GetMenuLayout();
994 unsigned __int64 menumask = g_ShellCache.GetMenuMask();
995 unsigned __int64 menuex = g_ShellCache.GetMenuExt();
997 int menuIndex = 0;
998 bool bAddSeparator = false;
999 bool bMenuEntryAdded = false;
1000 bool bMenuEmpty = true;
1001 // insert separator at start
1002 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL); idCmd++;
1003 bool bShowIcons = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\ShowContextMenuIcons"), TRUE));
1005 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1007 if (menuInfo[menuIndex].command == ShellSeparator)
1009 // we don't add a separator immediately. Because there might not be
1010 // another 'normal' menu entry after we insert a separator.
1011 // we simply set a flag here, indicating that before the next
1012 // 'normal' menu entry, a separator should be added.
1013 if (!bMenuEmpty)
1014 bAddSeparator = true;
1015 if (bMenuEntryAdded)
1016 bAddSeparator = true;
1018 else
1020 // check the conditions whether to show the menu entry or not
1021 bool bInsertMenu = ShouldInsertItem(menuInfo[menuIndex]);
1022 if (menuInfo[menuIndex].menuID & menuex)
1024 if( !(itemStates & ITEMIS_EXTENDED) )
1026 bInsertMenu = false;
1030 if (menuInfo[menuIndex].menuID & (~menumask))
1032 if (bInsertMenu)
1034 bool bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1035 // insert a separator
1036 if ((bMenuEntryAdded)&&(bAddSeparator)&&(!bIsTop))
1038 bAddSeparator = false;
1039 bMenuEntryAdded = false;
1040 InsertMenu(subMenu, indexSubMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
1041 idCmd++;
1044 // handle special cases (sub menus)
1045 if ((menuInfo[menuIndex].command == ShellMenuIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuUnIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuDeleteIgnoreSub))
1047 if(InsertIgnoreSubmenus(idCmd, idCmdFirst, hMenu, subMenu, indexMenu, indexSubMenu, topmenu, bShowIcons, uFlags))
1048 bMenuEntryAdded = true;
1050 else
1052 bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1054 // insert the menu entry
1055 InsertGitMenu( bIsTop,
1056 bIsTop ? hMenu : subMenu,
1057 bIsTop ? indexMenu++ : indexSubMenu++,
1058 idCmd++,
1059 menuInfo[menuIndex].menuTextID,
1060 bShowIcons ? menuInfo[menuIndex].iconID : 0,
1061 idCmdFirst,
1062 menuInfo[menuIndex].command,
1063 uFlags);
1064 if (!bIsTop)
1066 bMenuEntryAdded = true;
1067 bMenuEmpty = false;
1068 bAddSeparator = false;
1074 menuIndex++;
1077 // do not show TortoiseGit menu if it's empty
1078 if (bMenuEmpty)
1080 if (idCmd - idCmdFirst > 0)
1082 //separator after
1083 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL); idCmd++;
1085 TweakMenu(hMenu);
1087 //return number of menu items added
1088 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
1091 //add sub menu to main context menu
1092 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1093 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1094 MAKESTRING(IDS_MENUSUBMENU);
1095 if (!g_ShellCache.HasShellMenuAccelerators())
1097 // remove the accelerators
1098 tstring temp = stringtablebuffer;
1099 temp.erase(std::remove(temp.begin(), temp.end(), '&'), temp.end());
1100 _tcscpy_s(stringtablebuffer, temp.c_str());
1102 MENUITEMINFO menuiteminfo;
1103 SecureZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
1104 menuiteminfo.cbSize = sizeof(menuiteminfo);
1105 menuiteminfo.fType = MFT_STRING;
1106 menuiteminfo.dwTypeData = stringtablebuffer;
1108 UINT uIcon = bShowIcons ? IDI_APP : 0;
1109 if (!folder_.empty())
1111 uIcon = bShowIcons ? IDI_MENUFOLDER : 0;
1112 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFolder;
1113 myIDMap[idCmd] = ShellSubMenuFolder;
1114 menuiteminfo.dwItemData = (ULONG_PTR)g_MenuIDString;
1116 else if (!bShortcut && (files_.size()==1))
1118 uIcon = bShowIcons ? IDI_MENUFILE : 0;
1119 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFile;
1120 myIDMap[idCmd] = ShellSubMenuFile;
1122 else if (bShortcut && (files_.size()==1))
1124 uIcon = bShowIcons ? IDI_MENULINK : 0;
1125 myIDMap[idCmd - idCmdFirst] = ShellSubMenuLink;
1126 myIDMap[idCmd] = ShellSubMenuLink;
1128 else if (!files_.empty())
1130 uIcon = bShowIcons ? IDI_MENUMULTIPLE : 0;
1131 myIDMap[idCmd - idCmdFirst] = ShellSubMenuMultiple;
1132 myIDMap[idCmd] = ShellSubMenuMultiple;
1134 else
1136 myIDMap[idCmd - idCmdFirst] = ShellSubMenu;
1137 myIDMap[idCmd] = ShellSubMenu;
1139 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
1140 if (uIcon)
1142 menuiteminfo.fMask |= MIIM_BITMAP;
1143 menuiteminfo.hbmpItem = SysInfo::Instance().IsVistaOrLater() ? m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, uIcon) : HBMMENU_CALLBACK;
1145 menuiteminfo.hSubMenu = subMenu;
1146 menuiteminfo.wID = idCmd++;
1147 InsertMenuItem(hMenu, indexMenu++, TRUE, &menuiteminfo);
1149 //separator after
1150 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL); idCmd++;
1152 TweakMenu(hMenu);
1154 //return number of menu items added
1155 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
1158 void CShellExt::TweakMenu(HMENU hMenu)
1160 MENUINFO MenuInfo = {};
1161 MenuInfo.cbSize = sizeof(MenuInfo);
1162 MenuInfo.fMask = MIM_STYLE | MIM_APPLYTOSUBMENUS;
1163 MenuInfo.dwStyle = MNS_CHECKORBMP;
1164 SetMenuInfo(hMenu, &MenuInfo);
1167 void CShellExt::AddPathCommand(tstring& gitCmd, LPCTSTR command, bool bFilesAllowed)
1169 gitCmd += command;
1170 gitCmd += _T(" /path:\"");
1171 if ((bFilesAllowed) && !files_.empty())
1172 gitCmd += files_.front();
1173 else
1174 gitCmd += folder_;
1175 gitCmd += _T("\"");
1178 void CShellExt::AddPathFileCommand(tstring& gitCmd, LPCTSTR command)
1180 tstring tempfile = WriteFileListToTempFile();
1181 gitCmd += command;
1182 gitCmd += _T(" /pathfile:\"");
1183 gitCmd += tempfile;
1184 gitCmd += _T("\"");
1185 gitCmd += _T(" /deletepathfile");
1188 void CShellExt::AddPathFileDropCommand(tstring& gitCmd, LPCTSTR command)
1190 tstring tempfile = WriteFileListToTempFile();
1191 gitCmd += command;
1192 gitCmd += _T(" /pathfile:\"");
1193 gitCmd += tempfile;
1194 gitCmd += _T("\"");
1195 gitCmd += _T(" /deletepathfile");
1196 gitCmd += _T(" /droptarget:\"");
1197 gitCmd += folder_;
1198 gitCmd += _T("\"");
1201 STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
1203 __try
1205 return InvokeCommand_Wrap(lpcmi);
1207 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1210 return E_FAIL;
1213 // This is called when you invoke a command on the menu:
1214 STDMETHODIMP CShellExt::InvokeCommand_Wrap(LPCMINVOKECOMMANDINFO lpcmi)
1216 PreserveChdir preserveChdir;
1217 HRESULT hr = E_INVALIDARG;
1218 if (lpcmi == NULL)
1219 return hr;
1221 if (!files_.empty() || !folder_.empty())
1223 UINT_PTR idCmd = LOWORD(lpcmi->lpVerb);
1225 if (HIWORD(lpcmi->lpVerb))
1227 stdstring verb = stdstring(MultibyteToWide(lpcmi->lpVerb));
1228 std::map<stdstring, UINT_PTR>::const_iterator verb_it = myVerbsMap.lower_bound(verb);
1229 if (verb_it != myVerbsMap.end() && verb_it->first == verb)
1230 idCmd = verb_it->second;
1231 else
1232 return hr;
1235 // See if we have a handler interface for this id
1236 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
1237 if (id_it != myIDMap.end() && id_it->first == idCmd)
1239 tstring tortoiseProcPath = CPathUtils::GetAppDirectory(g_hmodThisDll) + _T("TortoiseGitProc.exe");
1240 tstring tortoiseMergePath = CPathUtils::GetAppDirectory(g_hmodThisDll) + _T("TortoiseGitMerge.exe");
1242 //TortoiseGitProc expects a command line of the form:
1243 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1244 // or
1245 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1247 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1248 //* pathfile is a path to a temporary file which contains a list of file paths
1249 stdstring gitCmd = _T(" /command:");
1250 stdstring tempfile;
1251 switch (id_it->second)
1253 //#region case
1254 case ShellMenuSync:
1256 TCHAR syncSeq[12] = { 0 };
1257 _stprintf_s(syncSeq, _T("%d"), g_syncSeq++);
1258 AddPathCommand(gitCmd, L"sync", false);
1259 gitCmd += _T(" /seq:");
1260 gitCmd += syncSeq;
1262 break;
1263 case ShellMenuSubSync:
1264 AddPathFileCommand(gitCmd, L"subsync");
1265 if (itemStatesFolder & ITEMIS_SUBMODULECONTAINER || (itemStates & ITEMIS_SUBMODULECONTAINER && itemStates & ITEMIS_WCROOT && itemStates & ITEMIS_ONLYONE))
1267 gitCmd += _T(" /bkpath:\"");
1268 gitCmd += folder_;
1269 gitCmd += _T("\"");
1271 break;
1272 case ShellMenuUpdateExt:
1273 AddPathFileCommand(gitCmd, L"subupdate");
1274 if (itemStatesFolder & ITEMIS_SUBMODULECONTAINER || (itemStates & ITEMIS_SUBMODULECONTAINER && itemStates & ITEMIS_WCROOT && itemStates & ITEMIS_ONLYONE))
1276 gitCmd += _T(" /bkpath:\"");
1277 gitCmd += folder_;
1278 gitCmd += _T("\"");
1280 break;
1281 case ShellMenuCommit:
1282 AddPathFileCommand(gitCmd, L"commit");
1283 break;
1284 case ShellMenuAdd:
1285 AddPathFileCommand(gitCmd, L"add");
1286 break;
1287 case ShellMenuIgnore:
1288 AddPathFileCommand(gitCmd, L"ignore");
1289 break;
1290 case ShellMenuIgnoreCaseSensitive:
1291 AddPathFileCommand(gitCmd, L"ignore");
1292 gitCmd += _T(" /onlymask");
1293 break;
1294 case ShellMenuDeleteIgnore:
1295 AddPathFileCommand(gitCmd, L"ignore");
1296 gitCmd += _T(" /delete");
1297 break;
1298 case ShellMenuDeleteIgnoreCaseSensitive:
1299 AddPathFileCommand(gitCmd, L"ignore");
1300 gitCmd += _T(" /delete /onlymask");
1301 break;
1302 case ShellMenuUnIgnore:
1303 AddPathFileCommand(gitCmd, L"unignore");
1304 break;
1305 case ShellMenuUnIgnoreCaseSensitive:
1306 AddPathFileCommand(gitCmd, L"unignore");
1307 gitCmd += _T(" /onlymask");
1308 break;
1309 case ShellMenuMergeAbort:
1310 AddPathCommand(gitCmd, L"merge", false);
1311 gitCmd += _T(" /abort");
1312 break;
1313 case ShellMenuRevert:
1314 AddPathFileCommand(gitCmd, L"revert");
1315 break;
1316 case ShellMenuCleanup:
1317 AddPathFileCommand(gitCmd, L"cleanup");
1318 break;
1319 case ShellMenuSendMail:
1320 AddPathFileCommand(gitCmd, L"sendmail");
1321 break;
1322 case ShellMenuResolve:
1323 AddPathFileCommand(gitCmd, L"resolve");
1324 break;
1325 case ShellMenuSwitch:
1326 AddPathCommand(gitCmd, L"switch", false);
1327 break;
1328 case ShellMenuExport:
1329 AddPathCommand(gitCmd, L"export", false);
1330 break;
1331 case ShellMenuAbout:
1332 gitCmd += _T("about");
1333 break;
1334 case ShellMenuCreateRepos:
1335 AddPathCommand(gitCmd, L"repocreate", false);
1336 break;
1337 case ShellMenuMerge:
1338 AddPathCommand(gitCmd, L"merge", false);
1339 break;
1340 case ShellMenuCopy:
1341 AddPathCommand(gitCmd, L"copy", true);
1342 break;
1343 case ShellMenuSettings:
1344 AddPathCommand(gitCmd, L"settings", true);
1345 break;
1346 case ShellMenuHelp:
1347 gitCmd += _T("help");
1348 break;
1349 case ShellMenuRename:
1350 AddPathCommand(gitCmd, L"rename", true);
1351 break;
1352 case ShellMenuRemove:
1353 AddPathFileCommand(gitCmd, L"remove");
1354 break;
1355 case ShellMenuRemoveKeep:
1356 AddPathFileCommand(gitCmd, L"remove");
1357 gitCmd += _T(" /keep");
1358 break;
1359 case ShellMenuDiff:
1360 gitCmd += _T("diff /path:\"");
1361 if (files_.size() == 1)
1362 gitCmd += files_.front();
1363 else if (files_.size() == 2)
1365 std::vector<stdstring>::iterator I = files_.begin();
1366 gitCmd += *I;
1367 ++I;
1368 gitCmd += _T("\" /path2:\"");
1369 gitCmd += *I;
1371 else
1372 gitCmd += folder_;
1373 gitCmd += _T("\"");
1374 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1375 gitCmd += _T(" /alternative");
1376 break;
1377 case ShellMenuDiffLater:
1378 if (lpcmi->fMask & CMIC_MASK_CONTROL_DOWN)
1380 gitCmd.clear();
1381 regDiffLater.removeValue();
1383 else if (files_.size() == 1)
1385 if (std::wstring(regDiffLater).empty())
1387 gitCmd.clear();
1388 regDiffLater = files_[0];
1390 else
1392 AddPathCommand(gitCmd, L"diff", true);
1393 gitCmd += _T(" /path2:\"");
1394 gitCmd += std::wstring(regDiffLater);
1395 gitCmd += _T("\"");
1396 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1397 gitCmd += _T(" /alternative");
1398 regDiffLater.removeValue();
1401 else
1403 gitCmd.clear();
1405 break;
1406 case ShellMenuPrevDiff:
1407 AddPathCommand(gitCmd, L"prevdiff", true);
1408 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1409 gitCmd += _T(" /alternative");
1410 break;
1411 case ShellMenuDiffTwo:
1412 AddPathCommand(gitCmd, L"diffcommits", true);
1413 break;
1414 case ShellMenuDropCopyAdd:
1415 AddPathFileDropCommand(gitCmd, L"dropcopyadd");
1416 break;
1417 case ShellMenuDropCopy:
1418 AddPathFileDropCommand(gitCmd, L"dropcopy");
1419 break;
1420 case ShellMenuDropCopyRename:
1421 AddPathFileDropCommand(gitCmd, L"dropcopy");
1422 gitCmd += _T("\" /rename";)
1423 break;
1424 case ShellMenuDropMove:
1425 AddPathFileDropCommand(gitCmd, L"dropmove");
1426 break;
1427 case ShellMenuDropMoveRename:
1428 AddPathFileDropCommand(gitCmd, L"dropmove");
1429 gitCmd += _T("\" /rename";)
1430 break;
1431 case ShellMenuDropExport:
1432 AddPathFileDropCommand(gitCmd, L"dropexport");
1433 break;
1434 case ShellMenuDropExportExtended:
1435 AddPathFileDropCommand(gitCmd, L"dropexport");
1436 gitCmd += _T(" /extended");
1437 break;
1438 case ShellMenuLog:
1439 case ShellMenuLogSubmoduleFolder:
1440 AddPathCommand(gitCmd, L"log", true);
1441 if (id_it->second == ShellMenuLogSubmoduleFolder)
1442 gitCmd += _T(" /submodule");
1443 break;
1444 case ShellMenuDaemon:
1445 AddPathCommand(gitCmd, L"daemon", true);
1446 break;
1447 case ShellMenuRevisionGraph:
1448 AddPathCommand(gitCmd, L"revisiongraph", true);
1449 break;
1450 case ShellMenuConflictEditor:
1451 AddPathCommand(gitCmd, L"conflicteditor", true);
1452 break;
1453 case ShellMenuGitSVNRebase:
1454 AddPathCommand(gitCmd, L"svnrebase", false);
1455 break;
1456 case ShellMenuGitSVNDCommit:
1457 AddPathCommand(gitCmd, L"svndcommit", true);
1458 break;
1459 case ShellMenuGitSVNDFetch:
1460 AddPathCommand(gitCmd, L"svnfetch", false);
1461 break;
1462 case ShellMenuGitSVNIgnore:
1463 AddPathCommand(gitCmd, L"svnignore", false);
1464 break;
1465 case ShellMenuRebase:
1466 AddPathCommand(gitCmd, L"rebase", false);
1467 break;
1468 case ShellMenuShowChanged:
1469 if (files_.size() > 1)
1471 AddPathFileCommand(gitCmd, L"repostatus");
1473 else
1475 AddPathCommand(gitCmd, L"repostatus", true);
1477 break;
1478 case ShellMenuRepoBrowse:
1479 AddPathCommand(gitCmd, L"repobrowser", false);
1480 break;
1481 case ShellMenuRefBrowse:
1482 AddPathCommand(gitCmd, L"refbrowse", false);
1483 break;
1484 case ShellMenuRefLog:
1485 AddPathCommand(gitCmd, L"reflog", false);
1486 break;
1487 case ShellMenuStashSave:
1488 AddPathCommand(gitCmd, L"stashsave", true);
1489 break;
1490 case ShellMenuStashApply:
1491 AddPathCommand(gitCmd, L"stashapply", false);
1492 break;
1493 case ShellMenuStashPop:
1494 AddPathCommand(gitCmd, L"stashpop", false);
1495 break;
1496 case ShellMenuStashList:
1497 AddPathCommand(gitCmd, L"reflog", false);
1498 gitCmd += _T(" /ref:refs/stash");
1499 break;
1500 case ShellMenuBisectStart:
1501 AddPathCommand(gitCmd, L"bisect", false);
1502 gitCmd += _T("\" /start");
1503 break;
1504 case ShellMenuBisectGood:
1505 AddPathCommand(gitCmd, L"bisect", false);
1506 gitCmd += _T("\" /good");
1507 break;
1508 case ShellMenuBisectBad:
1509 AddPathCommand(gitCmd, L"bisect", false);
1510 gitCmd += _T("\" /bad");
1511 break;
1512 case ShellMenuBisectReset:
1513 AddPathCommand(gitCmd, L"bisect", false);
1514 gitCmd += _T("\" /reset");
1515 break;
1516 case ShellMenuSubAdd:
1517 AddPathCommand(gitCmd, L"subadd", false);
1518 break;
1519 case ShellMenuBlame:
1520 AddPathCommand(gitCmd, L"blame", true);
1521 break;
1522 case ShellMenuApplyPatch:
1523 if ((itemStates & ITEMIS_PATCHINCLIPBOARD) && ((~itemStates) & ITEMIS_PATCHFILE))
1525 // if there's a patch file in the clipboard, we save it
1526 // to a temporary file and tell TortoiseGitMerge to use that one
1527 UINT cFormat = RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
1528 if ((cFormat)&&(OpenClipboard(NULL)))
1530 HGLOBAL hglb = GetClipboardData(cFormat);
1531 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
1533 DWORD len = GetTortoiseGitTempPath(0, NULL);
1534 std::unique_ptr<TCHAR[]> path(new TCHAR[len + 1]);
1535 std::unique_ptr<TCHAR[]> tempF(new TCHAR[len + 100]);
1536 GetTortoiseGitTempPath(len + 1, path.get());
1537 GetTempFileName(path.get(), TEXT("git"), 0, tempF.get());
1538 std::wstring sTempFile = std::wstring(tempF.get());
1540 FILE * outFile;
1541 size_t patchlen = strlen(lpstr);
1542 _tfopen_s(&outFile, sTempFile.c_str(), _T("wb"));
1543 if(outFile)
1545 size_t size = fwrite(lpstr, sizeof(char), patchlen, outFile);
1546 if (size == patchlen)
1548 itemStates |= ITEMIS_PATCHFILE;
1549 files_.clear();
1550 files_.push_back(sTempFile);
1552 fclose(outFile);
1554 GlobalUnlock(hglb);
1555 CloseClipboard();
1558 if (itemStates & ITEMIS_PATCHFILE)
1560 gitCmd = _T(" /diff:\"");
1561 if (!files_.empty())
1563 gitCmd += files_.front();
1564 if (itemStatesFolder & ITEMIS_FOLDERINGIT)
1566 gitCmd += _T("\" /patchpath:\"");
1567 gitCmd += folder_;
1570 else
1571 gitCmd += folder_;
1572 if (itemStates & ITEMIS_INVERSIONEDFOLDER)
1573 gitCmd += _T("\" /wc");
1574 else
1575 gitCmd += _T("\"");
1577 else
1579 gitCmd = _T(" /patchpath:\"");
1580 if (!files_.empty())
1581 gitCmd += files_.front();
1582 else
1583 gitCmd += folder_;
1584 gitCmd += _T("\"");
1586 myIDMap.clear();
1587 myVerbsIDMap.clear();
1588 myVerbsMap.clear();
1589 RunCommand(tortoiseMergePath, gitCmd, _T("TortoiseGitMerge launch failed"));
1590 return S_OK;
1591 break;
1592 case ShellMenuClipPaste:
1593 if (WriteClipboardPathsToTempFile(tempfile))
1595 bool bCopy = true;
1596 UINT cPrefDropFormat = RegisterClipboardFormat(_T("Preferred DropEffect"));
1597 if (cPrefDropFormat)
1599 if (OpenClipboard(lpcmi->hwnd))
1601 HGLOBAL hglb = GetClipboardData(cPrefDropFormat);
1602 if (hglb)
1604 DWORD* effect = (DWORD*) GlobalLock(hglb);
1605 if (*effect == DROPEFFECT_MOVE)
1606 bCopy = false;
1607 GlobalUnlock(hglb);
1609 CloseClipboard();
1613 if (bCopy)
1614 gitCmd += _T("pastecopy /pathfile:\"");
1615 else
1616 gitCmd += _T("pastemove /pathfile:\"");
1617 gitCmd += tempfile;
1618 gitCmd += _T("\"");
1619 gitCmd += _T(" /deletepathfile");
1620 gitCmd += _T(" /droptarget:\"");
1621 gitCmd += folder_;
1622 gitCmd += _T("\"");
1624 else return S_OK;
1625 break;
1626 case ShellMenuClone:
1627 AddPathCommand(gitCmd, L"clone", false);
1628 break;
1629 case ShellMenuPull:
1630 AddPathCommand(gitCmd, L"pull", false);
1631 break;
1632 case ShellMenuPush:
1633 AddPathCommand(gitCmd, L"push", false);
1634 break;
1635 case ShellMenuBranch:
1636 AddPathCommand(gitCmd, L"branch", false);
1637 break;
1638 case ShellMenuTag:
1639 AddPathCommand(gitCmd, L"tag", false);
1640 break;
1641 case ShellMenuFormatPatch:
1642 AddPathCommand(gitCmd, L"formatpatch", false);
1643 break;
1644 case ShellMenuImportPatch:
1645 AddPathFileCommand(gitCmd, L"importpatch");
1646 break;
1647 case ShellMenuFetch:
1648 AddPathCommand(gitCmd, L"fetch", false);
1649 break;
1651 default:
1652 break;
1653 //#endregion
1654 } // switch (id_it->second)
1655 if (!gitCmd.empty())
1657 gitCmd += _T(" /hwnd:");
1658 TCHAR buf[30] = { 0 };
1659 _stprintf_s(buf, _T("%p"), (void*)lpcmi->hwnd);
1660 gitCmd += buf;
1661 myIDMap.clear();
1662 myVerbsIDMap.clear();
1663 myVerbsMap.clear();
1664 RunCommand(tortoiseProcPath, gitCmd, _T("TortoiseProc launch failed"));
1666 hr = S_OK;
1667 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1668 } // if (files_.empty() || folder_.empty())
1669 return hr;
1673 // This is for the status bar and things like that:
1674 STDMETHODIMP CShellExt::GetCommandString(UINT_PTR idCmd,
1675 UINT uFlags,
1676 UINT FAR * reserved,
1677 LPSTR pszName,
1678 UINT cchMax)
1680 __try
1682 return GetCommandString_Wrap(idCmd, uFlags, reserved, pszName, cchMax);
1684 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1687 return E_FAIL;
1690 // This is for the status bar and things like that:
1691 STDMETHODIMP CShellExt::GetCommandString_Wrap(UINT_PTR idCmd,
1692 UINT uFlags,
1693 UINT FAR * /*reserved*/,
1694 LPSTR pszName,
1695 UINT cchMax)
1697 PreserveChdir preserveChdir;
1698 //do we know the id?
1699 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
1700 if (id_it == myIDMap.end() || id_it->first != idCmd)
1702 return E_INVALIDARG; //no, we don't
1705 LoadLangDll();
1706 HRESULT hr = E_INVALIDARG;
1708 MAKESTRING(IDS_MENUDESCDEFAULT);
1709 int menuIndex = 0;
1710 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1712 if (menuInfo[menuIndex].command == (GitCommands)id_it->second)
1714 MAKESTRING(menuInfo[menuIndex].menuDescID);
1715 break;
1717 menuIndex++;
1720 const TCHAR * desc = stringtablebuffer;
1721 switch(uFlags)
1723 case GCS_HELPTEXTA:
1725 std::string help = WideToMultibyte(desc);
1726 lstrcpynA(pszName, help.c_str(), cchMax - 1);
1727 hr = S_OK;
1728 break;
1730 case GCS_HELPTEXTW:
1732 wide_string help = desc;
1733 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax - 1);
1734 hr = S_OK;
1735 break;
1737 case GCS_VERBA:
1739 std::map<UINT_PTR, stdstring>::const_iterator verb_id_it = myVerbsIDMap.lower_bound(idCmd);
1740 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
1742 std::string help = WideToMultibyte(verb_id_it->second);
1743 lstrcpynA(pszName, help.c_str(), cchMax - 1);
1744 hr = S_OK;
1747 break;
1748 case GCS_VERBW:
1750 std::map<UINT_PTR, stdstring>::const_iterator verb_id_it = myVerbsIDMap.lower_bound(idCmd);
1751 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
1753 wide_string help = verb_id_it->second;
1754 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": verb : %ws\n", help.c_str());
1755 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax - 1);
1756 hr = S_OK;
1759 break;
1761 return hr;
1764 STDMETHODIMP CShellExt::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
1766 __try
1768 return HandleMenuMsg_Wrap(uMsg, wParam, lParam);
1770 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1773 return E_FAIL;
1776 STDMETHODIMP CShellExt::HandleMenuMsg_Wrap(UINT uMsg, WPARAM wParam, LPARAM lParam)
1778 LRESULT res;
1779 return HandleMenuMsg2(uMsg, wParam, lParam, &res);
1782 STDMETHODIMP CShellExt::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pResult)
1784 __try
1786 return HandleMenuMsg2_Wrap(uMsg, wParam, lParam, pResult);
1788 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1791 return E_FAIL;
1794 STDMETHODIMP CShellExt::HandleMenuMsg2_Wrap(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pResult)
1796 PreserveChdir preserveChdir;
1798 LRESULT res;
1799 if (pResult == NULL)
1800 pResult = &res;
1801 *pResult = FALSE;
1803 LoadLangDll();
1804 switch (uMsg)
1806 case WM_MEASUREITEM:
1808 MEASUREITEMSTRUCT* lpmis = (MEASUREITEMSTRUCT*)lParam;
1809 if (lpmis==NULL)
1810 break;
1811 lpmis->itemWidth = 16;
1812 lpmis->itemHeight = 16;
1813 *pResult = TRUE;
1815 break;
1816 case WM_DRAWITEM:
1818 LPCTSTR resource;
1819 DRAWITEMSTRUCT* lpdis = (DRAWITEMSTRUCT*)lParam;
1820 if ((lpdis==NULL)||(lpdis->CtlType != ODT_MENU))
1821 return S_OK; //not for a menu
1822 resource = GetMenuTextFromResource((int)myIDMap[lpdis->itemID]);
1823 if (resource == NULL)
1824 return S_OK;
1825 HICON hIcon = (HICON)LoadImage(g_hResInst, resource, IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
1826 if (hIcon == NULL)
1827 return S_OK;
1828 DrawIconEx(lpdis->hDC,
1829 lpdis->rcItem.left,
1830 lpdis->rcItem.top + (lpdis->rcItem.bottom - lpdis->rcItem.top - 16) / 2,
1831 hIcon, 16, 16,
1832 0, NULL, DI_NORMAL);
1833 DestroyIcon(hIcon);
1834 *pResult = TRUE;
1836 break;
1837 case WM_MENUCHAR:
1839 TCHAR *szItem;
1840 if (HIWORD(wParam) != MF_POPUP)
1841 return S_OK;
1842 int nChar = LOWORD(wParam);
1843 if (_istascii((wint_t)nChar) && _istupper((wint_t)nChar))
1844 nChar = tolower(nChar);
1845 // we have the char the user pressed, now search that char in all our
1846 // menu items
1847 std::vector<UINT_PTR> accmenus;
1848 for (std::map<UINT_PTR, UINT_PTR>::iterator It = mySubMenuMap.begin(); It != mySubMenuMap.end(); ++It)
1850 LPCTSTR resource = GetMenuTextFromResource((int)mySubMenuMap[It->first]);
1851 if (resource == NULL)
1852 continue;
1853 szItem = stringtablebuffer;
1854 TCHAR * amp = _tcschr(szItem, '&');
1855 if (amp == NULL)
1856 continue;
1857 amp++;
1858 int ampChar = LOWORD(*amp);
1859 if (_istascii((wint_t)ampChar) && _istupper((wint_t)ampChar))
1860 ampChar = tolower(ampChar);
1861 if (ampChar == nChar)
1863 // yep, we found a menu which has the pressed key
1864 // as an accelerator. Add that menu to the list to
1865 // process later.
1866 accmenus.push_back(It->first);
1869 if (accmenus.empty())
1871 // no menu with that accelerator key.
1872 *pResult = MAKELONG(0, MNC_IGNORE);
1873 return S_OK;
1875 if (accmenus.size() == 1)
1877 // Only one menu with that accelerator key. We're lucky!
1878 // So just execute that menu entry.
1879 *pResult = MAKELONG(accmenus[0], MNC_EXECUTE);
1880 return S_OK;
1882 if (accmenus.size() > 1)
1884 // we have more than one menu item with this accelerator key!
1885 MENUITEMINFO mif;
1886 mif.cbSize = sizeof(MENUITEMINFO);
1887 mif.fMask = MIIM_STATE;
1888 for (std::vector<UINT_PTR>::iterator it = accmenus.begin(); it != accmenus.end(); ++it)
1890 GetMenuItemInfo((HMENU)lParam, (UINT)*it, TRUE, &mif);
1891 if (mif.fState == MFS_HILITE)
1893 // this is the selected item, so select the next one
1894 ++it;
1895 if (it == accmenus.end())
1896 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
1897 else
1898 *pResult = MAKELONG(*it, MNC_SELECT);
1899 return S_OK;
1902 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
1905 break;
1906 default:
1907 return S_OK;
1910 return S_OK;
1913 LPCTSTR CShellExt::GetMenuTextFromResource(int id)
1915 TCHAR textbuf[255] = { 0 };
1916 LPCTSTR resource = NULL;
1917 unsigned __int64 layout = g_ShellCache.GetMenuLayout();
1918 space = 6;
1920 int menuIndex = 0;
1921 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1923 if (menuInfo[menuIndex].command == id)
1925 MAKESTRING(menuInfo[menuIndex].menuTextID);
1926 resource = MAKEINTRESOURCE(menuInfo[menuIndex].iconID);
1927 switch (id)
1929 case ShellSubMenuMultiple:
1930 case ShellSubMenuLink:
1931 case ShellSubMenuFolder:
1932 case ShellSubMenuFile:
1933 case ShellSubMenu:
1934 space = 0;
1935 break;
1936 default:
1937 space = (layout & menuInfo[menuIndex].menuID) ? 0 : 6;
1938 if (layout & menuInfo[menuIndex].menuID)
1940 _tcscpy_s(textbuf, 255, _T("Git "));
1941 _tcscat_s(textbuf, 255, stringtablebuffer);
1942 _tcscpy_s(stringtablebuffer, 255, textbuf);
1944 break;
1946 return resource;
1948 menuIndex++;
1950 return NULL;
1953 bool CShellExt::IsIllegalFolder(std::wstring folder, int * cslidarray)
1955 int i=0;
1956 TCHAR buf[MAX_PATH] = {0}; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
1957 LPITEMIDLIST pidl = NULL;
1958 while (cslidarray[i])
1960 ++i;
1961 pidl = NULL;
1962 if (SHGetFolderLocation(NULL, cslidarray[i-1], NULL, 0, &pidl)!=S_OK)
1963 continue;
1964 if (!SHGetPathFromIDList(pidl, buf))
1966 // not a file system path, definitely illegal for our use
1967 CoTaskMemFree(pidl);
1968 continue;
1970 CoTaskMemFree(pidl);
1971 if (_tcslen(buf)==0)
1972 continue;
1973 if (_tcscmp(buf, folder.c_str())==0)
1974 return true;
1976 return false;
1979 bool CShellExt::InsertIgnoreSubmenus(UINT &idCmd, UINT idCmdFirst, HMENU hMenu, HMENU subMenu, UINT &indexMenu, int &indexSubMenu, unsigned __int64 topmenu, bool bShowIcons, UINT /*uFlags*/)
1981 HMENU ignoresubmenu = NULL;
1982 int indexignoresub = 0;
1983 bool bShowIgnoreMenu = false;
1984 TCHAR maskbuf[MAX_PATH] = {0}; // MAX_PATH is ok, since this only holds a filename
1985 TCHAR ignorepath[MAX_PATH] = {0}; // MAX_PATH is ok, since this only holds a filename
1986 if (files_.empty())
1987 return false;
1988 UINT icon = bShowIcons ? IDI_IGNORE : 0;
1990 std::vector<stdstring>::iterator I = files_.begin();
1991 if (_tcsrchr(I->c_str(), '\\'))
1992 _tcscpy_s(ignorepath, MAX_PATH, _tcsrchr(I->c_str(), '\\')+1);
1993 else
1994 _tcscpy_s(ignorepath, MAX_PATH, I->c_str());
1995 if ((itemStates & ITEMIS_IGNORED) && (!ignoredprops.empty()))
1997 // check if the item name is ignored or the mask
1998 size_t p = 0;
1999 while ( (p=ignoredprops.find( ignorepath,p )) != -1 )
2001 if ( (p==0 || ignoredprops[p-1]==TCHAR('\n'))
2002 && (p+_tcslen(ignorepath)==ignoredprops.length() || ignoredprops[p+_tcslen(ignorepath)+1]==TCHAR('\n')) )
2004 break;
2006 p++;
2008 if (p!=-1)
2010 ignoresubmenu = CreateMenu();
2011 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2012 stdstring verb = stdstring(ignorepath);
2013 myVerbsMap[verb] = idCmd - idCmdFirst;
2014 myVerbsMap[verb] = idCmd;
2015 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2016 myVerbsIDMap[idCmd] = verb;
2017 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnore;
2018 myIDMap[idCmd++] = ShellMenuUnIgnore;
2019 bShowIgnoreMenu = true;
2021 _tcscpy_s(maskbuf, MAX_PATH, _T("*"));
2022 if (_tcsrchr(ignorepath, '.'))
2024 _tcscat_s(maskbuf, MAX_PATH, _tcsrchr(ignorepath, '.'));
2025 p = ignoredprops.find(maskbuf);
2026 if ((p!=-1) &&
2027 ((ignoredprops.compare(maskbuf)==0) || (ignoredprops.find('\n', p)==p+_tcslen(maskbuf)+1) || (ignoredprops.rfind('\n', p)==p-1)))
2029 if (ignoresubmenu==NULL)
2030 ignoresubmenu = CreateMenu();
2032 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2033 stdstring verb = stdstring(maskbuf);
2034 myVerbsMap[verb] = idCmd - idCmdFirst;
2035 myVerbsMap[verb] = idCmd;
2036 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2037 myVerbsIDMap[idCmd] = verb;
2038 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreCaseSensitive;
2039 myIDMap[idCmd++] = ShellMenuUnIgnoreCaseSensitive;
2040 bShowIgnoreMenu = true;
2044 else if ((itemStates & ITEMIS_IGNORED) == 0)
2046 bShowIgnoreMenu = true;
2047 ignoresubmenu = CreateMenu();
2048 if (itemStates & ITEMIS_ONLYONE)
2050 if (itemStates & ITEMIS_INGIT)
2052 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2053 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
2054 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
2056 _tcscpy_s(maskbuf, MAX_PATH, _T("*"));
2057 if (!(itemStates & ITEMIS_FOLDER) && _tcsrchr(ignorepath, '.'))
2059 _tcscat_s(maskbuf, MAX_PATH, _tcsrchr(ignorepath, '.'));
2060 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2061 stdstring verb = stdstring(maskbuf);
2062 myVerbsMap[verb] = idCmd - idCmdFirst;
2063 myVerbsMap[verb] = idCmd;
2064 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2065 myVerbsIDMap[idCmd] = verb;
2066 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
2067 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
2070 else
2072 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2073 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
2074 myIDMap[idCmd++] = ShellMenuIgnore;
2076 _tcscpy_s(maskbuf, MAX_PATH, _T("*"));
2077 if (!(itemStates & ITEMIS_FOLDER) && _tcsrchr(ignorepath, '.'))
2079 _tcscat_s(maskbuf, MAX_PATH, _tcsrchr(ignorepath, '.'));
2080 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2081 stdstring verb = stdstring(maskbuf);
2082 myVerbsMap[verb] = idCmd - idCmdFirst;
2083 myVerbsMap[verb] = idCmd;
2084 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2085 myVerbsIDMap[idCmd] = verb;
2086 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2087 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2091 else
2093 if (itemStates & ITEMIS_INGIT)
2095 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE);
2096 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2097 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2098 stdstring verb = stdstring(ignorepath);
2099 myVerbsMap[verb] = idCmd - idCmdFirst;
2100 myVerbsMap[verb] = idCmd;
2101 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2102 myVerbsIDMap[idCmd] = verb;
2103 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
2104 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
2106 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK);
2107 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2108 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2109 verb = stdstring(ignorepath);
2110 myVerbsMap[verb] = idCmd - idCmdFirst;
2111 myVerbsMap[verb] = idCmd;
2112 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2113 myVerbsIDMap[idCmd] = verb;
2114 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
2115 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
2117 else
2119 MAKESTRING(IDS_MENUIGNOREMULTIPLE);
2120 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2121 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2122 stdstring verb = stdstring(ignorepath);
2123 myVerbsMap[verb] = idCmd - idCmdFirst;
2124 myVerbsMap[verb] = idCmd;
2125 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2126 myVerbsIDMap[idCmd] = verb;
2127 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
2128 myIDMap[idCmd++] = ShellMenuIgnore;
2130 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK);
2131 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2132 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2133 verb = stdstring(ignorepath);
2134 myVerbsMap[verb] = idCmd - idCmdFirst;
2135 myVerbsMap[verb] = idCmd;
2136 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2137 myVerbsIDMap[idCmd] = verb;
2138 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2139 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2144 if (bShowIgnoreMenu)
2146 MENUITEMINFO menuiteminfo;
2147 SecureZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
2148 menuiteminfo.cbSize = sizeof(menuiteminfo);
2149 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
2150 if (icon)
2152 menuiteminfo.fMask |= MIIM_BITMAP;
2153 menuiteminfo.hbmpItem = SysInfo::Instance().IsVistaOrLater() ? m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, icon) : m_iconBitmapUtils.IconToBitmap(g_hResInst, icon);
2155 menuiteminfo.fType = MFT_STRING;
2156 menuiteminfo.hSubMenu = ignoresubmenu;
2157 menuiteminfo.wID = idCmd;
2158 SecureZeroMemory(stringtablebuffer, sizeof(stringtablebuffer));
2159 if (itemStates & ITEMIS_IGNORED)
2160 GetMenuTextFromResource(ShellMenuUnIgnoreSub);
2161 else if (itemStates & ITEMIS_INGIT)
2162 GetMenuTextFromResource(ShellMenuDeleteIgnoreSub);
2163 else
2164 GetMenuTextFromResource(ShellMenuIgnoreSub);
2165 menuiteminfo.dwTypeData = stringtablebuffer;
2166 menuiteminfo.cch = (UINT)min(_tcslen(menuiteminfo.dwTypeData), UINT_MAX);
2168 InsertMenuItem((topmenu & MENUIGNORE) ? hMenu : subMenu, (topmenu & MENUIGNORE) ? indexMenu++ : indexSubMenu++, TRUE, &menuiteminfo);
2169 if (itemStates & ITEMIS_IGNORED)
2171 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreSub;
2172 myIDMap[idCmd++] = ShellMenuUnIgnoreSub;
2174 else
2176 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreSub;
2177 myIDMap[idCmd++] = ShellMenuIgnoreSub;
2180 return bShowIgnoreMenu;
2183 void CShellExt::RunCommand(const tstring& path, const tstring& command, LPCTSTR errorMessage)
2185 if (CCreateProcessHelper::CreateProcessDetached(path.c_str(), const_cast<TCHAR*>(command.c_str())))
2187 // process started - exit
2188 return;
2191 MessageBox(NULL, CFormatMessageWrapper(), errorMessage, MB_OK | MB_ICONINFORMATION);
2194 bool CShellExt::ShouldInsertItem(const MenuInfo& item) const
2196 return ShouldEnableMenu(item.first) || ShouldEnableMenu(item.second) ||
2197 ShouldEnableMenu(item.third) || ShouldEnableMenu(item.fourth);
2200 bool CShellExt::ShouldEnableMenu(const YesNoPair& pair) const
2202 if (pair.yes && pair.no)
2204 if (((pair.yes & itemStates) == pair.yes) && ((pair.no & (~itemStates)) == pair.no))
2205 return true;
2207 else if ((pair.yes) && ((pair.yes & itemStates) == pair.yes))
2208 return true;
2209 else if ((pair.no) && ((pair.no & (~itemStates)) == pair.no))
2210 return true;
2211 return false;