Make using alternative merge tool work by pressing the shift key
[TortoiseGit.git] / src / TortoiseShell / ContextMenu.cpp
blobd9b1612a6496184fc2558665d41f8d832423b77f
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012, 2014-2016 - TortoiseSVN
4 // Copyright (C) 2008-2016 - 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 tstring 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 nullptr,
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)
84 if (!CRegStdDWORD(L"Software\\TortoiseGit\\EnableDragContextMenu", TRUE))
86 ReleaseStgMedium(&medium);
87 return S_OK;
90 FORMATETC etc = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
91 STGMEDIUM stg = { TYMED_HGLOBAL };
92 if ( FAILED( pDataObj->GetData ( &etc, &stg )))
94 ReleaseStgMedium ( &medium );
95 return E_INVALIDARG;
99 HDROP drop = (HDROP)GlobalLock(stg.hGlobal);
100 if (!drop)
102 ReleaseStgMedium ( &stg );
103 ReleaseStgMedium ( &medium );
104 return E_INVALIDARG;
107 int count = DragQueryFile(drop, (UINT)-1, nullptr, 0);
108 if (count == 1)
109 itemStates |= ITEMIS_ONLYONE;
110 for (int i = 0; i < count; i++)
112 // find the path length in chars
113 UINT len = DragQueryFile(drop, i, nullptr, 0);
114 if (len == 0)
115 continue;
116 auto szFileName = std::make_unique<TCHAR[]>(len + 1);
117 if (0 == DragQueryFile(drop, i, szFileName.get(), len + 1))
118 continue;
119 tstring str = tstring(szFileName.get());
120 if ((!str.empty()) && (g_ShellCache.IsContextPathAllowed(szFileName.get())))
123 CTGitPath strpath;
124 strpath.SetFromWin(str.c_str());
125 itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE : 0;
126 itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE : 0;
128 files_.push_back(str);
129 if (i == 0)
131 //get the git status of the item
132 git_wc_status_kind status = git_wc_status_none;
133 CTGitPath askedpath;
134 askedpath.SetFromWin(str.c_str());
135 CString workTreePath;
136 askedpath.HasAdminDir(&workTreePath);
137 uuidSource = workTreePath;
140 if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(str.c_str()))
142 CTGitPath tpath(str.c_str());
143 if (!tpath.HasAdminDir())
145 status = git_wc_status_none;
146 continue;
148 if (tpath.IsAdminDir())
150 status = git_wc_status_none;
151 continue;
153 TGITCacheResponse itemStatus;
154 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
155 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
157 fetchedstatus = status = GitStatus::GetMoreImportant(itemStatus.m_status.text_status, itemStatus.m_status.prop_status);
158 if (askedpath.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
160 itemStates |= ITEMIS_FOLDER;
161 if ((status != git_wc_status_unversioned) && (status != git_wc_status_ignored) && (status != git_wc_status_none))
162 itemStates |= ITEMIS_FOLDERINGIT;
166 else
168 GitStatus stat;
169 stat.GetStatus(CTGitPath(str.c_str()), false, false, true);
170 if (stat.status)
172 statuspath = str;
173 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
174 fetchedstatus = status;
175 if ( askedpath.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
177 itemStates |= ITEMIS_FOLDER;
178 if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
179 itemStates |= ITEMIS_FOLDERINGIT;
181 //if ((stat.status->entry)&&(stat.status->entry->uuid))
182 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
184 else
186 // sometimes, git_client_status() returns with an error.
187 // in that case, we have to check if the working copy is versioned
188 // anyway to show the 'correct' context menu
189 if (askedpath.HasAdminDir())
190 status = git_wc_status_normal;
194 catch ( ... )
196 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
199 // TODO: should we really assume any sub-directory to be versioned
200 // or only if it contains versioned files
201 itemStates |= askedpath.GetAdminDirMask();
203 if ((status == git_wc_status_unversioned) || (status == git_wc_status_ignored) || (status == git_wc_status_none))
204 itemStates &= ~ITEMIS_INGIT;
206 if (status == git_wc_status_ignored)
207 itemStates |= ITEMIS_IGNORED;
208 if (status == git_wc_status_normal)
209 itemStates |= ITEMIS_NORMAL;
210 if (status == git_wc_status_conflicted)
211 itemStates |= ITEMIS_CONFLICTED;
212 if (status == git_wc_status_added)
213 itemStates |= ITEMIS_ADDED;
214 if (status == git_wc_status_deleted)
215 itemStates |= ITEMIS_DELETED;
218 } // for (int i = 0; i < count; i++)
219 GlobalUnlock ( drop );
220 ReleaseStgMedium ( &stg );
222 } // if (m_State == FileStateDropHandler)
223 else
225 //Enumerate PIDLs which the user has selected
226 CIDA* cida = (CIDA*)GlobalLock(medium.hGlobal);
227 ItemIDList parent( GetPIDLFolder (cida));
229 int count = cida->cidl;
230 BOOL statfetched = FALSE;
231 for (int i = 0; i < count; ++i)
233 ItemIDList child (GetPIDLItem (cida, i), &parent);
234 tstring str = child.toString();
235 if ((str.empty() == false)&&(g_ShellCache.IsContextPathAllowed(str.c_str())))
237 //check if our menu is requested for a git admin directory
238 if (GitAdminDir::IsAdminDirPath(str.c_str()))
239 continue;
241 files_.push_back(str);
242 CTGitPath strpath;
243 strpath.SetFromWin(str.c_str());
244 itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE : 0;
245 itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE : 0;
246 if (!statfetched)
248 //get the git status of the item
249 git_wc_status_kind status = git_wc_status_none;
250 if ((g_ShellCache.IsSimpleContext())&&(strpath.IsDirectory()))
252 if (strpath.HasAdminDir())
253 status = git_wc_status_normal;
255 else
259 if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(str.c_str()))
261 CTGitPath tpath(str.c_str());
262 if(!tpath.HasAdminDir())
264 status = git_wc_status_none;
265 continue;
267 if(tpath.IsAdminDir())
269 status = git_wc_status_none;
270 continue;
272 TGITCacheResponse itemStatus;
273 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
274 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
276 fetchedstatus = status = GitStatus::GetMoreImportant(itemStatus.m_status.text_status, itemStatus.m_status.prop_status);
277 if (strpath.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
279 itemStates |= ITEMIS_FOLDER;
280 if ((status != git_wc_status_unversioned) && (status != git_wc_status_ignored) && (status != git_wc_status_none))
281 itemStates |= ITEMIS_FOLDERINGIT;
283 if (status == git_wc_status_conflicted)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
284 itemStates |= ITEMIS_CONFLICTED;
287 else
289 GitStatus stat;
290 if (strpath.HasAdminDir())
291 stat.GetStatus(strpath, false, false, true);
292 statuspath = str;
293 if (stat.status)
295 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
296 fetchedstatus = status;
297 if ( strpath.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
299 itemStates |= ITEMIS_FOLDER;
300 if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
301 itemStates |= ITEMIS_FOLDERINGIT;
303 // TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
304 if (status == git_wc_status_conflicted)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
305 itemStates |= ITEMIS_CONFLICTED;
306 //if ((stat.status->entry)&&(stat.status->entry->uuid))
307 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
309 else
311 // sometimes, git_client_status() returns with an error.
312 // in that case, we have to check if the working copy is versioned
313 // anyway to show the 'correct' context menu
314 if (strpath.HasAdminDir())
316 status = git_wc_status_normal;
317 fetchedstatus = status;
321 statfetched = TRUE;
323 catch ( ... )
325 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
329 itemStates |= strpath.GetAdminDirMask();
331 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
332 itemStates &= ~ITEMIS_INGIT;
333 if (status == git_wc_status_ignored)
335 itemStates |= ITEMIS_IGNORED;
338 if (status == git_wc_status_normal)
339 itemStates |= ITEMIS_NORMAL;
340 if (status == git_wc_status_conflicted)
341 itemStates |= ITEMIS_CONFLICTED;
342 if (status == git_wc_status_added)
343 itemStates |= ITEMIS_ADDED;
344 if (status == git_wc_status_deleted)
345 itemStates |= ITEMIS_DELETED;
348 } // for (int i = 0; i < count; ++i)
349 ItemIDList child (GetPIDLItem (cida, 0), &parent);
350 if (g_ShellCache.HasGITAdminDir(child.toString().c_str(), FALSE))
351 itemStates |= ITEMIS_INVERSIONEDFOLDER;
353 if (GitAdminDir::IsBareRepo(child.toString().c_str()))
354 itemStates = ITEMIS_BAREREPO;
356 GlobalUnlock(medium.hGlobal);
358 // if the item is a versioned folder, check if there's a patch file
359 // in the clipboard to be used in "Apply Patch"
360 UINT cFormatDiff = RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
361 if (cFormatDiff)
363 if (IsClipboardFormatAvailable(cFormatDiff))
364 itemStates |= ITEMIS_PATCHINCLIPBOARD;
366 if (IsClipboardFormatAvailable(CF_HDROP))
367 itemStates |= ITEMIS_PATHINCLIPBOARD;
371 ReleaseStgMedium ( &medium );
372 if (medium.pUnkForRelease)
374 IUnknown* relInterface = (IUnknown*)medium.pUnkForRelease;
375 relInterface->Release();
380 // get folder background
381 if (pIDFolder)
383 ItemIDList list(pIDFolder);
384 folder_ = list.toString();
385 git_wc_status_kind status = git_wc_status_none;
386 if (IsClipboardFormatAvailable(CF_HDROP))
387 itemStatesFolder |= ITEMIS_PATHINCLIPBOARD;
389 CTGitPath askedpath;
390 askedpath.SetFromWin(folder_.c_str());
392 if (g_ShellCache.IsContextPathAllowed(folder_.c_str()))
394 if (folder_.compare(statuspath)!=0)
396 CString worktreePath;
397 askedpath.HasAdminDir(&worktreePath);
398 uuidTarget = worktreePath;
401 if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(folder_.c_str()))
403 CTGitPath tpath(folder_.c_str());
404 if(!tpath.HasAdminDir())
405 status = git_wc_status_none;
406 else if(tpath.IsAdminDir())
407 status = git_wc_status_none;
408 else
410 TGITCacheResponse itemStatus;
411 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
412 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
413 status = GitStatus::GetMoreImportant(itemStatus.m_status.text_status, itemStatus.m_status.prop_status);
416 else
418 GitStatus stat;
419 stat.GetStatus(CTGitPath(folder_.c_str()), false, false, true);
420 if (stat.status)
422 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
423 // if ((stat.status->entry)&&(stat.status->entry->uuid))
424 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
427 else
429 // sometimes, git_client_status() returns with an error.
430 // in that case, we have to check if the working copy is versioned
431 // anyway to show the 'correct' context menu
432 if (askedpath.HasAdminDir())
433 status = git_wc_status_normal;
437 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
438 itemStatesFolder |= askedpath.GetAdminDirMask();
440 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
441 itemStates &= ~ITEMIS_INGIT;
443 if (status == git_wc_status_normal)
444 itemStatesFolder |= ITEMIS_NORMAL;
445 if (status == git_wc_status_conflicted)
446 itemStatesFolder |= ITEMIS_CONFLICTED;
447 if (status == git_wc_status_added)
448 itemStatesFolder |= ITEMIS_ADDED;
449 if (status == git_wc_status_deleted)
450 itemStatesFolder |= ITEMIS_DELETED;
453 catch ( ... )
455 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
458 else
460 status = fetchedstatus;
462 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
463 itemStatesFolder |= askedpath.GetAdminDirMask();
465 if (status == git_wc_status_ignored)
466 itemStatesFolder |= ITEMIS_IGNORED;
467 itemStatesFolder |= ITEMIS_FOLDER;
468 if (files_.empty())
469 itemStates |= ITEMIS_ONLYONE;
470 if (m_State != FileStateDropHandler)
471 itemStates |= itemStatesFolder;
473 else
475 folder_.clear();
476 status = fetchedstatus;
479 if (files_.size() == 2)
480 itemStates |= ITEMIS_TWO;
481 if ((files_.size() == 1)&&(g_ShellCache.IsContextPathAllowed(files_.front().c_str())))
483 itemStates |= ITEMIS_ONLYONE;
484 if (m_State != FileStateDropHandler)
486 if (PathIsDirectory(files_.front().c_str()))
488 folder_ = files_.front();
489 git_wc_status_kind status = git_wc_status_none;
490 CTGitPath askedpath;
491 askedpath.SetFromWin(folder_.c_str());
493 if (folder_.compare(statuspath)!=0)
497 GitStatus stat;
498 stat.GetStatus(CTGitPath(folder_.c_str()), false, false, true);
499 if (stat.status)
501 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
502 // if ((stat.status->entry)&&(stat.status->entry->uuid))
503 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
506 catch ( ... )
508 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
511 else
513 status = fetchedstatus;
515 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
516 itemStates |= askedpath.GetAdminDirMask();
518 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
519 itemStates &= ~ITEMIS_INGIT;
521 if (status == git_wc_status_ignored)
522 itemStates |= ITEMIS_IGNORED;
523 itemStates |= ITEMIS_FOLDER;
524 if (status == git_wc_status_added)
525 itemStates |= ITEMIS_ADDED;
526 if (status == git_wc_status_deleted)
527 itemStates |= ITEMIS_DELETED;
534 return S_OK;
537 void CShellExt::InsertGitMenu(BOOL istop, HMENU menu, UINT pos, UINT_PTR id, UINT stringid, UINT icon, UINT idCmdFirst, GitCommands com, UINT /*uFlags*/)
539 TCHAR menutextbuffer[512] = {0};
540 TCHAR verbsbuffer[255] = {0};
541 MAKESTRING(stringid);
543 if (istop)
545 //menu entry for the top context menu, so append an "Git " before
546 //the menu text to indicate where the entry comes from
547 _tcscpy_s(menutextbuffer, 255, _T("Git "));
548 if (!g_ShellCache.HasShellMenuAccelerators())
550 // remove the accelerators
551 tstring temp = stringtablebuffer;
552 temp.erase(std::remove(temp.begin(), temp.end(), '&'), temp.end());
553 _tcscpy_s(stringtablebuffer, 255, temp.c_str());
556 _tcscat_s(menutextbuffer, 255, stringtablebuffer);
558 // insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
559 // so we have an easy and fast way to check the current branch
560 // (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
561 if (com == ShellMenuCommit)
563 // get branch name
564 CTGitPath path(folder_.empty() ? files_.front().c_str() : folder_.c_str());
565 CString sProjectRoot;
566 CString sBranchName;
568 if (path.GetAdminDirMask() & ITEMIS_SUBMODULE)
570 if (istop)
571 _tcscpy_s(menutextbuffer, 255, _T("Git "));
572 else
573 menutextbuffer[0] = '\0';
574 MAKESTRING(IDS_MENUCOMMITSUBMODULE);
575 _tcscat_s(menutextbuffer, 255, stringtablebuffer);
578 if (path.HasAdminDir(&sProjectRoot) && !CGit::GetCurrentBranchFromFile(sProjectRoot, sBranchName))
580 if (sBranchName.GetLength() == 40)
582 // if SHA1 only show 4 first bytes
583 BOOL bIsSha1 = TRUE;
584 for (int i=0; i<40; i++)
585 if ( !iswxdigit(sBranchName[i]) )
587 bIsSha1 = FALSE;
588 break;
590 if (bIsSha1)
591 sBranchName = sBranchName.Left(8) + _T("....");
594 // sanity check
595 if (sBranchName.GetLength() > 64)
596 sBranchName = sBranchName.Left(64) + _T("...");
598 // scan to before "..."
599 LPTSTR s = menutextbuffer + _tcslen(menutextbuffer)-1;
600 if (s > menutextbuffer)
602 while (s > menutextbuffer)
604 if (*s != _T('.'))
606 s++;
607 break;
609 s--;
612 else
614 s = menutextbuffer;
617 // append branch name and end with ...
618 _tcscpy_s(s, 255 - _tcslen(menutextbuffer) - 1, _T(" -> \"") + sBranchName + _T("\"..."));
622 if (com == ShellMenuDiffLater)
624 std::wstring sPath = regDiffLater;
625 if (!sPath.empty())
627 // add the path of the saved file
628 wchar_t compact[40] = {0};
629 PathCompactPathEx(compact, sPath.c_str(), _countof(compact) - 1, 0);
630 MAKESTRING(IDS_MENUDIFFNOW);
631 CString sMenu;
632 sMenu.Format(CString(stringtablebuffer), compact);
633 wcscpy_s(menutextbuffer, sMenu);
637 MENUITEMINFO menuiteminfo = { 0 };
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 = m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, icon);
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 tstring verb = tstring(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(tstring& tempfile)
677 tempfile = tstring();
678 //write all selected files and paths to a temporary file
679 //for TortoiseGitProc.exe to read out again.
680 DWORD written = 0;
681 DWORD pathlength = GetTortoiseGitTempPath(0, nullptr);
682 auto path = std::make_unique<TCHAR[]>(pathlength + 1);
683 auto tempFile = std::make_unique<TCHAR[]>(pathlength + 100);
684 GetTortoiseGitTempPath(pathlength+1, path.get());
685 GetTempFileName(path.get(), _T("git"), 0, tempFile.get());
686 tempfile = tstring(tempFile.get());
688 CAutoFile file = ::CreateFile(tempFile.get(),
689 GENERIC_WRITE,
690 FILE_SHARE_READ,
691 nullptr,
692 CREATE_ALWAYS,
693 FILE_ATTRIBUTE_TEMPORARY,
694 nullptr);
696 if (!file)
697 return false;
699 if (!IsClipboardFormatAvailable(CF_HDROP))
700 return false;
701 if (!OpenClipboard(nullptr))
702 return false;
704 tstring sClipboardText;
705 HGLOBAL hglb = GetClipboardData(CF_HDROP);
706 SCOPE_EXIT
708 GlobalUnlock(hglb);
709 CloseClipboard();
711 HDROP hDrop = (HDROP)GlobalLock(hglb);
712 if (!hDrop)
713 return false;
714 SCOPE_EXIT { GlobalUnlock(hDrop); };
716 TCHAR szFileName[MAX_PATH] = {0};
717 UINT cFiles = DragQueryFile(hDrop, 0xFFFFFFFF, nullptr, 0);
718 for(UINT i = 0; i < cFiles; ++i)
720 DragQueryFile(hDrop, i, szFileName, _countof(szFileName));
721 tstring filename = szFileName;
722 ::WriteFile (file, filename.c_str(), (DWORD)filename.size()*sizeof(TCHAR), &written, 0);
723 ::WriteFile (file, _T("\n"), 2, &written, 0);
726 return true;
729 tstring 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, nullptr);
734 auto path = std::make_unique<TCHAR[]>(pathlength + 1);
735 auto tempFile = std::make_unique<TCHAR[]>(pathlength + 100);
736 GetTortoiseGitTempPath(pathlength + 1, path.get());
737 GetTempFileName(path.get(), _T("git"), 0, tempFile.get());
738 tstring retFilePath = tstring(tempFile.get());
740 CAutoFile file = ::CreateFile (tempFile.get(),
741 GENERIC_WRITE,
742 FILE_SHARE_READ,
743 nullptr,
744 CREATE_ALWAYS,
745 FILE_ATTRIBUTE_TEMPORARY,
746 nullptr);
748 if (!file)
750 MessageBox(nullptr, L"Could not create temporary file. Please check (the permissions of) your temp-folder: " + CString(tempFile.get()), L"TortoiseGit", MB_ICONERROR);
751 return tstring();
754 DWORD written = 0;
755 if (files_.empty())
757 ::WriteFile (file, folder_.c_str(), (DWORD)folder_.size()*sizeof(TCHAR), &written, 0);
758 ::WriteFile (file, _T("\n"), 2, &written, 0);
761 for (const auto& file_ : files_)
763 ::WriteFile(file, file_.c_str(), (DWORD)file_.size() * sizeof(TCHAR), &written, 0);
764 ::WriteFile (file, _T("\n"), 2, &written, 0);
766 return retFilePath;
769 STDMETHODIMP CShellExt::QueryDropContext(UINT uFlags, UINT idCmdFirst, HMENU hMenu, UINT &indexMenu)
771 if (!CRegStdDWORD(L"Software\\TortoiseGit\\EnableDragContextMenu", TRUE))
772 return S_OK;
774 PreserveChdir preserveChdir;
775 LoadLangDll();
777 if ((uFlags & CMF_DEFAULTONLY)!=0)
778 return S_OK; //we don't change the default action
780 if (files_.empty() || folder_.empty())
781 return S_OK;
783 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
784 return S_OK;
786 bool bSourceAndTargetFromSameRepository = (uuidSource.compare(uuidTarget) == 0) || uuidSource.empty() || uuidTarget.empty();
788 //the drop handler only has eight commands, but not all are visible at the same time:
789 //if the source file(s) are under version control then those files can be moved
790 //to the new location or they can be moved with a rename,
791 //if they are unversioned then they can be added to the working copy
792 //if they are versioned, they also can be exported to an unversioned location
793 UINT idCmd = idCmdFirst;
795 bool moveAvailable = false;
796 // Git move here
797 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
798 if ((bSourceAndTargetFromSameRepository || (itemStatesFolder & ITEMIS_ADDED)) && (itemStatesFolder & ITEMIS_FOLDERINGIT) && ((itemStates & (ITEMIS_NORMAL | ITEMIS_INGIT | ITEMIS_FOLDERINGIT)) && ((~itemStates) & ITEMIS_ADDED)))
800 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVEMENU, 0, idCmdFirst, ShellMenuDropMove, uFlags);
801 moveAvailable = true;
804 // Git move and rename here
805 // 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
806 if ((bSourceAndTargetFromSameRepository || (itemStatesFolder & ITEMIS_ADDED)) && (itemStatesFolder & ITEMIS_FOLDERINGIT) && (itemStates & (ITEMIS_NORMAL | ITEMIS_INGIT | ITEMIS_FOLDERINGIT)) && (itemStates & ITEMIS_ONLYONE) && ((~itemStates) & ITEMIS_ADDED))
808 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVERENAMEMENU, 0, idCmdFirst, ShellMenuDropMoveRename, uFlags);
809 moveAvailable = true;
812 // Git copy here
813 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
814 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&((~itemStates) & ITEMIS_ADDED))
815 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYMENU, 0, idCmdFirst, ShellMenuDropCopy, uFlags);
817 // Git copy and rename here, source and target from same repository
818 // available if source is a single, versioned but not added item, target is versioned or target folder is added
819 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
820 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYRENAMEMENU, 0, idCmdFirst, ShellMenuDropCopyRename, uFlags);
822 // Git add here
823 // available if target is versioned and source is either unversioned or from another repository
824 if ((itemStatesFolder & ITEMIS_FOLDERINGIT) && (((~itemStates) & ITEMIS_INGIT) || !bSourceAndTargetFromSameRepository) && !moveAvailable)
825 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYADDMENU, 0, idCmdFirst, ShellMenuDropCopyAdd, uFlags);
827 // Git export here
828 // available if source is versioned and a folder
829 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
830 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
832 // Git export all here
833 // available if source is versioned and a folder
834 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
835 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
837 // apply patch
838 // available if source is a patchfile
839 if (itemStates & ITEMIS_PATCHFILE)
841 if (itemStates & ITEMIS_ONLYONE)
842 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_MENUAPPLYPATCH, 0, idCmdFirst, ShellMenuApplyPatch, uFlags);
843 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_MENUIMPORTPATCH, 0, idCmdFirst, ShellMenuImportPatchDrop, uFlags);
846 // separator
847 if (idCmd != idCmdFirst)
848 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr);
850 TweakMenu(hMenu);
852 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
855 STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu,
856 UINT indexMenu,
857 UINT idCmdFirst,
858 UINT idCmdLast,
859 UINT uFlags)
861 __try
863 return QueryContextMenu_Wrap(hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
865 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
868 return E_FAIL;
871 STDMETHODIMP CShellExt::QueryContextMenu_Wrap(HMENU hMenu,
872 UINT indexMenu,
873 UINT idCmdFirst,
874 UINT /*idCmdLast*/,
875 UINT uFlags)
877 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Shell :: QueryContextMenu itemStates=%ld\n", itemStates);
878 PreserveChdir preserveChdir;
880 //first check if our drop handler is called
881 //and then (if true) provide the context menu for the
882 //drop handler
883 if (m_State == FileStateDropHandler)
885 return QueryDropContext(uFlags, idCmdFirst, hMenu, indexMenu);
888 if ((uFlags & CMF_DEFAULTONLY)!=0)
889 return S_OK; //we don't change the default action
891 if (files_.empty() && folder_.empty())
892 return S_OK;
894 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
895 return S_OK;
897 int csidlarray[] =
899 CSIDL_BITBUCKET,
900 CSIDL_CDBURN_AREA,
901 CSIDL_COMMON_FAVORITES,
902 CSIDL_COMMON_STARTMENU,
903 CSIDL_COMPUTERSNEARME,
904 CSIDL_CONNECTIONS,
905 CSIDL_CONTROLS,
906 CSIDL_COOKIES,
907 CSIDL_FAVORITES,
908 CSIDL_FONTS,
909 CSIDL_HISTORY,
910 CSIDL_INTERNET,
911 CSIDL_INTERNET_CACHE,
912 CSIDL_NETHOOD,
913 CSIDL_NETWORK,
914 CSIDL_PRINTERS,
915 CSIDL_PRINTHOOD,
916 CSIDL_RECENT,
917 CSIDL_SENDTO,
918 CSIDL_STARTMENU,
921 if (IsIllegalFolder(folder_, csidlarray))
922 return S_OK;
924 if (folder_.empty())
926 // folder is empty, but maybe files are selected
927 if (files_.empty())
928 return S_OK; // nothing selected - we don't have a menu to show
929 // check whether a selected entry is an UID - those are namespace extensions
930 // which we can't handle
931 for (const auto& file : files_)
933 if (_tcsncmp(file.c_str(), _T("::{"), 3)==0)
934 return S_OK;
937 else
939 if (_tcsncmp(folder_.c_str(), _T("::{"), 3) == 0)
940 return S_OK;
943 if (((uFlags & CMF_EXTENDEDVERBS) == 0) && g_ShellCache.HideMenusForUnversionedItems())
945 if ((itemStates & (ITEMIS_INGIT | ITEMIS_INVERSIONEDFOLDER | ITEMIS_FOLDERINGIT | ITEMIS_BAREREPO)) == 0)
946 return S_OK;
949 //check if our menu is requested for a git admin directory
950 if (GitAdminDir::IsAdminDirPath(folder_.c_str()))
951 return S_OK;
953 if (uFlags & CMF_EXTENDEDVERBS)
954 itemStates |= ITEMIS_EXTENDED;
956 regDiffLater.read();
957 if (!std::wstring(regDiffLater).empty())
958 itemStates |= ITEMIS_HASDIFFLATER;
960 const BOOL bShortcut = !!(uFlags & CMF_VERBSONLY);
961 if ( bShortcut && (files_.size()==1))
963 // Don't show the context menu for a link if the
964 // destination is not part of a working copy.
965 // It would only show the standard menu items
966 // which are already shown for the lnk-file.
967 CString path = files_.front().c_str();
968 if (!GitAdminDir::HasAdminDir(path))
970 return S_OK;
974 //check if we already added our menu entry for a folder.
975 //we check that by iterating through all menu entries and check if
976 //the dwItemData member points to our global ID string. That string is set
977 //by our shell extension when the folder menu is inserted.
978 TCHAR menubuf[MAX_PATH] = {0};
979 int count = GetMenuItemCount(hMenu);
980 for (int i=0; i<count; ++i)
982 MENUITEMINFO miif = { 0 };
983 miif.cbSize = sizeof(MENUITEMINFO);
984 miif.fMask = MIIM_DATA;
985 miif.dwTypeData = menubuf;
986 miif.cch = _countof(menubuf);
987 GetMenuItemInfo(hMenu, i, TRUE, &miif);
988 if (miif.dwItemData == (ULONG_PTR)g_MenuIDString)
989 return S_OK;
992 LoadLangDll();
993 UINT idCmd = idCmdFirst;
995 //create the sub menu
996 HMENU subMenu = CreateMenu();
997 int indexSubMenu = 0;
999 unsigned __int64 topmenu = g_ShellCache.GetMenuLayout();
1000 unsigned __int64 menumask = g_ShellCache.GetMenuMask();
1001 unsigned __int64 menuex = g_ShellCache.GetMenuExt();
1003 int menuIndex = 0;
1004 bool bAddSeparator = false;
1005 bool bMenuEntryAdded = false;
1006 bool bMenuEmpty = true;
1007 // insert separator at start
1008 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr); idCmd++;
1009 bool bShowIcons = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\ShowContextMenuIcons"), TRUE));
1011 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1013 if (menuInfo[menuIndex].command == ShellSeparator)
1015 // we don't add a separator immediately. Because there might not be
1016 // another 'normal' menu entry after we insert a separator.
1017 // we simply set a flag here, indicating that before the next
1018 // 'normal' menu entry, a separator should be added.
1019 if (!bMenuEmpty)
1020 bAddSeparator = true;
1021 if (bMenuEntryAdded)
1022 bAddSeparator = true;
1024 else
1026 // check the conditions whether to show the menu entry or not
1027 bool bInsertMenu = ShouldInsertItem(menuInfo[menuIndex]);
1028 if (menuInfo[menuIndex].menuID & menuex)
1030 if( !(itemStates & ITEMIS_EXTENDED) )
1031 bInsertMenu = false;
1034 if (menuInfo[menuIndex].menuID & (~menumask))
1036 if (bInsertMenu)
1038 bool bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1039 // insert a separator
1040 if ((bMenuEntryAdded)&&(bAddSeparator)&&(!bIsTop))
1042 bAddSeparator = false;
1043 bMenuEntryAdded = false;
1044 InsertMenu(subMenu, indexSubMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, nullptr);
1045 idCmd++;
1048 // handle special cases (sub menus)
1049 if ((menuInfo[menuIndex].command == ShellMenuIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuUnIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuDeleteIgnoreSub))
1051 if(InsertIgnoreSubmenus(idCmd, idCmdFirst, hMenu, subMenu, indexMenu, indexSubMenu, topmenu, bShowIcons, uFlags))
1052 bMenuEntryAdded = true;
1054 else
1056 bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1058 // insert the menu entry
1059 InsertGitMenu( bIsTop,
1060 bIsTop ? hMenu : subMenu,
1061 bIsTop ? indexMenu++ : indexSubMenu++,
1062 idCmd++,
1063 menuInfo[menuIndex].menuTextID,
1064 bShowIcons ? menuInfo[menuIndex].iconID : 0,
1065 idCmdFirst,
1066 menuInfo[menuIndex].command,
1067 uFlags);
1068 if (!bIsTop)
1070 bMenuEntryAdded = true;
1071 bMenuEmpty = false;
1072 bAddSeparator = false;
1078 menuIndex++;
1081 // do not show TortoiseGit menu if it's empty
1082 if (bMenuEmpty)
1084 if (idCmd - idCmdFirst > 0)
1086 //separator after
1087 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr); idCmd++;
1089 TweakMenu(hMenu);
1091 //return number of menu items added
1092 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
1095 //add sub menu to main context menu
1096 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1097 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1098 MAKESTRING(IDS_MENUSUBMENU);
1099 if (!g_ShellCache.HasShellMenuAccelerators())
1101 // remove the accelerators
1102 tstring temp = stringtablebuffer;
1103 temp.erase(std::remove(temp.begin(), temp.end(), '&'), temp.end());
1104 _tcscpy_s(stringtablebuffer, temp.c_str());
1106 MENUITEMINFO menuiteminfo = { 0 };
1107 menuiteminfo.cbSize = sizeof(menuiteminfo);
1108 menuiteminfo.fType = MFT_STRING;
1109 menuiteminfo.dwTypeData = stringtablebuffer;
1111 UINT uIcon = bShowIcons ? IDI_APP : 0;
1112 if (!folder_.empty())
1114 uIcon = bShowIcons ? IDI_MENUFOLDER : 0;
1115 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFolder;
1116 myIDMap[idCmd] = ShellSubMenuFolder;
1117 menuiteminfo.dwItemData = (ULONG_PTR)g_MenuIDString;
1119 else if (!bShortcut && (files_.size()==1))
1121 uIcon = bShowIcons ? IDI_MENUFILE : 0;
1122 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFile;
1123 myIDMap[idCmd] = ShellSubMenuFile;
1125 else if (bShortcut && (files_.size()==1))
1127 uIcon = bShowIcons ? IDI_MENULINK : 0;
1128 myIDMap[idCmd - idCmdFirst] = ShellSubMenuLink;
1129 myIDMap[idCmd] = ShellSubMenuLink;
1131 else if (!files_.empty())
1133 uIcon = bShowIcons ? IDI_MENUMULTIPLE : 0;
1134 myIDMap[idCmd - idCmdFirst] = ShellSubMenuMultiple;
1135 myIDMap[idCmd] = ShellSubMenuMultiple;
1137 else
1139 myIDMap[idCmd - idCmdFirst] = ShellSubMenu;
1140 myIDMap[idCmd] = ShellSubMenu;
1142 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
1143 if (uIcon)
1145 menuiteminfo.fMask |= MIIM_BITMAP;
1146 menuiteminfo.hbmpItem = m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, uIcon);
1148 menuiteminfo.hSubMenu = subMenu;
1149 menuiteminfo.wID = idCmd++;
1150 InsertMenuItem(hMenu, indexMenu++, TRUE, &menuiteminfo);
1152 //separator after
1153 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr); idCmd++;
1155 TweakMenu(hMenu);
1157 //return number of menu items added
1158 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
1161 void CShellExt::TweakMenu(HMENU hMenu)
1163 MENUINFO MenuInfo = {};
1164 MenuInfo.cbSize = sizeof(MenuInfo);
1165 MenuInfo.fMask = MIM_STYLE | MIM_APPLYTOSUBMENUS;
1166 MenuInfo.dwStyle = MNS_CHECKORBMP;
1167 SetMenuInfo(hMenu, &MenuInfo);
1170 void CShellExt::AddPathCommand(tstring& gitCmd, LPCTSTR command, bool bFilesAllowed)
1172 gitCmd += command;
1173 gitCmd += _T(" /path:\"");
1174 if ((bFilesAllowed) && !files_.empty())
1175 gitCmd += files_.front();
1176 else
1177 gitCmd += folder_;
1178 gitCmd += _T("\"");
1181 void CShellExt::AddPathFileCommand(tstring& gitCmd, LPCTSTR command)
1183 tstring tempfile = WriteFileListToTempFile();
1184 gitCmd += command;
1185 gitCmd += _T(" /pathfile:\"");
1186 gitCmd += tempfile;
1187 gitCmd += _T("\"");
1188 gitCmd += _T(" /deletepathfile");
1191 void CShellExt::AddPathFileDropCommand(tstring& gitCmd, LPCTSTR command)
1193 tstring tempfile = WriteFileListToTempFile();
1194 gitCmd += command;
1195 gitCmd += _T(" /pathfile:\"");
1196 gitCmd += tempfile;
1197 gitCmd += _T("\"");
1198 gitCmd += _T(" /deletepathfile");
1199 gitCmd += _T(" /droptarget:\"");
1200 gitCmd += folder_;
1201 gitCmd += _T("\"");
1204 STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
1206 __try
1208 return InvokeCommand_Wrap(lpcmi);
1210 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1213 return E_FAIL;
1216 // This is called when you invoke a command on the menu:
1217 STDMETHODIMP CShellExt::InvokeCommand_Wrap(LPCMINVOKECOMMANDINFO lpcmi)
1219 PreserveChdir preserveChdir;
1220 HRESULT hr = E_INVALIDARG;
1221 if (!lpcmi)
1222 return hr;
1224 if (!files_.empty() || !folder_.empty())
1226 UINT_PTR idCmd = LOWORD(lpcmi->lpVerb);
1228 if (HIWORD(lpcmi->lpVerb))
1230 tstring verb = tstring(MultibyteToWide(lpcmi->lpVerb));
1231 std::map<tstring, UINT_PTR>::const_iterator verb_it = myVerbsMap.lower_bound(verb);
1232 if (verb_it != myVerbsMap.end() && verb_it->first == verb)
1233 idCmd = verb_it->second;
1234 else
1235 return hr;
1238 // See if we have a handler interface for this id
1239 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
1240 if (id_it != myIDMap.end() && id_it->first == idCmd)
1242 tstring tortoiseProcPath(CPathUtils::GetAppDirectory(g_hmodThisDll) + _T("TortoiseGitProc.exe"));
1243 tstring tortoiseMergePath(CPathUtils::GetAppDirectory(g_hmodThisDll) + _T("TortoiseGitMerge.exe"));
1245 //TortoiseGitProc expects a command line of the form:
1246 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1247 // or
1248 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1250 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1251 //* pathfile is a path to a temporary file which contains a list of file paths
1252 tstring gitCmd = _T(" /command:");
1253 tstring tempfile;
1254 switch (id_it->second)
1256 //#region case
1257 case ShellMenuSync:
1259 TCHAR syncSeq[12] = { 0 };
1260 _stprintf_s(syncSeq, _T("%d"), g_syncSeq++);
1261 AddPathCommand(gitCmd, L"sync", false);
1262 gitCmd += _T(" /seq:");
1263 gitCmd += syncSeq;
1265 break;
1266 case ShellMenuSubSync:
1267 AddPathFileCommand(gitCmd, L"subsync");
1268 if (itemStatesFolder & ITEMIS_SUBMODULECONTAINER || (itemStates & ITEMIS_SUBMODULECONTAINER && itemStates & ITEMIS_WCROOT && itemStates & ITEMIS_ONLYONE))
1270 gitCmd += _T(" /bkpath:\"");
1271 gitCmd += folder_;
1272 gitCmd += _T("\"");
1274 break;
1275 case ShellMenuUpdateExt:
1276 AddPathFileCommand(gitCmd, L"subupdate");
1277 if (itemStatesFolder & ITEMIS_SUBMODULECONTAINER || (itemStates & ITEMIS_SUBMODULECONTAINER && itemStates & ITEMIS_WCROOT && itemStates & ITEMIS_ONLYONE))
1279 gitCmd += _T(" /bkpath:\"");
1280 gitCmd += folder_;
1281 gitCmd += _T("\"");
1283 break;
1284 case ShellMenuCommit:
1285 AddPathFileCommand(gitCmd, L"commit");
1286 break;
1287 case ShellMenuAdd:
1288 AddPathFileCommand(gitCmd, L"add");
1289 break;
1290 case ShellMenuIgnore:
1291 AddPathFileCommand(gitCmd, L"ignore");
1292 break;
1293 case ShellMenuIgnoreCaseSensitive:
1294 AddPathFileCommand(gitCmd, L"ignore");
1295 gitCmd += _T(" /onlymask");
1296 break;
1297 case ShellMenuDeleteIgnore:
1298 AddPathFileCommand(gitCmd, L"ignore");
1299 gitCmd += _T(" /delete");
1300 break;
1301 case ShellMenuDeleteIgnoreCaseSensitive:
1302 AddPathFileCommand(gitCmd, L"ignore");
1303 gitCmd += _T(" /delete /onlymask");
1304 break;
1305 case ShellMenuUnIgnore:
1306 AddPathFileCommand(gitCmd, L"unignore");
1307 break;
1308 case ShellMenuUnIgnoreCaseSensitive:
1309 AddPathFileCommand(gitCmd, L"unignore");
1310 gitCmd += _T(" /onlymask");
1311 break;
1312 case ShellMenuMergeAbort:
1313 AddPathCommand(gitCmd, L"merge", false);
1314 gitCmd += _T(" /abort");
1315 break;
1316 case ShellMenuRevert:
1317 AddPathFileCommand(gitCmd, L"revert");
1318 break;
1319 case ShellMenuCleanup:
1320 AddPathFileCommand(gitCmd, L"cleanup");
1321 break;
1322 case ShellMenuSendMail:
1323 AddPathFileCommand(gitCmd, L"sendmail");
1324 break;
1325 case ShellMenuResolve:
1326 AddPathFileCommand(gitCmd, L"resolve");
1327 break;
1328 case ShellMenuSwitch:
1329 AddPathCommand(gitCmd, L"switch", false);
1330 break;
1331 case ShellMenuExport:
1332 AddPathCommand(gitCmd, L"export", false);
1333 break;
1334 case ShellMenuAbout:
1335 gitCmd += _T("about");
1336 break;
1337 case ShellMenuCreateRepos:
1338 AddPathCommand(gitCmd, L"repocreate", false);
1339 break;
1340 case ShellMenuMerge:
1341 AddPathCommand(gitCmd, L"merge", false);
1342 break;
1343 case ShellMenuCopy:
1344 AddPathCommand(gitCmd, L"copy", true);
1345 break;
1346 case ShellMenuSettings:
1347 AddPathCommand(gitCmd, L"settings", true);
1348 break;
1349 case ShellMenuHelp:
1350 gitCmd += _T("help");
1351 break;
1352 case ShellMenuRename:
1353 AddPathCommand(gitCmd, L"rename", true);
1354 break;
1355 case ShellMenuRemove:
1356 AddPathFileCommand(gitCmd, L"remove");
1357 break;
1358 case ShellMenuRemoveKeep:
1359 AddPathFileCommand(gitCmd, L"remove");
1360 gitCmd += _T(" /keep");
1361 break;
1362 case ShellMenuDiff:
1363 gitCmd += _T("diff /path:\"");
1364 if (files_.size() == 1)
1365 gitCmd += files_.front();
1366 else if (files_.size() == 2)
1368 auto I = files_.cbegin();
1369 gitCmd += *I;
1370 ++I;
1371 gitCmd += _T("\" /path2:\"");
1372 gitCmd += *I;
1374 else
1375 gitCmd += folder_;
1376 gitCmd += _T("\"");
1377 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1378 gitCmd += _T(" /alternative");
1379 break;
1380 case ShellMenuDiffLater:
1381 if (lpcmi->fMask & CMIC_MASK_CONTROL_DOWN)
1383 gitCmd.clear();
1384 regDiffLater.removeValue();
1386 else if (files_.size() == 1)
1388 if (std::wstring(regDiffLater).empty())
1390 gitCmd.clear();
1391 regDiffLater = files_[0];
1393 else
1395 AddPathCommand(gitCmd, L"diff", true);
1396 gitCmd += _T(" /path2:\"");
1397 gitCmd += std::wstring(regDiffLater);
1398 gitCmd += _T("\"");
1399 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1400 gitCmd += _T(" /alternative");
1401 regDiffLater.removeValue();
1404 else
1405 gitCmd.clear();
1406 break;
1407 case ShellMenuPrevDiff:
1408 AddPathCommand(gitCmd, L"prevdiff", true);
1409 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1410 gitCmd += _T(" /alternative");
1411 break;
1412 case ShellMenuDiffTwo:
1413 AddPathCommand(gitCmd, L"diffcommits", true);
1414 break;
1415 case ShellMenuDropCopyAdd:
1416 AddPathFileDropCommand(gitCmd, L"dropcopyadd");
1417 break;
1418 case ShellMenuDropCopy:
1419 AddPathFileDropCommand(gitCmd, L"dropcopy");
1420 break;
1421 case ShellMenuDropCopyRename:
1422 AddPathFileDropCommand(gitCmd, L"dropcopy");
1423 gitCmd += _T("\" /rename";)
1424 break;
1425 case ShellMenuDropMove:
1426 AddPathFileDropCommand(gitCmd, L"dropmove");
1427 break;
1428 case ShellMenuDropMoveRename:
1429 AddPathFileDropCommand(gitCmd, L"dropmove");
1430 gitCmd += _T("\" /rename";)
1431 break;
1432 case ShellMenuDropExport:
1433 AddPathFileDropCommand(gitCmd, L"dropexport");
1434 break;
1435 case ShellMenuDropExportExtended:
1436 AddPathFileDropCommand(gitCmd, L"dropexport");
1437 gitCmd += _T(" /extended");
1438 break;
1439 case ShellMenuLog:
1440 case ShellMenuLogSubmoduleFolder:
1441 AddPathCommand(gitCmd, L"log", true);
1442 if (id_it->second == ShellMenuLogSubmoduleFolder)
1443 gitCmd += _T(" /submodule");
1444 break;
1445 case ShellMenuDaemon:
1446 AddPathCommand(gitCmd, L"daemon", true);
1447 break;
1448 case ShellMenuRevisionGraph:
1449 AddPathCommand(gitCmd, L"revisiongraph", true);
1450 break;
1451 case ShellMenuConflictEditor:
1452 AddPathCommand(gitCmd, L"conflicteditor", true);
1453 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1454 gitCmd += L" /alternative";
1455 break;
1456 case ShellMenuGitSVNRebase:
1457 AddPathCommand(gitCmd, L"svnrebase", false);
1458 break;
1459 case ShellMenuGitSVNDCommit:
1460 AddPathCommand(gitCmd, L"svndcommit", true);
1461 break;
1462 case ShellMenuGitSVNDFetch:
1463 AddPathCommand(gitCmd, L"svnfetch", false);
1464 break;
1465 case ShellMenuGitSVNIgnore:
1466 AddPathCommand(gitCmd, L"svnignore", false);
1467 break;
1468 case ShellMenuRebase:
1469 AddPathCommand(gitCmd, L"rebase", false);
1470 break;
1471 case ShellMenuShowChanged:
1472 if (files_.size() > 1)
1473 AddPathFileCommand(gitCmd, L"repostatus");
1474 else
1475 AddPathCommand(gitCmd, L"repostatus", true);
1476 break;
1477 case ShellMenuRepoBrowse:
1478 AddPathCommand(gitCmd, L"repobrowser", false);
1479 break;
1480 case ShellMenuRefBrowse:
1481 AddPathCommand(gitCmd, L"refbrowse", false);
1482 break;
1483 case ShellMenuRefLog:
1484 AddPathCommand(gitCmd, L"reflog", false);
1485 break;
1486 case ShellMenuStashSave:
1487 AddPathCommand(gitCmd, L"stashsave", true);
1488 break;
1489 case ShellMenuStashApply:
1490 AddPathCommand(gitCmd, L"stashapply", false);
1491 break;
1492 case ShellMenuStashPop:
1493 AddPathCommand(gitCmd, L"stashpop", false);
1494 break;
1495 case ShellMenuStashList:
1496 AddPathCommand(gitCmd, L"reflog", false);
1497 gitCmd += _T(" /ref:refs/stash");
1498 break;
1499 case ShellMenuBisectStart:
1500 AddPathCommand(gitCmd, L"bisect", false);
1501 gitCmd += _T("\" /start");
1502 break;
1503 case ShellMenuBisectGood:
1504 AddPathCommand(gitCmd, L"bisect", false);
1505 gitCmd += _T("\" /good");
1506 break;
1507 case ShellMenuBisectBad:
1508 AddPathCommand(gitCmd, L"bisect", false);
1509 gitCmd += _T("\" /bad");
1510 break;
1511 case ShellMenuBisectReset:
1512 AddPathCommand(gitCmd, L"bisect", false);
1513 gitCmd += _T("\" /reset");
1514 break;
1515 case ShellMenuSubAdd:
1516 AddPathCommand(gitCmd, L"subadd", false);
1517 break;
1518 case ShellMenuBlame:
1519 AddPathCommand(gitCmd, L"blame", true);
1520 break;
1521 case ShellMenuApplyPatch:
1522 if ((itemStates & ITEMIS_PATCHINCLIPBOARD) && ((~itemStates) & ITEMIS_PATCHFILE))
1524 // if there's a patch file in the clipboard, we save it
1525 // to a temporary file and tell TortoiseGitMerge to use that one
1526 UINT cFormat = RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
1527 if (cFormat && OpenClipboard(nullptr))
1529 HGLOBAL hglb = GetClipboardData(cFormat);
1530 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
1532 DWORD len = GetTortoiseGitTempPath(0, nullptr);
1533 auto path = std::make_unique<TCHAR[]>(len + 1);
1534 auto tempF = std::make_unique<TCHAR[]>(len + 100);
1535 GetTortoiseGitTempPath(len + 1, path.get());
1536 GetTempFileName(path.get(), TEXT("git"), 0, tempF.get());
1537 std::wstring sTempFile = std::wstring(tempF.get());
1539 FILE * outFile;
1540 size_t patchlen = strlen(lpstr);
1541 _tfopen_s(&outFile, sTempFile.c_str(), _T("wb"));
1542 if(outFile)
1544 size_t size = fwrite(lpstr, sizeof(char), patchlen, outFile);
1545 if (size == patchlen)
1547 itemStates |= ITEMIS_PATCHFILE;
1548 files_.clear();
1549 files_.push_back(sTempFile);
1551 fclose(outFile);
1553 GlobalUnlock(hglb);
1554 CloseClipboard();
1557 if (itemStates & ITEMIS_PATCHFILE)
1559 gitCmd = _T(" /diff:\"");
1560 if (!files_.empty())
1562 gitCmd += files_.front();
1563 if (itemStatesFolder & ITEMIS_FOLDERINGIT)
1565 gitCmd += _T("\" /patchpath:\"");
1566 gitCmd += folder_;
1569 else
1570 gitCmd += folder_;
1571 if (itemStates & ITEMIS_INVERSIONEDFOLDER)
1572 gitCmd += _T("\" /wc");
1573 else
1574 gitCmd += _T("\"");
1576 else
1578 gitCmd = _T(" /patchpath:\"");
1579 if (!files_.empty())
1580 gitCmd += files_.front();
1581 else
1582 gitCmd += folder_;
1583 gitCmd += _T("\"");
1585 myIDMap.clear();
1586 myVerbsIDMap.clear();
1587 myVerbsMap.clear();
1588 RunCommand(tortoiseMergePath, gitCmd, _T("TortoiseGitMerge launch failed"));
1589 return S_OK;
1590 break;
1591 case ShellMenuClipPaste:
1592 if (WriteClipboardPathsToTempFile(tempfile))
1594 bool bCopy = true;
1595 UINT cPrefDropFormat = RegisterClipboardFormat(_T("Preferred DropEffect"));
1596 if (cPrefDropFormat)
1598 if (OpenClipboard(lpcmi->hwnd))
1600 HGLOBAL hglb = GetClipboardData(cPrefDropFormat);
1601 if (hglb)
1603 DWORD* effect = (DWORD*) GlobalLock(hglb);
1604 if (*effect == DROPEFFECT_MOVE)
1605 bCopy = false;
1606 GlobalUnlock(hglb);
1608 CloseClipboard();
1612 if (bCopy)
1613 gitCmd += _T("pastecopy /pathfile:\"");
1614 else
1615 gitCmd += _T("pastemove /pathfile:\"");
1616 gitCmd += tempfile;
1617 gitCmd += _T("\"");
1618 gitCmd += _T(" /deletepathfile");
1619 gitCmd += _T(" /droptarget:\"");
1620 gitCmd += folder_;
1621 gitCmd += _T("\"");
1623 else return S_OK;
1624 break;
1625 case ShellMenuClone:
1626 AddPathCommand(gitCmd, L"clone", false);
1627 break;
1628 case ShellMenuPull:
1629 AddPathCommand(gitCmd, L"pull", false);
1630 break;
1631 case ShellMenuPush:
1632 AddPathCommand(gitCmd, L"push", false);
1633 break;
1634 case ShellMenuBranch:
1635 AddPathCommand(gitCmd, L"branch", false);
1636 break;
1637 case ShellMenuTag:
1638 AddPathCommand(gitCmd, L"tag", false);
1639 break;
1640 case ShellMenuFormatPatch:
1641 AddPathCommand(gitCmd, L"formatpatch", false);
1642 break;
1643 case ShellMenuImportPatch:
1644 AddPathFileCommand(gitCmd, L"importpatch");
1645 break;
1646 case ShellMenuImportPatchDrop:
1647 AddPathFileDropCommand(gitCmd, L"importpatch");
1648 break;
1649 case ShellMenuFetch:
1650 AddPathCommand(gitCmd, L"fetch", false);
1651 break;
1653 default:
1654 break;
1655 //#endregion
1656 } // switch (id_it->second)
1657 if (!gitCmd.empty())
1659 gitCmd += _T(" /hwnd:");
1660 TCHAR buf[30] = { 0 };
1661 _stprintf_s(buf, _T("%p"), (void*)lpcmi->hwnd);
1662 gitCmd += buf;
1663 myIDMap.clear();
1664 myVerbsIDMap.clear();
1665 myVerbsMap.clear();
1666 RunCommand(tortoiseProcPath, gitCmd, _T("TortoiseProc launch failed"));
1668 hr = S_OK;
1669 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1670 } // if (files_.empty() || folder_.empty())
1671 return hr;
1674 // This is for the status bar and things like that:
1675 STDMETHODIMP CShellExt::GetCommandString(UINT_PTR idCmd,
1676 UINT uFlags,
1677 UINT FAR * reserved,
1678 LPSTR pszName,
1679 UINT cchMax)
1681 __try
1683 return GetCommandString_Wrap(idCmd, uFlags, reserved, pszName, cchMax);
1685 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1688 return E_FAIL;
1691 // This is for the status bar and things like that:
1692 STDMETHODIMP CShellExt::GetCommandString_Wrap(UINT_PTR idCmd,
1693 UINT uFlags,
1694 UINT FAR * /*reserved*/,
1695 LPSTR pszName,
1696 UINT cchMax)
1698 PreserveChdir preserveChdir;
1699 //do we know the id?
1700 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
1701 if (id_it == myIDMap.end() || id_it->first != idCmd)
1703 return E_INVALIDARG; //no, we don't
1706 LoadLangDll();
1707 HRESULT hr = E_INVALIDARG;
1709 MAKESTRING(IDS_MENUDESCDEFAULT);
1710 int menuIndex = 0;
1711 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1713 if (menuInfo[menuIndex].command == (GitCommands)id_it->second)
1715 MAKESTRING(menuInfo[menuIndex].menuDescID);
1716 break;
1718 menuIndex++;
1721 const TCHAR * desc = stringtablebuffer;
1722 switch(uFlags)
1724 case GCS_HELPTEXTA:
1726 std::string help = WideToMultibyte(desc);
1727 lstrcpynA(pszName, help.c_str(), cchMax - 1);
1728 hr = S_OK;
1729 break;
1731 case GCS_HELPTEXTW:
1733 std::wstring help = desc;
1734 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax - 1);
1735 hr = S_OK;
1736 break;
1738 case GCS_VERBA:
1740 std::map<UINT_PTR, tstring>::const_iterator verb_id_it = myVerbsIDMap.lower_bound(idCmd);
1741 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
1743 std::string help = WideToMultibyte(verb_id_it->second);
1744 lstrcpynA(pszName, help.c_str(), cchMax - 1);
1745 hr = S_OK;
1748 break;
1749 case GCS_VERBW:
1751 std::map<UINT_PTR, tstring>::const_iterator verb_id_it = myVerbsIDMap.lower_bound(idCmd);
1752 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
1754 std::wstring help = verb_id_it->second;
1755 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": verb : %ws\n", help.c_str());
1756 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax - 1);
1757 hr = S_OK;
1760 break;
1762 return hr;
1765 STDMETHODIMP CShellExt::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
1767 __try
1769 return HandleMenuMsg_Wrap(uMsg, wParam, lParam);
1771 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1774 return E_FAIL;
1777 STDMETHODIMP CShellExt::HandleMenuMsg_Wrap(UINT uMsg, WPARAM wParam, LPARAM lParam)
1779 LRESULT res;
1780 return HandleMenuMsg2(uMsg, wParam, lParam, &res);
1783 STDMETHODIMP CShellExt::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pResult)
1785 __try
1787 return HandleMenuMsg2_Wrap(uMsg, wParam, lParam, pResult);
1789 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1792 return E_FAIL;
1795 STDMETHODIMP CShellExt::HandleMenuMsg2_Wrap(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pResult)
1797 PreserveChdir preserveChdir;
1799 LRESULT res;
1800 if (!pResult)
1801 pResult = &res;
1802 *pResult = FALSE;
1804 LoadLangDll();
1805 switch (uMsg)
1807 case WM_MEASUREITEM:
1809 MEASUREITEMSTRUCT* lpmis = (MEASUREITEMSTRUCT*)lParam;
1810 if (!lpmis)
1811 break;
1812 lpmis->itemWidth = 16;
1813 lpmis->itemHeight = 16;
1814 *pResult = TRUE;
1816 break;
1817 case WM_DRAWITEM:
1819 LPCTSTR resource;
1820 DRAWITEMSTRUCT* lpdis = (DRAWITEMSTRUCT*)lParam;
1821 if (!lpdis || lpdis->CtlType != ODT_MENU)
1822 return S_OK; //not for a menu
1823 resource = GetMenuTextFromResource((int)myIDMap[lpdis->itemID]);
1824 if (!resource)
1825 return S_OK;
1826 HICON hIcon = (HICON)LoadImage(g_hResInst, resource, IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
1827 if (!hIcon)
1828 return S_OK;
1829 DrawIconEx(lpdis->hDC,
1830 lpdis->rcItem.left,
1831 lpdis->rcItem.top + (lpdis->rcItem.bottom - lpdis->rcItem.top - 16) / 2,
1832 hIcon, 16, 16,
1833 0, nullptr, DI_NORMAL);
1834 DestroyIcon(hIcon);
1835 *pResult = TRUE;
1837 break;
1838 case WM_MENUCHAR:
1840 TCHAR *szItem;
1841 if (HIWORD(wParam) != MF_POPUP)
1842 return S_OK;
1843 int nChar = LOWORD(wParam);
1844 if (_istascii((wint_t)nChar) && _istupper((wint_t)nChar))
1845 nChar = tolower(nChar);
1846 // we have the char the user pressed, now search that char in all our
1847 // menu items
1848 std::vector<UINT_PTR> accmenus;
1849 for (auto It = mySubMenuMap.cbegin(); It != mySubMenuMap.cend(); ++It)
1851 LPCTSTR resource = GetMenuTextFromResource((int)mySubMenuMap[It->first]);
1852 if (!resource)
1853 continue;
1854 szItem = stringtablebuffer;
1855 TCHAR * amp = _tcschr(szItem, '&');
1856 if (!amp)
1857 continue;
1858 amp++;
1859 int ampChar = LOWORD(*amp);
1860 if (_istascii((wint_t)ampChar) && _istupper((wint_t)ampChar))
1861 ampChar = tolower(ampChar);
1862 if (ampChar == nChar)
1864 // yep, we found a menu which has the pressed key
1865 // as an accelerator. Add that menu to the list to
1866 // process later.
1867 accmenus.push_back(It->first);
1870 if (accmenus.empty())
1872 // no menu with that accelerator key.
1873 *pResult = MAKELONG(0, MNC_IGNORE);
1874 return S_OK;
1876 if (accmenus.size() == 1)
1878 // Only one menu with that accelerator key. We're lucky!
1879 // So just execute that menu entry.
1880 *pResult = MAKELONG(accmenus[0], MNC_EXECUTE);
1881 return S_OK;
1883 if (accmenus.size() > 1)
1885 // we have more than one menu item with this accelerator key!
1886 MENUITEMINFO mif;
1887 mif.cbSize = sizeof(MENUITEMINFO);
1888 mif.fMask = MIIM_STATE;
1889 for (auto it = accmenus.cbegin(); it != accmenus.cend(); ++it)
1891 GetMenuItemInfo((HMENU)lParam, (UINT)*it, TRUE, &mif);
1892 if (mif.fState == MFS_HILITE)
1894 // this is the selected item, so select the next one
1895 ++it;
1896 if (it == accmenus.end())
1897 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
1898 else
1899 *pResult = MAKELONG(*it, MNC_SELECT);
1900 return S_OK;
1903 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
1906 break;
1907 default:
1908 return S_OK;
1911 return S_OK;
1914 LPCTSTR CShellExt::GetMenuTextFromResource(int id)
1916 TCHAR textbuf[255] = { 0 };
1917 LPCTSTR resource = nullptr;
1918 unsigned __int64 layout = g_ShellCache.GetMenuLayout();
1919 space = 6;
1921 int menuIndex = 0;
1922 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1924 if (menuInfo[menuIndex].command == id)
1926 MAKESTRING(menuInfo[menuIndex].menuTextID);
1927 resource = MAKEINTRESOURCE(menuInfo[menuIndex].iconID);
1928 switch (id)
1930 case ShellSubMenuMultiple:
1931 case ShellSubMenuLink:
1932 case ShellSubMenuFolder:
1933 case ShellSubMenuFile:
1934 case ShellSubMenu:
1935 space = 0;
1936 break;
1937 default:
1938 space = (layout & menuInfo[menuIndex].menuID) ? 0 : 6;
1939 if (layout & menuInfo[menuIndex].menuID)
1941 _tcscpy_s(textbuf, 255, _T("Git "));
1942 _tcscat_s(textbuf, 255, stringtablebuffer);
1943 _tcscpy_s(stringtablebuffer, 255, textbuf);
1945 break;
1947 return resource;
1949 menuIndex++;
1951 return nullptr;
1954 bool CShellExt::IsIllegalFolder(std::wstring folder, int * cslidarray)
1956 int i=0;
1957 TCHAR buf[MAX_PATH] = {0}; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
1958 LPITEMIDLIST pidl = nullptr;
1959 while (cslidarray[i])
1961 ++i;
1962 pidl = nullptr;
1963 if (SHGetFolderLocation(nullptr, cslidarray[i - 1], nullptr, 0, &pidl) != S_OK)
1964 continue;
1965 if (!SHGetPathFromIDList(pidl, buf))
1967 // not a file system path, definitely illegal for our use
1968 CoTaskMemFree(pidl);
1969 continue;
1971 CoTaskMemFree(pidl);
1972 if (_tcslen(buf)==0)
1973 continue;
1974 if (_tcscmp(buf, folder.c_str())==0)
1975 return true;
1977 return false;
1980 bool CShellExt::InsertIgnoreSubmenus(UINT &idCmd, UINT idCmdFirst, HMENU hMenu, HMENU subMenu, UINT &indexMenu, int &indexSubMenu, unsigned __int64 topmenu, bool bShowIcons, UINT /*uFlags*/)
1982 HMENU ignoresubmenu = nullptr;
1983 int indexignoresub = 0;
1984 bool bShowIgnoreMenu = false;
1985 TCHAR maskbuf[MAX_PATH] = {0}; // MAX_PATH is ok, since this only holds a filename
1986 TCHAR ignorepath[MAX_PATH] = {0}; // MAX_PATH is ok, since this only holds a filename
1987 if (files_.empty())
1988 return false;
1989 UINT icon = bShowIcons ? IDI_IGNORE : 0;
1991 auto I = files_.cbegin();
1992 if (_tcsrchr(I->c_str(), '\\'))
1993 _tcscpy_s(ignorepath, MAX_PATH, _tcsrchr(I->c_str(), '\\')+1);
1994 else
1995 _tcscpy_s(ignorepath, MAX_PATH, I->c_str());
1996 if ((itemStates & ITEMIS_IGNORED) && (!ignoredprops.empty()))
1998 // check if the item name is ignored or the mask
1999 size_t p = 0;
2000 while ( (p=ignoredprops.find( ignorepath,p )) != -1 )
2002 if ( (p==0 || ignoredprops[p-1]==TCHAR('\n'))
2003 && (p+_tcslen(ignorepath)==ignoredprops.length() || ignoredprops[p+_tcslen(ignorepath)+1]==TCHAR('\n')) )
2005 break;
2007 p++;
2009 if (p!=-1)
2011 ignoresubmenu = CreateMenu();
2012 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2013 tstring verb = tstring(ignorepath);
2014 myVerbsMap[verb] = idCmd - idCmdFirst;
2015 myVerbsMap[verb] = idCmd;
2016 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2017 myVerbsIDMap[idCmd] = verb;
2018 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnore;
2019 myIDMap[idCmd++] = ShellMenuUnIgnore;
2020 bShowIgnoreMenu = true;
2022 _tcscpy_s(maskbuf, MAX_PATH, _T("*"));
2023 if (_tcsrchr(ignorepath, '.'))
2025 _tcscat_s(maskbuf, MAX_PATH, _tcsrchr(ignorepath, '.'));
2026 p = ignoredprops.find(maskbuf);
2027 if ((p!=-1) &&
2028 ((ignoredprops.compare(maskbuf)==0) || (ignoredprops.find('\n', p)==p+_tcslen(maskbuf)+1) || (ignoredprops.rfind('\n', p)==p-1)))
2030 if (!ignoresubmenu)
2031 ignoresubmenu = CreateMenu();
2033 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2034 tstring verb = tstring(maskbuf);
2035 myVerbsMap[verb] = idCmd - idCmdFirst;
2036 myVerbsMap[verb] = idCmd;
2037 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2038 myVerbsIDMap[idCmd] = verb;
2039 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreCaseSensitive;
2040 myIDMap[idCmd++] = ShellMenuUnIgnoreCaseSensitive;
2041 bShowIgnoreMenu = true;
2045 else if ((itemStates & ITEMIS_IGNORED) == 0)
2047 bShowIgnoreMenu = true;
2048 ignoresubmenu = CreateMenu();
2049 if (itemStates & ITEMIS_ONLYONE)
2051 if (itemStates & ITEMIS_INGIT)
2053 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2054 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
2055 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
2057 _tcscpy_s(maskbuf, MAX_PATH, _T("*"));
2058 if (!(itemStates & ITEMIS_FOLDER) && _tcsrchr(ignorepath, '.'))
2060 _tcscat_s(maskbuf, MAX_PATH, _tcsrchr(ignorepath, '.'));
2061 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2062 tstring verb = tstring(maskbuf);
2063 myVerbsMap[verb] = idCmd - idCmdFirst;
2064 myVerbsMap[verb] = idCmd;
2065 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2066 myVerbsIDMap[idCmd] = verb;
2067 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
2068 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
2071 else
2073 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2074 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
2075 myIDMap[idCmd++] = ShellMenuIgnore;
2077 _tcscpy_s(maskbuf, MAX_PATH, _T("*"));
2078 if (!(itemStates & ITEMIS_FOLDER) && _tcsrchr(ignorepath, '.'))
2080 _tcscat_s(maskbuf, MAX_PATH, _tcsrchr(ignorepath, '.'));
2081 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2082 tstring verb = tstring(maskbuf);
2083 myVerbsMap[verb] = idCmd - idCmdFirst;
2084 myVerbsMap[verb] = idCmd;
2085 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2086 myVerbsIDMap[idCmd] = verb;
2087 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2088 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2092 else
2094 // note: as of Windows 7, the shell does not pass more than 16 items from a multiselection
2095 // in the Initialize() call before the QueryContextMenu() call. Which means even if the user
2096 // has selected more than 16 files, we won't know about that here.
2097 // Note: after QueryContextMenu() exits, Initialize() is called again with all selected files.
2098 if (itemStates & ITEMIS_INGIT)
2100 if (files_.size() >= 16)
2102 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE2);
2103 wcscpy_s(ignorepath, stringtablebuffer);
2105 else
2107 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE);
2108 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2110 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2111 tstring verb = tstring(ignorepath);
2112 myVerbsMap[verb] = idCmd - idCmdFirst;
2113 myVerbsMap[verb] = idCmd;
2114 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2115 myVerbsIDMap[idCmd] = verb;
2116 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
2117 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
2119 if (files_.size() >= 16)
2121 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK2);
2122 wcscpy_s(ignorepath, stringtablebuffer);
2124 else
2126 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK);
2127 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2129 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2130 verb = tstring(ignorepath);
2131 myVerbsMap[verb] = idCmd - idCmdFirst;
2132 myVerbsMap[verb] = idCmd;
2133 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2134 myVerbsIDMap[idCmd] = verb;
2135 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
2136 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
2138 else
2140 if (files_.size() >= 16)
2142 MAKESTRING(IDS_MENUIGNOREMULTIPLE2);
2143 wcscpy_s(ignorepath, stringtablebuffer);
2145 else
2147 MAKESTRING(IDS_MENUIGNOREMULTIPLE);
2148 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2150 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2151 tstring verb = tstring(ignorepath);
2152 myVerbsMap[verb] = idCmd - idCmdFirst;
2153 myVerbsMap[verb] = idCmd;
2154 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2155 myVerbsIDMap[idCmd] = verb;
2156 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
2157 myIDMap[idCmd++] = ShellMenuIgnore;
2159 if (files_.size() >= 16)
2161 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK2);
2162 wcscpy_s(ignorepath, stringtablebuffer);
2164 else
2166 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK);
2167 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2169 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2170 verb = tstring(ignorepath);
2171 myVerbsMap[verb] = idCmd - idCmdFirst;
2172 myVerbsMap[verb] = idCmd;
2173 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2174 myVerbsIDMap[idCmd] = verb;
2175 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2176 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2181 if (bShowIgnoreMenu)
2183 MENUITEMINFO menuiteminfo = { 0 };
2184 menuiteminfo.cbSize = sizeof(menuiteminfo);
2185 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
2186 if (icon)
2188 menuiteminfo.fMask |= MIIM_BITMAP;
2189 menuiteminfo.hbmpItem = m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, icon);
2191 menuiteminfo.fType = MFT_STRING;
2192 menuiteminfo.hSubMenu = ignoresubmenu;
2193 menuiteminfo.wID = idCmd;
2194 SecureZeroMemory(stringtablebuffer, sizeof(stringtablebuffer));
2195 if (itemStates & ITEMIS_IGNORED)
2196 GetMenuTextFromResource(ShellMenuUnIgnoreSub);
2197 else if (itemStates & ITEMIS_INGIT)
2198 GetMenuTextFromResource(ShellMenuDeleteIgnoreSub);
2199 else
2200 GetMenuTextFromResource(ShellMenuIgnoreSub);
2201 menuiteminfo.dwTypeData = stringtablebuffer;
2202 menuiteminfo.cch = (UINT)min(_tcslen(menuiteminfo.dwTypeData), UINT_MAX);
2204 InsertMenuItem((topmenu & MENUIGNORE) ? hMenu : subMenu, (topmenu & MENUIGNORE) ? indexMenu++ : indexSubMenu++, TRUE, &menuiteminfo);
2205 if (itemStates & ITEMIS_IGNORED)
2207 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreSub;
2208 myIDMap[idCmd++] = ShellMenuUnIgnoreSub;
2210 else
2212 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreSub;
2213 myIDMap[idCmd++] = ShellMenuIgnoreSub;
2216 return bShowIgnoreMenu;
2219 void CShellExt::RunCommand(const tstring& path, const tstring& command, LPCTSTR errorMessage)
2221 if (CCreateProcessHelper::CreateProcessDetached(path.c_str(), const_cast<TCHAR*>(command.c_str())))
2223 // process started - exit
2224 return;
2227 MessageBox(nullptr, CFormatMessageWrapper(), errorMessage, MB_OK | MB_ICONERROR);
2230 bool CShellExt::ShouldInsertItem(const MenuInfo& item) const
2232 return ShouldEnableMenu(item.first) || ShouldEnableMenu(item.second) ||
2233 ShouldEnableMenu(item.third) || ShouldEnableMenu(item.fourth);
2236 bool CShellExt::ShouldEnableMenu(const YesNoPair& pair) const
2238 if (pair.yes && pair.no)
2240 if (((pair.yes & itemStates) == pair.yes) && ((pair.no & (~itemStates)) == pair.no))
2241 return true;
2243 else if ((pair.yes) && ((pair.yes & itemStates) == pair.yes))
2244 return true;
2245 else if ((pair.no) && ((pair.no & (~itemStates)) == pair.no))
2246 return true;
2247 return false;