Handle versioncheck.tortoisegit.org as official source
[TortoiseGit.git] / src / TortoiseShell / ContextMenu.cpp
blob2dc7ddaccbba1b4a338c3d45362089f6453d2567
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012, 2014-2015 - TortoiseSVN
4 // Copyright (C) 2008-2014 - 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 (g_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 (g_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 _tcscat_s(menutextbuffer, 255, CString(MAKEINTRESOURCE(IDS_MENUCOMMITSUBMODULE)));
574 if (path.HasAdminDir(&sProjectRoot) && !CGit::GetCurrentBranchFromFile(sProjectRoot, sBranchName))
576 if (sBranchName.GetLength() == 40)
578 // if SHA1 only show 4 first bytes
579 BOOL bIsSha1 = TRUE;
580 for (int i=0; i<40; i++)
581 if ( !iswxdigit(sBranchName[i]) )
583 bIsSha1 = FALSE;
584 break;
586 if (bIsSha1)
587 sBranchName = sBranchName.Left(8) + _T("....");
590 // sanity check
591 if (sBranchName.GetLength() > 64)
592 sBranchName = sBranchName.Left(64) + _T("...");
594 // scan to before "..."
595 LPTSTR s = menutextbuffer + _tcslen(menutextbuffer)-1;
596 if (s > menutextbuffer)
598 while (s > menutextbuffer)
600 if (*s != _T('.'))
602 s++;
603 break;
605 s--;
608 else
610 s = menutextbuffer;
613 // append branch name and end with ...
614 _tcscpy_s(s, 255 - _tcslen(menutextbuffer) - 1, _T(" -> \"") + sBranchName + _T("\"..."));
618 if (com == ShellMenuDiffLater)
620 std::wstring sPath = regDiffLater;
621 if (!sPath.empty())
623 // add the path of the saved file
624 wchar_t compact[40] = {0};
625 PathCompactPathEx(compact, sPath.c_str(), _countof(compact) - 1, 0);
626 CString sMenu;
627 sMenu.Format(IDS_MENUDIFFNOW, compact);
628 wcscpy_s(menutextbuffer, sMenu);
632 MENUITEMINFO menuiteminfo;
633 SecureZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
634 menuiteminfo.cbSize = sizeof(menuiteminfo);
635 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING;
636 menuiteminfo.fType = MFT_STRING;
637 menuiteminfo.dwTypeData = menutextbuffer;
638 if (icon)
640 menuiteminfo.fMask |= MIIM_BITMAP;
641 menuiteminfo.hbmpItem = SysInfo::Instance().IsVistaOrLater() ? m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, icon) : HBMMENU_CALLBACK;
643 menuiteminfo.wID = (UINT)id;
644 InsertMenuItem(menu, pos, TRUE, &menuiteminfo);
646 if (istop)
648 //menu entry for the top context menu, so append an "Git " before
649 //the menu text to indicate where the entry comes from
650 _tcscpy_s(menutextbuffer, 255, _T("Git "));
652 LoadString(g_hResInst, stringid, verbsbuffer, _countof(verbsbuffer));
653 _tcscat_s(menutextbuffer, 255, verbsbuffer);
654 stdstring verb = stdstring(menutextbuffer);
655 if (verb.find('&') != -1)
657 verb.erase(verb.find('&'),1);
659 myVerbsMap[verb] = id - idCmdFirst;
660 myVerbsMap[verb] = id;
661 myVerbsIDMap[id - idCmdFirst] = verb;
662 myVerbsIDMap[id] = verb;
663 // We store the relative and absolute diameter
664 // (drawitem callback uses absolute, others relative)
665 myIDMap[id - idCmdFirst] = com;
666 myIDMap[id] = com;
667 if (!istop)
668 mySubMenuMap[pos] = com;
671 bool CShellExt::WriteClipboardPathsToTempFile(stdstring& tempfile)
673 bool bRet = true;
674 tempfile = stdstring();
675 //write all selected files and paths to a temporary file
676 //for TortoiseGitProc.exe to read out again.
677 DWORD written = 0;
678 DWORD pathlength = GetTortoiseGitTempPath(0, NULL);
679 std::unique_ptr<TCHAR[]> path(new TCHAR[pathlength + 1]);
680 std::unique_ptr<TCHAR[]> tempFile(new TCHAR[pathlength + 100]);
681 GetTortoiseGitTempPath(pathlength+1, path.get());
682 GetTempFileName(path.get(), _T("git"), 0, tempFile.get());
683 tempfile = stdstring(tempFile.get());
685 CAutoFile file = ::CreateFile(tempFile.get(),
686 GENERIC_WRITE,
687 FILE_SHARE_READ,
689 CREATE_ALWAYS,
690 FILE_ATTRIBUTE_TEMPORARY,
693 if (!file)
694 return false;
696 if (!IsClipboardFormatAvailable(CF_HDROP))
697 return false;
698 if (!OpenClipboard(NULL))
699 return false;
701 stdstring sClipboardText;
702 HGLOBAL hglb = GetClipboardData(CF_HDROP);
703 HDROP hDrop = (HDROP)GlobalLock(hglb);
704 if(hDrop != NULL)
706 TCHAR szFileName[MAX_PATH] = {0};
707 UINT cFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
708 for(UINT i = 0; i < cFiles; ++i)
710 DragQueryFile(hDrop, i, szFileName, _countof(szFileName));
711 stdstring filename = szFileName;
712 ::WriteFile (file, filename.c_str(), (DWORD)filename.size()*sizeof(TCHAR), &written, 0);
713 ::WriteFile (file, _T("\n"), 2, &written, 0);
715 GlobalUnlock(hDrop);
717 else bRet = false;
718 GlobalUnlock(hglb);
720 CloseClipboard();
722 return bRet;
725 stdstring CShellExt::WriteFileListToTempFile()
727 //write all selected files and paths to a temporary file
728 //for TortoiseGitProc.exe to read out again.
729 DWORD pathlength = GetTortoiseGitTempPath(0, NULL);
730 std::unique_ptr<TCHAR[]> path(new TCHAR[pathlength + 1]);
731 std::unique_ptr<TCHAR[]> tempFile(new TCHAR[pathlength + 100]);
732 GetTortoiseGitTempPath(pathlength + 1, path.get());
733 GetTempFileName(path.get(), _T("git"), 0, tempFile.get());
734 stdstring retFilePath = stdstring(tempFile.get());
736 CAutoFile file = ::CreateFile (tempFile.get(),
737 GENERIC_WRITE,
738 FILE_SHARE_READ,
740 CREATE_ALWAYS,
741 FILE_ATTRIBUTE_TEMPORARY,
744 if (!file)
745 return stdstring();
747 DWORD written = 0;
748 if (files_.empty())
750 ::WriteFile (file, folder_.c_str(), (DWORD)folder_.size()*sizeof(TCHAR), &written, 0);
751 ::WriteFile (file, _T("\n"), 2, &written, 0);
754 for (std::vector<stdstring>::iterator I = files_.begin(); I != files_.end(); ++I)
756 ::WriteFile (file, I->c_str(), (DWORD)I->size()*sizeof(TCHAR), &written, 0);
757 ::WriteFile (file, _T("\n"), 2, &written, 0);
759 return retFilePath;
762 STDMETHODIMP CShellExt::QueryDropContext(UINT uFlags, UINT idCmdFirst, HMENU hMenu, UINT &indexMenu)
764 if (!CRegStdDWORD(L"Software\\TortoiseGit\\EnableDragContextMenu", TRUE))
765 return S_OK;
767 PreserveChdir preserveChdir;
768 LoadLangDll();
770 if ((uFlags & CMF_DEFAULTONLY)!=0)
771 return S_OK; //we don't change the default action
773 if (files_.empty() || folder_.empty())
774 return S_OK;
776 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
777 return S_OK;
779 bool bSourceAndTargetFromSameRepository = (uuidSource.compare(uuidTarget) == 0) || uuidSource.empty() || uuidTarget.empty();
781 //the drop handler only has eight commands, but not all are visible at the same time:
782 //if the source file(s) are under version control then those files can be moved
783 //to the new location or they can be moved with a rename,
784 //if they are unversioned then they can be added to the working copy
785 //if they are versioned, they also can be exported to an unversioned location
786 UINT idCmd = idCmdFirst;
788 bool moveAvailable = false;
789 // Git move here
790 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
791 if ((bSourceAndTargetFromSameRepository || (itemStatesFolder & ITEMIS_ADDED)) && (itemStatesFolder & ITEMIS_FOLDERINGIT) && ((itemStates & (ITEMIS_NORMAL | ITEMIS_INGIT | ITEMIS_FOLDERINGIT)) && ((~itemStates) & ITEMIS_ADDED)))
793 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVEMENU, 0, idCmdFirst, ShellMenuDropMove, uFlags);
794 moveAvailable = true;
797 // Git move and rename here
798 // 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
799 if ((bSourceAndTargetFromSameRepository || (itemStatesFolder & ITEMIS_ADDED)) && (itemStatesFolder & ITEMIS_FOLDERINGIT) && (itemStates & (ITEMIS_NORMAL | ITEMIS_INGIT | ITEMIS_FOLDERINGIT)) && (itemStates & ITEMIS_ONLYONE) && ((~itemStates) & ITEMIS_ADDED))
801 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVERENAMEMENU, 0, idCmdFirst, ShellMenuDropMoveRename, uFlags);
802 moveAvailable = true;
805 // Git copy here
806 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
807 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&((~itemStates) & ITEMIS_ADDED))
808 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYMENU, 0, idCmdFirst, ShellMenuDropCopy, uFlags);
810 // Git copy and rename here, source and target from same repository
811 // available if source is a single, versioned but not added item, target is versioned or target folder is added
812 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
813 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYRENAMEMENU, 0, idCmdFirst, ShellMenuDropCopyRename, uFlags);
815 // Git add here
816 // available if target is versioned and source is either unversioned or from another repository
817 if ((itemStatesFolder & ITEMIS_FOLDERINGIT) && (((~itemStates) & ITEMIS_INGIT) || !bSourceAndTargetFromSameRepository) && !moveAvailable)
818 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYADDMENU, 0, idCmdFirst, ShellMenuDropCopyAdd, uFlags);
820 // Git export here
821 // available if source is versioned and a folder
822 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
823 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
825 // Git export all here
826 // available if source is versioned and a folder
827 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
828 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
830 // apply patch
831 // available if source is a patchfile
832 if (itemStates & ITEMIS_PATCHFILE)
833 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_MENUAPPLYPATCH, 0, idCmdFirst, ShellMenuApplyPatch, uFlags);
835 // separator
836 if (idCmd != idCmdFirst)
837 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
839 TweakMenu(hMenu);
841 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
844 STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu,
845 UINT indexMenu,
846 UINT idCmdFirst,
847 UINT idCmdLast,
848 UINT uFlags)
850 __try
852 return QueryContextMenu_Wrap(hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
854 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
857 return E_FAIL;
860 STDMETHODIMP CShellExt::QueryContextMenu_Wrap(HMENU hMenu,
861 UINT indexMenu,
862 UINT idCmdFirst,
863 UINT /*idCmdLast*/,
864 UINT uFlags)
866 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Shell :: QueryContextMenu itemStates=%ld\n", itemStates);
867 PreserveChdir preserveChdir;
869 //first check if our drop handler is called
870 //and then (if true) provide the context menu for the
871 //drop handler
872 if (m_State == FileStateDropHandler)
874 return QueryDropContext(uFlags, idCmdFirst, hMenu, indexMenu);
877 if ((uFlags & CMF_DEFAULTONLY)!=0)
878 return S_OK; //we don't change the default action
880 if (files_.empty() && folder_.empty())
881 return S_OK;
883 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
884 return S_OK;
886 int csidlarray[] =
888 CSIDL_BITBUCKET,
889 CSIDL_CDBURN_AREA,
890 CSIDL_COMMON_FAVORITES,
891 CSIDL_COMMON_STARTMENU,
892 CSIDL_COMPUTERSNEARME,
893 CSIDL_CONNECTIONS,
894 CSIDL_CONTROLS,
895 CSIDL_COOKIES,
896 CSIDL_FAVORITES,
897 CSIDL_FONTS,
898 CSIDL_HISTORY,
899 CSIDL_INTERNET,
900 CSIDL_INTERNET_CACHE,
901 CSIDL_NETHOOD,
902 CSIDL_NETWORK,
903 CSIDL_PRINTERS,
904 CSIDL_PRINTHOOD,
905 CSIDL_RECENT,
906 CSIDL_SENDTO,
907 CSIDL_STARTMENU,
910 if (IsIllegalFolder(folder_, csidlarray))
911 return S_OK;
913 if (folder_.empty())
915 // folder is empty, but maybe files are selected
916 if (files_.empty())
917 return S_OK; // nothing selected - we don't have a menu to show
918 // check whether a selected entry is an UID - those are namespace extensions
919 // which we can't handle
920 for (std::vector<stdstring>::const_iterator it = files_.begin(); it != files_.end(); ++it)
922 if (_tcsncmp(it->c_str(), _T("::{"), 3)==0)
923 return S_OK;
926 else
928 if (_tcsncmp(folder_.c_str(), _T("::{"), 3) == 0)
929 return S_OK;
932 if (((uFlags & CMF_EXTENDEDVERBS) == 0) && g_ShellCache.HideMenusForUnversionedItems())
934 if ((itemStates & (ITEMIS_INGIT|ITEMIS_INVERSIONEDFOLDER|ITEMIS_FOLDERINGIT|ITEMIS_BAREREPO|ITEMIS_TWO))==0)
935 return S_OK;
938 //check if our menu is requested for a git admin directory
939 if (g_GitAdminDir.IsAdminDirPath(folder_.c_str()))
940 return S_OK;
942 if (uFlags & CMF_EXTENDEDVERBS)
943 itemStates |= ITEMIS_EXTENDED;
945 regDiffLater.read();
946 if (!std::wstring(regDiffLater).empty())
947 itemStates |= ITEMIS_HASDIFFLATER;
949 const BOOL bShortcut = !!(uFlags & CMF_VERBSONLY);
950 if ( bShortcut && (files_.size()==1))
952 // Don't show the context menu for a link if the
953 // destination is not part of a working copy.
954 // It would only show the standard menu items
955 // which are already shown for the lnk-file.
956 CString path = files_.front().c_str();
957 if ( !g_GitAdminDir.HasAdminDir(path) )
959 return S_OK;
963 //check if we already added our menu entry for a folder.
964 //we check that by iterating through all menu entries and check if
965 //the dwItemData member points to our global ID string. That string is set
966 //by our shell extension when the folder menu is inserted.
967 TCHAR menubuf[MAX_PATH] = {0};
968 int count = GetMenuItemCount(hMenu);
969 for (int i=0; i<count; ++i)
971 MENUITEMINFO miif;
972 SecureZeroMemory(&miif, sizeof(MENUITEMINFO));
973 miif.cbSize = sizeof(MENUITEMINFO);
974 miif.fMask = MIIM_DATA;
975 miif.dwTypeData = menubuf;
976 miif.cch = _countof(menubuf);
977 GetMenuItemInfo(hMenu, i, TRUE, &miif);
978 if (miif.dwItemData == (ULONG_PTR)g_MenuIDString)
979 return S_OK;
982 LoadLangDll();
983 UINT idCmd = idCmdFirst;
985 //create the sub menu
986 HMENU subMenu = CreateMenu();
987 int indexSubMenu = 0;
989 unsigned __int64 topmenu = g_ShellCache.GetMenuLayout();
990 unsigned __int64 menumask = g_ShellCache.GetMenuMask();
991 unsigned __int64 menuex = g_ShellCache.GetMenuExt();
993 int menuIndex = 0;
994 bool bAddSeparator = false;
995 bool bMenuEntryAdded = false;
996 bool bMenuEmpty = true;
997 // insert separator at start
998 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL); idCmd++;
999 bool bShowIcons = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\ShowContextMenuIcons"), TRUE));
1001 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1003 if (menuInfo[menuIndex].command == ShellSeparator)
1005 // we don't add a separator immediately. Because there might not be
1006 // another 'normal' menu entry after we insert a separator.
1007 // we simply set a flag here, indicating that before the next
1008 // 'normal' menu entry, a separator should be added.
1009 if (!bMenuEmpty)
1010 bAddSeparator = true;
1011 if (bMenuEntryAdded)
1012 bAddSeparator = true;
1014 else
1016 // check the conditions whether to show the menu entry or not
1017 bool bInsertMenu = ShouldInsertItem(menuInfo[menuIndex]);
1018 if (menuInfo[menuIndex].menuID & menuex)
1020 if( !(itemStates & ITEMIS_EXTENDED) )
1022 bInsertMenu = false;
1026 if (menuInfo[menuIndex].menuID & (~menumask))
1028 if (bInsertMenu)
1030 bool bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1031 // insert a separator
1032 if ((bMenuEntryAdded)&&(bAddSeparator)&&(!bIsTop))
1034 bAddSeparator = false;
1035 bMenuEntryAdded = false;
1036 InsertMenu(subMenu, indexSubMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
1037 idCmd++;
1040 // handle special cases (sub menus)
1041 if ((menuInfo[menuIndex].command == ShellMenuIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuUnIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuDeleteIgnoreSub))
1043 if(InsertIgnoreSubmenus(idCmd, idCmdFirst, hMenu, subMenu, indexMenu, indexSubMenu, topmenu, bShowIcons, uFlags))
1044 bMenuEntryAdded = true;
1046 else
1048 bool bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1050 // insert the menu entry
1051 InsertGitMenu( bIsTop,
1052 bIsTop ? hMenu : subMenu,
1053 bIsTop ? indexMenu++ : indexSubMenu++,
1054 idCmd++,
1055 menuInfo[menuIndex].menuTextID,
1056 bShowIcons ? menuInfo[menuIndex].iconID : 0,
1057 idCmdFirst,
1058 menuInfo[menuIndex].command,
1059 uFlags);
1060 if (!bIsTop)
1062 bMenuEntryAdded = true;
1063 bMenuEmpty = false;
1064 bAddSeparator = false;
1070 menuIndex++;
1073 // do not show TortoiseGit menu if it's empty
1074 if (bMenuEmpty)
1076 if (idCmd - idCmdFirst > 0)
1078 //separator after
1079 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL); idCmd++;
1081 TweakMenu(hMenu);
1083 //return number of menu items added
1084 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
1087 //add sub menu to main context menu
1088 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1089 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1090 MAKESTRING(IDS_MENUSUBMENU);
1091 if (!g_ShellCache.HasShellMenuAccelerators())
1093 // remove the accelerators
1094 tstring temp = stringtablebuffer;
1095 temp.erase(std::remove(temp.begin(), temp.end(), '&'), temp.end());
1096 _tcscpy_s(stringtablebuffer, temp.c_str());
1098 MENUITEMINFO menuiteminfo;
1099 SecureZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
1100 menuiteminfo.cbSize = sizeof(menuiteminfo);
1101 menuiteminfo.fType = MFT_STRING;
1102 menuiteminfo.dwTypeData = stringtablebuffer;
1104 UINT uIcon = bShowIcons ? IDI_APP : 0;
1105 if (!folder_.empty())
1107 uIcon = bShowIcons ? IDI_MENUFOLDER : 0;
1108 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFolder;
1109 myIDMap[idCmd] = ShellSubMenuFolder;
1110 menuiteminfo.dwItemData = (ULONG_PTR)g_MenuIDString;
1112 else if (!bShortcut && (files_.size()==1))
1114 uIcon = bShowIcons ? IDI_MENUFILE : 0;
1115 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFile;
1116 myIDMap[idCmd] = ShellSubMenuFile;
1118 else if (bShortcut && (files_.size()==1))
1120 uIcon = bShowIcons ? IDI_MENULINK : 0;
1121 myIDMap[idCmd - idCmdFirst] = ShellSubMenuLink;
1122 myIDMap[idCmd] = ShellSubMenuLink;
1124 else if (!files_.empty())
1126 uIcon = bShowIcons ? IDI_MENUMULTIPLE : 0;
1127 myIDMap[idCmd - idCmdFirst] = ShellSubMenuMultiple;
1128 myIDMap[idCmd] = ShellSubMenuMultiple;
1130 else
1132 myIDMap[idCmd - idCmdFirst] = ShellSubMenu;
1133 myIDMap[idCmd] = ShellSubMenu;
1135 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
1136 if (uIcon)
1138 menuiteminfo.fMask |= MIIM_BITMAP;
1139 menuiteminfo.hbmpItem = SysInfo::Instance().IsVistaOrLater() ? m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, uIcon) : HBMMENU_CALLBACK;
1141 menuiteminfo.hSubMenu = subMenu;
1142 menuiteminfo.wID = idCmd++;
1143 InsertMenuItem(hMenu, indexMenu++, TRUE, &menuiteminfo);
1145 //separator after
1146 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL); idCmd++;
1148 TweakMenu(hMenu);
1150 //return number of menu items added
1151 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
1154 void CShellExt::TweakMenu(HMENU hMenu)
1156 MENUINFO MenuInfo = {};
1157 MenuInfo.cbSize = sizeof(MenuInfo);
1158 MenuInfo.fMask = MIM_STYLE | MIM_APPLYTOSUBMENUS;
1159 MenuInfo.dwStyle = MNS_CHECKORBMP;
1160 SetMenuInfo(hMenu, &MenuInfo);
1163 void CShellExt::AddPathCommand(tstring& gitCmd, LPCTSTR command, bool bFilesAllowed)
1165 gitCmd += command;
1166 gitCmd += _T(" /path:\"");
1167 if ((bFilesAllowed) && !files_.empty())
1168 gitCmd += files_.front();
1169 else
1170 gitCmd += folder_;
1171 gitCmd += _T("\"");
1174 void CShellExt::AddPathFileCommand(tstring& gitCmd, LPCTSTR command)
1176 tstring tempfile = WriteFileListToTempFile();
1177 gitCmd += command;
1178 gitCmd += _T(" /pathfile:\"");
1179 gitCmd += tempfile;
1180 gitCmd += _T("\"");
1181 gitCmd += _T(" /deletepathfile");
1184 void CShellExt::AddPathFileDropCommand(tstring& gitCmd, LPCTSTR command)
1186 tstring tempfile = WriteFileListToTempFile();
1187 gitCmd += command;
1188 gitCmd += _T(" /pathfile:\"");
1189 gitCmd += tempfile;
1190 gitCmd += _T("\"");
1191 gitCmd += _T(" /deletepathfile");
1192 gitCmd += _T(" /droptarget:\"");
1193 gitCmd += folder_;
1194 gitCmd += _T("\"");
1197 STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
1199 __try
1201 return InvokeCommand_Wrap(lpcmi);
1203 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1206 return E_FAIL;
1209 // This is called when you invoke a command on the menu:
1210 STDMETHODIMP CShellExt::InvokeCommand_Wrap(LPCMINVOKECOMMANDINFO lpcmi)
1212 PreserveChdir preserveChdir;
1213 HRESULT hr = E_INVALIDARG;
1214 if (lpcmi == NULL)
1215 return hr;
1217 if (!files_.empty() || !folder_.empty())
1219 UINT_PTR idCmd = LOWORD(lpcmi->lpVerb);
1221 if (HIWORD(lpcmi->lpVerb))
1223 stdstring verb = stdstring(MultibyteToWide(lpcmi->lpVerb));
1224 std::map<stdstring, UINT_PTR>::const_iterator verb_it = myVerbsMap.lower_bound(verb);
1225 if (verb_it != myVerbsMap.end() && verb_it->first == verb)
1226 idCmd = verb_it->second;
1227 else
1228 return hr;
1231 // See if we have a handler interface for this id
1232 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
1233 if (id_it != myIDMap.end() && id_it->first == idCmd)
1235 tstring tortoiseProcPath = CPathUtils::GetAppDirectory(g_hmodThisDll) + _T("TortoiseGitProc.exe");
1236 tstring tortoiseMergePath = CPathUtils::GetAppDirectory(g_hmodThisDll) + _T("TortoiseGitMerge.exe");
1238 //TortoiseGitProc expects a command line of the form:
1239 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1240 // or
1241 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1243 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1244 //* pathfile is a path to a temporary file which contains a list of file paths
1245 stdstring gitCmd = _T(" /command:");
1246 stdstring tempfile;
1247 switch (id_it->second)
1249 //#region case
1250 case ShellMenuSync:
1252 TCHAR syncSeq[12] = { 0 };
1253 _stprintf_s(syncSeq, _T("%d"), g_syncSeq++);
1254 AddPathCommand(gitCmd, L"sync", false);
1255 gitCmd += _T(" /seq:");
1256 gitCmd += syncSeq;
1258 break;
1259 case ShellMenuSubSync:
1260 AddPathFileCommand(gitCmd, L"subsync");
1261 if (itemStatesFolder & ITEMIS_SUBMODULECONTAINER || (itemStates & ITEMIS_SUBMODULECONTAINER && itemStates & ITEMIS_WCROOT && itemStates & ITEMIS_ONLYONE))
1263 gitCmd += _T(" /bkpath:\"");
1264 gitCmd += folder_;
1265 gitCmd += _T("\"");
1267 break;
1268 case ShellMenuUpdateExt:
1269 AddPathFileCommand(gitCmd, L"subupdate");
1270 if (itemStatesFolder & ITEMIS_SUBMODULECONTAINER || (itemStates & ITEMIS_SUBMODULECONTAINER && itemStates & ITEMIS_WCROOT && itemStates & ITEMIS_ONLYONE))
1272 gitCmd += _T(" /bkpath:\"");
1273 gitCmd += folder_;
1274 gitCmd += _T("\"");
1276 break;
1277 case ShellMenuCommit:
1278 AddPathFileCommand(gitCmd, L"commit");
1279 break;
1280 case ShellMenuAdd:
1281 AddPathFileCommand(gitCmd, L"add");
1282 break;
1283 case ShellMenuIgnore:
1284 AddPathFileCommand(gitCmd, L"ignore");
1285 break;
1286 case ShellMenuIgnoreCaseSensitive:
1287 AddPathFileCommand(gitCmd, L"ignore");
1288 gitCmd += _T(" /onlymask");
1289 break;
1290 case ShellMenuDeleteIgnore:
1291 AddPathFileCommand(gitCmd, L"ignore");
1292 gitCmd += _T(" /delete");
1293 break;
1294 case ShellMenuDeleteIgnoreCaseSensitive:
1295 AddPathFileCommand(gitCmd, L"ignore");
1296 gitCmd += _T(" /delete /onlymask");
1297 break;
1298 case ShellMenuUnIgnore:
1299 AddPathFileCommand(gitCmd, L"unignore");
1300 break;
1301 case ShellMenuUnIgnoreCaseSensitive:
1302 AddPathFileCommand(gitCmd, L"unignore");
1303 gitCmd += _T(" /onlymask");
1304 break;
1305 case ShellMenuMergeAbort:
1306 AddPathCommand(gitCmd, L"merge", false);
1307 gitCmd += _T(" /abort");
1308 break;
1309 case ShellMenuRevert:
1310 AddPathFileCommand(gitCmd, L"revert");
1311 break;
1312 case ShellMenuCleanup:
1313 AddPathFileCommand(gitCmd, L"cleanup");
1314 break;
1315 case ShellMenuSendMail:
1316 AddPathFileCommand(gitCmd, L"sendmail");
1317 break;
1318 case ShellMenuResolve:
1319 AddPathFileCommand(gitCmd, L"resolve");
1320 break;
1321 case ShellMenuSwitch:
1322 AddPathCommand(gitCmd, L"switch", false);
1323 break;
1324 case ShellMenuExport:
1325 AddPathCommand(gitCmd, L"export", false);
1326 break;
1327 case ShellMenuAbout:
1328 gitCmd += _T("about");
1329 break;
1330 case ShellMenuCreateRepos:
1331 AddPathCommand(gitCmd, L"repocreate", false);
1332 break;
1333 case ShellMenuMerge:
1334 AddPathCommand(gitCmd, L"merge", false);
1335 break;
1336 case ShellMenuCopy:
1337 AddPathCommand(gitCmd, L"copy", true);
1338 break;
1339 case ShellMenuSettings:
1340 AddPathCommand(gitCmd, L"settings", true);
1341 break;
1342 case ShellMenuHelp:
1343 gitCmd += _T("help");
1344 break;
1345 case ShellMenuRename:
1346 AddPathCommand(gitCmd, L"rename", true);
1347 break;
1348 case ShellMenuRemove:
1349 AddPathFileCommand(gitCmd, L"remove");
1350 break;
1351 case ShellMenuRemoveKeep:
1352 AddPathFileCommand(gitCmd, L"remove");
1353 gitCmd += _T(" /keep");
1354 break;
1355 case ShellMenuDiff:
1356 gitCmd += _T("diff /path:\"");
1357 if (files_.size() == 1)
1358 gitCmd += files_.front();
1359 else if (files_.size() == 2)
1361 std::vector<stdstring>::iterator I = files_.begin();
1362 gitCmd += *I;
1363 ++I;
1364 gitCmd += _T("\" /path2:\"");
1365 gitCmd += *I;
1367 else
1368 gitCmd += folder_;
1369 gitCmd += _T("\"");
1370 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1371 gitCmd += _T(" /alternative");
1372 break;
1373 case ShellMenuDiffLater:
1374 if (lpcmi->fMask & CMIC_MASK_CONTROL_DOWN)
1376 gitCmd.clear();
1377 regDiffLater.removeValue();
1379 else if (files_.size() == 1)
1381 if (std::wstring(regDiffLater).empty())
1383 gitCmd.clear();
1384 regDiffLater = files_[0];
1386 else
1388 AddPathCommand(gitCmd, L"diff", true);
1389 gitCmd += _T(" /path2:\"");
1390 gitCmd += std::wstring(regDiffLater);
1391 gitCmd += _T("\"");
1392 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1393 gitCmd += _T(" /alternative");
1394 regDiffLater.removeValue();
1397 else
1399 gitCmd.clear();
1401 break;
1402 case ShellMenuPrevDiff:
1403 AddPathCommand(gitCmd, L"prevdiff", true);
1404 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1405 gitCmd += _T(" /alternative");
1406 break;
1407 case ShellMenuDiffTwo:
1408 AddPathCommand(gitCmd, L"diffcommits", true);
1409 break;
1410 case ShellMenuDropCopyAdd:
1411 AddPathFileDropCommand(gitCmd, L"dropcopyadd");
1412 break;
1413 case ShellMenuDropCopy:
1414 AddPathFileDropCommand(gitCmd, L"dropcopy");
1415 break;
1416 case ShellMenuDropCopyRename:
1417 AddPathFileDropCommand(gitCmd, L"dropcopy");
1418 gitCmd += _T("\" /rename";)
1419 break;
1420 case ShellMenuDropMove:
1421 AddPathFileDropCommand(gitCmd, L"dropmove");
1422 break;
1423 case ShellMenuDropMoveRename:
1424 AddPathFileDropCommand(gitCmd, L"dropmove");
1425 gitCmd += _T("\" /rename";)
1426 break;
1427 case ShellMenuDropExport:
1428 AddPathFileDropCommand(gitCmd, L"dropexport");
1429 break;
1430 case ShellMenuDropExportExtended:
1431 AddPathFileDropCommand(gitCmd, L"dropexport");
1432 gitCmd += _T(" /extended");
1433 break;
1434 case ShellMenuLog:
1435 case ShellMenuLogSubmoduleFolder:
1436 AddPathCommand(gitCmd, L"log", true);
1437 if (id_it->second == ShellMenuLogSubmoduleFolder)
1438 gitCmd += _T(" /submodule");
1439 break;
1440 case ShellMenuDaemon:
1441 AddPathCommand(gitCmd, L"daemon", true);
1442 break;
1443 case ShellMenuRevisionGraph:
1444 AddPathCommand(gitCmd, L"revisiongraph", true);
1445 break;
1446 case ShellMenuConflictEditor:
1447 AddPathCommand(gitCmd, L"conflicteditor", true);
1448 break;
1449 case ShellMenuGitSVNRebase:
1450 AddPathCommand(gitCmd, L"svnrebase", false);
1451 break;
1452 case ShellMenuGitSVNDCommit:
1453 AddPathCommand(gitCmd, L"svndcommit", true);
1454 break;
1455 case ShellMenuGitSVNDFetch:
1456 AddPathCommand(gitCmd, L"svnfetch", false);
1457 break;
1458 case ShellMenuGitSVNIgnore:
1459 AddPathCommand(gitCmd, L"svnignore", false);
1460 break;
1461 case ShellMenuRebase:
1462 AddPathCommand(gitCmd, L"rebase", false);
1463 break;
1464 case ShellMenuShowChanged:
1465 if (files_.size() > 1)
1467 AddPathFileCommand(gitCmd, L"repostatus");
1469 else
1471 AddPathCommand(gitCmd, L"repostatus", true);
1473 break;
1474 case ShellMenuRepoBrowse:
1475 AddPathCommand(gitCmd, L"repobrowser", false);
1476 break;
1477 case ShellMenuRefBrowse:
1478 AddPathCommand(gitCmd, L"refbrowse", false);
1479 break;
1480 case ShellMenuRefLog:
1481 AddPathCommand(gitCmd, L"reflog", false);
1482 break;
1483 case ShellMenuStashSave:
1484 AddPathCommand(gitCmd, L"stashsave", true);
1485 break;
1486 case ShellMenuStashApply:
1487 AddPathCommand(gitCmd, L"stashapply", false);
1488 break;
1489 case ShellMenuStashPop:
1490 AddPathCommand(gitCmd, L"stashpop", false);
1491 break;
1492 case ShellMenuStashList:
1493 AddPathCommand(gitCmd, L"reflog", false);
1494 gitCmd += _T(" /ref:refs/stash");
1495 break;
1496 case ShellMenuBisectStart:
1497 AddPathCommand(gitCmd, L"bisect", false);
1498 gitCmd += _T("\" /start");
1499 break;
1500 case ShellMenuBisectGood:
1501 AddPathCommand(gitCmd, L"bisect", false);
1502 gitCmd += _T("\" /good");
1503 break;
1504 case ShellMenuBisectBad:
1505 AddPathCommand(gitCmd, L"bisect", false);
1506 gitCmd += _T("\" /bad");
1507 break;
1508 case ShellMenuBisectReset:
1509 AddPathCommand(gitCmd, L"bisect", false);
1510 gitCmd += _T("\" /reset");
1511 break;
1512 case ShellMenuSubAdd:
1513 AddPathCommand(gitCmd, L"subadd", false);
1514 break;
1515 case ShellMenuBlame:
1516 AddPathCommand(gitCmd, L"blame", true);
1517 break;
1518 case ShellMenuApplyPatch:
1519 if ((itemStates & ITEMIS_PATCHINCLIPBOARD) && ((~itemStates) & ITEMIS_PATCHFILE))
1521 // if there's a patch file in the clipboard, we save it
1522 // to a temporary file and tell TortoiseGitMerge to use that one
1523 UINT cFormat = RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
1524 if ((cFormat)&&(OpenClipboard(NULL)))
1526 HGLOBAL hglb = GetClipboardData(cFormat);
1527 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
1529 DWORD len = GetTortoiseGitTempPath(0, NULL);
1530 std::unique_ptr<TCHAR[]> path(new TCHAR[len + 1]);
1531 std::unique_ptr<TCHAR[]> tempF(new TCHAR[len + 100]);
1532 GetTortoiseGitTempPath(len + 1, path.get());
1533 GetTempFileName(path.get(), TEXT("git"), 0, tempF.get());
1534 std::wstring sTempFile = std::wstring(tempF.get());
1536 FILE * outFile;
1537 size_t patchlen = strlen(lpstr);
1538 _tfopen_s(&outFile, sTempFile.c_str(), _T("wb"));
1539 if(outFile)
1541 size_t size = fwrite(lpstr, sizeof(char), patchlen, outFile);
1542 if (size == patchlen)
1544 itemStates |= ITEMIS_PATCHFILE;
1545 files_.clear();
1546 files_.push_back(sTempFile);
1548 fclose(outFile);
1550 GlobalUnlock(hglb);
1551 CloseClipboard();
1554 if (itemStates & ITEMIS_PATCHFILE)
1556 gitCmd = _T(" /diff:\"");
1557 if (!files_.empty())
1559 gitCmd += files_.front();
1560 if (itemStatesFolder & ITEMIS_FOLDERINGIT)
1562 gitCmd += _T("\" /patchpath:\"");
1563 gitCmd += folder_;
1566 else
1567 gitCmd += folder_;
1568 if (itemStates & ITEMIS_INVERSIONEDFOLDER)
1569 gitCmd += _T("\" /wc");
1570 else
1571 gitCmd += _T("\"");
1573 else
1575 gitCmd = _T(" /patchpath:\"");
1576 if (!files_.empty())
1577 gitCmd += files_.front();
1578 else
1579 gitCmd += folder_;
1580 gitCmd += _T("\"");
1582 myIDMap.clear();
1583 myVerbsIDMap.clear();
1584 myVerbsMap.clear();
1585 RunCommand(tortoiseMergePath, gitCmd, _T("TortoiseGitMerge launch failed"));
1586 return S_OK;
1587 break;
1588 case ShellMenuClipPaste:
1589 if (WriteClipboardPathsToTempFile(tempfile))
1591 bool bCopy = true;
1592 UINT cPrefDropFormat = RegisterClipboardFormat(_T("Preferred DropEffect"));
1593 if (cPrefDropFormat)
1595 if (OpenClipboard(lpcmi->hwnd))
1597 HGLOBAL hglb = GetClipboardData(cPrefDropFormat);
1598 if (hglb)
1600 DWORD* effect = (DWORD*) GlobalLock(hglb);
1601 if (*effect == DROPEFFECT_MOVE)
1602 bCopy = false;
1603 GlobalUnlock(hglb);
1605 CloseClipboard();
1609 if (bCopy)
1610 gitCmd += _T("pastecopy /pathfile:\"");
1611 else
1612 gitCmd += _T("pastemove /pathfile:\"");
1613 gitCmd += tempfile;
1614 gitCmd += _T("\"");
1615 gitCmd += _T(" /deletepathfile");
1616 gitCmd += _T(" /droptarget:\"");
1617 gitCmd += folder_;
1618 gitCmd += _T("\"");
1620 else return S_OK;
1621 break;
1622 case ShellMenuClone:
1623 AddPathCommand(gitCmd, L"clone", false);
1624 break;
1625 case ShellMenuPull:
1626 AddPathCommand(gitCmd, L"pull", false);
1627 break;
1628 case ShellMenuPush:
1629 AddPathCommand(gitCmd, L"push", false);
1630 break;
1631 case ShellMenuBranch:
1632 AddPathCommand(gitCmd, L"branch", false);
1633 break;
1634 case ShellMenuTag:
1635 AddPathCommand(gitCmd, L"tag", false);
1636 break;
1637 case ShellMenuFormatPatch:
1638 AddPathCommand(gitCmd, L"formatpatch", false);
1639 break;
1640 case ShellMenuImportPatch:
1641 AddPathFileCommand(gitCmd, L"importpatch");
1642 break;
1643 case ShellMenuFetch:
1644 AddPathCommand(gitCmd, L"fetch", false);
1645 break;
1647 default:
1648 break;
1649 //#endregion
1650 } // switch (id_it->second)
1651 if (!gitCmd.empty())
1653 gitCmd += _T(" /hwnd:");
1654 TCHAR buf[30] = { 0 };
1655 _stprintf_s(buf, _T("%p"), (void*)lpcmi->hwnd);
1656 gitCmd += buf;
1657 myIDMap.clear();
1658 myVerbsIDMap.clear();
1659 myVerbsMap.clear();
1660 RunCommand(tortoiseProcPath, gitCmd, _T("TortoiseProc launch failed"));
1662 hr = S_OK;
1663 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1664 } // if (files_.empty() || folder_.empty())
1665 return hr;
1669 // This is for the status bar and things like that:
1670 STDMETHODIMP CShellExt::GetCommandString(UINT_PTR idCmd,
1671 UINT uFlags,
1672 UINT FAR * reserved,
1673 LPSTR pszName,
1674 UINT cchMax)
1676 __try
1678 return GetCommandString_Wrap(idCmd, uFlags, reserved, pszName, cchMax);
1680 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1683 return E_FAIL;
1686 // This is for the status bar and things like that:
1687 STDMETHODIMP CShellExt::GetCommandString_Wrap(UINT_PTR idCmd,
1688 UINT uFlags,
1689 UINT FAR * /*reserved*/,
1690 LPSTR pszName,
1691 UINT cchMax)
1693 PreserveChdir preserveChdir;
1694 //do we know the id?
1695 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
1696 if (id_it == myIDMap.end() || id_it->first != idCmd)
1698 return E_INVALIDARG; //no, we don't
1701 LoadLangDll();
1702 HRESULT hr = E_INVALIDARG;
1704 MAKESTRING(IDS_MENUDESCDEFAULT);
1705 int menuIndex = 0;
1706 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1708 if (menuInfo[menuIndex].command == (GitCommands)id_it->second)
1710 MAKESTRING(menuInfo[menuIndex].menuDescID);
1711 break;
1713 menuIndex++;
1716 const TCHAR * desc = stringtablebuffer;
1717 switch(uFlags)
1719 case GCS_HELPTEXTA:
1721 std::string help = WideToMultibyte(desc);
1722 lstrcpynA(pszName, help.c_str(), cchMax);
1723 hr = S_OK;
1724 break;
1726 case GCS_HELPTEXTW:
1728 wide_string help = desc;
1729 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax);
1730 hr = S_OK;
1731 break;
1733 case GCS_VERBA:
1735 std::map<UINT_PTR, stdstring>::const_iterator verb_id_it = myVerbsIDMap.lower_bound(idCmd);
1736 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
1738 std::string help = WideToMultibyte(verb_id_it->second);
1739 lstrcpynA(pszName, help.c_str(), cchMax);
1740 hr = S_OK;
1743 break;
1744 case GCS_VERBW:
1746 std::map<UINT_PTR, stdstring>::const_iterator verb_id_it = myVerbsIDMap.lower_bound(idCmd);
1747 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
1749 wide_string help = verb_id_it->second;
1750 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": verb : %ws\n", help.c_str());
1751 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax);
1752 hr = S_OK;
1755 break;
1757 return hr;
1760 STDMETHODIMP CShellExt::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
1762 __try
1764 return HandleMenuMsg_Wrap(uMsg, wParam, lParam);
1766 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1769 return E_FAIL;
1772 STDMETHODIMP CShellExt::HandleMenuMsg_Wrap(UINT uMsg, WPARAM wParam, LPARAM lParam)
1774 LRESULT res;
1775 return HandleMenuMsg2(uMsg, wParam, lParam, &res);
1778 STDMETHODIMP CShellExt::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pResult)
1780 __try
1782 return HandleMenuMsg2_Wrap(uMsg, wParam, lParam, pResult);
1784 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1787 return E_FAIL;
1790 STDMETHODIMP CShellExt::HandleMenuMsg2_Wrap(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pResult)
1792 PreserveChdir preserveChdir;
1794 LRESULT res;
1795 if (pResult == NULL)
1796 pResult = &res;
1797 *pResult = FALSE;
1799 LoadLangDll();
1800 switch (uMsg)
1802 case WM_MEASUREITEM:
1804 MEASUREITEMSTRUCT* lpmis = (MEASUREITEMSTRUCT*)lParam;
1805 if (lpmis==NULL)
1806 break;
1807 lpmis->itemWidth = 16;
1808 lpmis->itemHeight = 16;
1809 *pResult = TRUE;
1811 break;
1812 case WM_DRAWITEM:
1814 LPCTSTR resource;
1815 DRAWITEMSTRUCT* lpdis = (DRAWITEMSTRUCT*)lParam;
1816 if ((lpdis==NULL)||(lpdis->CtlType != ODT_MENU))
1817 return S_OK; //not for a menu
1818 resource = GetMenuTextFromResource((int)myIDMap[lpdis->itemID]);
1819 if (resource == NULL)
1820 return S_OK;
1821 HICON hIcon = (HICON)LoadImage(g_hResInst, resource, IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
1822 if (hIcon == NULL)
1823 return S_OK;
1824 DrawIconEx(lpdis->hDC,
1825 lpdis->rcItem.left,
1826 lpdis->rcItem.top + (lpdis->rcItem.bottom - lpdis->rcItem.top - 16) / 2,
1827 hIcon, 16, 16,
1828 0, NULL, DI_NORMAL);
1829 DestroyIcon(hIcon);
1830 *pResult = TRUE;
1832 break;
1833 case WM_MENUCHAR:
1835 TCHAR *szItem;
1836 if (HIWORD(wParam) != MF_POPUP)
1837 return S_OK;
1838 int nChar = LOWORD(wParam);
1839 if (_istascii((wint_t)nChar) && _istupper((wint_t)nChar))
1840 nChar = tolower(nChar);
1841 // we have the char the user pressed, now search that char in all our
1842 // menu items
1843 std::vector<UINT_PTR> accmenus;
1844 for (std::map<UINT_PTR, UINT_PTR>::iterator It = mySubMenuMap.begin(); It != mySubMenuMap.end(); ++It)
1846 LPCTSTR resource = GetMenuTextFromResource((int)mySubMenuMap[It->first]);
1847 if (resource == NULL)
1848 continue;
1849 szItem = stringtablebuffer;
1850 TCHAR * amp = _tcschr(szItem, '&');
1851 if (amp == NULL)
1852 continue;
1853 amp++;
1854 int ampChar = LOWORD(*amp);
1855 if (_istascii((wint_t)ampChar) && _istupper((wint_t)ampChar))
1856 ampChar = tolower(ampChar);
1857 if (ampChar == nChar)
1859 // yep, we found a menu which has the pressed key
1860 // as an accelerator. Add that menu to the list to
1861 // process later.
1862 accmenus.push_back(It->first);
1865 if (accmenus.empty())
1867 // no menu with that accelerator key.
1868 *pResult = MAKELONG(0, MNC_IGNORE);
1869 return S_OK;
1871 if (accmenus.size() == 1)
1873 // Only one menu with that accelerator key. We're lucky!
1874 // So just execute that menu entry.
1875 *pResult = MAKELONG(accmenus[0], MNC_EXECUTE);
1876 return S_OK;
1878 if (accmenus.size() > 1)
1880 // we have more than one menu item with this accelerator key!
1881 MENUITEMINFO mif;
1882 mif.cbSize = sizeof(MENUITEMINFO);
1883 mif.fMask = MIIM_STATE;
1884 for (std::vector<UINT_PTR>::iterator it = accmenus.begin(); it != accmenus.end(); ++it)
1886 GetMenuItemInfo((HMENU)lParam, (UINT)*it, TRUE, &mif);
1887 if (mif.fState == MFS_HILITE)
1889 // this is the selected item, so select the next one
1890 ++it;
1891 if (it == accmenus.end())
1892 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
1893 else
1894 *pResult = MAKELONG(*it, MNC_SELECT);
1895 return S_OK;
1898 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
1901 break;
1902 default:
1903 return S_OK;
1906 return S_OK;
1909 LPCTSTR CShellExt::GetMenuTextFromResource(int id)
1911 TCHAR textbuf[255] = { 0 };
1912 LPCTSTR resource = NULL;
1913 unsigned __int64 layout = g_ShellCache.GetMenuLayout();
1914 space = 6;
1916 int menuIndex = 0;
1917 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1919 if (menuInfo[menuIndex].command == id)
1921 MAKESTRING(menuInfo[menuIndex].menuTextID);
1922 resource = MAKEINTRESOURCE(menuInfo[menuIndex].iconID);
1923 switch (id)
1925 case ShellSubMenuMultiple:
1926 case ShellSubMenuLink:
1927 case ShellSubMenuFolder:
1928 case ShellSubMenuFile:
1929 case ShellSubMenu:
1930 space = 0;
1931 break;
1932 default:
1933 space = (layout & menuInfo[menuIndex].menuID) ? 0 : 6;
1934 if (layout & menuInfo[menuIndex].menuID)
1936 _tcscpy_s(textbuf, 255, _T("Git "));
1937 _tcscat_s(textbuf, 255, stringtablebuffer);
1938 _tcscpy_s(stringtablebuffer, 255, textbuf);
1940 break;
1942 return resource;
1944 menuIndex++;
1946 return NULL;
1949 bool CShellExt::IsIllegalFolder(std::wstring folder, int * cslidarray)
1951 int i=0;
1952 TCHAR buf[MAX_PATH] = {0}; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
1953 LPITEMIDLIST pidl = NULL;
1954 while (cslidarray[i])
1956 ++i;
1957 pidl = NULL;
1958 if (SHGetFolderLocation(NULL, cslidarray[i-1], NULL, 0, &pidl)!=S_OK)
1959 continue;
1960 if (!SHGetPathFromIDList(pidl, buf))
1962 // not a file system path, definitely illegal for our use
1963 CoTaskMemFree(pidl);
1964 continue;
1966 CoTaskMemFree(pidl);
1967 if (_tcslen(buf)==0)
1968 continue;
1969 if (_tcscmp(buf, folder.c_str())==0)
1970 return true;
1972 return false;
1975 bool CShellExt::InsertIgnoreSubmenus(UINT &idCmd, UINT idCmdFirst, HMENU hMenu, HMENU subMenu, UINT &indexMenu, int &indexSubMenu, unsigned __int64 topmenu, bool bShowIcons, UINT /*uFlags*/)
1977 HMENU ignoresubmenu = NULL;
1978 int indexignoresub = 0;
1979 bool bShowIgnoreMenu = false;
1980 TCHAR maskbuf[MAX_PATH] = {0}; // MAX_PATH is ok, since this only holds a filename
1981 TCHAR ignorepath[MAX_PATH] = {0}; // MAX_PATH is ok, since this only holds a filename
1982 if (files_.empty())
1983 return false;
1984 UINT icon = bShowIcons ? IDI_IGNORE : 0;
1986 std::vector<stdstring>::iterator I = files_.begin();
1987 if (_tcsrchr(I->c_str(), '\\'))
1988 _tcscpy_s(ignorepath, MAX_PATH, _tcsrchr(I->c_str(), '\\')+1);
1989 else
1990 _tcscpy_s(ignorepath, MAX_PATH, I->c_str());
1991 if ((itemStates & ITEMIS_IGNORED) && (!ignoredprops.empty()))
1993 // check if the item name is ignored or the mask
1994 size_t p = 0;
1995 while ( (p=ignoredprops.find( ignorepath,p )) != -1 )
1997 if ( (p==0 || ignoredprops[p-1]==TCHAR('\n'))
1998 && (p+_tcslen(ignorepath)==ignoredprops.length() || ignoredprops[p+_tcslen(ignorepath)+1]==TCHAR('\n')) )
2000 break;
2002 p++;
2004 if (p!=-1)
2006 ignoresubmenu = CreateMenu();
2007 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2008 stdstring verb = stdstring(ignorepath);
2009 myVerbsMap[verb] = idCmd - idCmdFirst;
2010 myVerbsMap[verb] = idCmd;
2011 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2012 myVerbsIDMap[idCmd] = verb;
2013 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnore;
2014 myIDMap[idCmd++] = ShellMenuUnIgnore;
2015 bShowIgnoreMenu = true;
2017 _tcscpy_s(maskbuf, MAX_PATH, _T("*"));
2018 if (_tcsrchr(ignorepath, '.'))
2020 _tcscat_s(maskbuf, MAX_PATH, _tcsrchr(ignorepath, '.'));
2021 p = ignoredprops.find(maskbuf);
2022 if ((p!=-1) &&
2023 ((ignoredprops.compare(maskbuf)==0) || (ignoredprops.find('\n', p)==p+_tcslen(maskbuf)+1) || (ignoredprops.rfind('\n', p)==p-1)))
2025 if (ignoresubmenu==NULL)
2026 ignoresubmenu = CreateMenu();
2028 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2029 stdstring verb = stdstring(maskbuf);
2030 myVerbsMap[verb] = idCmd - idCmdFirst;
2031 myVerbsMap[verb] = idCmd;
2032 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2033 myVerbsIDMap[idCmd] = verb;
2034 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreCaseSensitive;
2035 myIDMap[idCmd++] = ShellMenuUnIgnoreCaseSensitive;
2036 bShowIgnoreMenu = true;
2040 else if ((itemStates & ITEMIS_IGNORED) == 0)
2042 bShowIgnoreMenu = true;
2043 ignoresubmenu = CreateMenu();
2044 if (itemStates & ITEMIS_ONLYONE)
2046 if (itemStates & ITEMIS_INGIT)
2048 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2049 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
2050 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
2052 _tcscpy_s(maskbuf, MAX_PATH, _T("*"));
2053 if (!(itemStates & ITEMIS_FOLDER) && _tcsrchr(ignorepath, '.'))
2055 _tcscat_s(maskbuf, MAX_PATH, _tcsrchr(ignorepath, '.'));
2056 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2057 stdstring verb = stdstring(maskbuf);
2058 myVerbsMap[verb] = idCmd - idCmdFirst;
2059 myVerbsMap[verb] = idCmd;
2060 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2061 myVerbsIDMap[idCmd] = verb;
2062 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
2063 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
2066 else
2068 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2069 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
2070 myIDMap[idCmd++] = ShellMenuIgnore;
2072 _tcscpy_s(maskbuf, MAX_PATH, _T("*"));
2073 if (!(itemStates & ITEMIS_FOLDER) && _tcsrchr(ignorepath, '.'))
2075 _tcscat_s(maskbuf, MAX_PATH, _tcsrchr(ignorepath, '.'));
2076 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2077 stdstring verb = stdstring(maskbuf);
2078 myVerbsMap[verb] = idCmd - idCmdFirst;
2079 myVerbsMap[verb] = idCmd;
2080 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2081 myVerbsIDMap[idCmd] = verb;
2082 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2083 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2087 else
2089 if (itemStates & ITEMIS_INGIT)
2091 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE);
2092 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2093 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2094 stdstring verb = stdstring(ignorepath);
2095 myVerbsMap[verb] = idCmd - idCmdFirst;
2096 myVerbsMap[verb] = idCmd;
2097 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2098 myVerbsIDMap[idCmd] = verb;
2099 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
2100 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
2102 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK);
2103 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2104 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2105 verb = stdstring(ignorepath);
2106 myVerbsMap[verb] = idCmd - idCmdFirst;
2107 myVerbsMap[verb] = idCmd;
2108 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2109 myVerbsIDMap[idCmd] = verb;
2110 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
2111 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
2113 else
2115 MAKESTRING(IDS_MENUIGNOREMULTIPLE);
2116 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2117 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2118 stdstring verb = stdstring(ignorepath);
2119 myVerbsMap[verb] = idCmd - idCmdFirst;
2120 myVerbsMap[verb] = idCmd;
2121 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2122 myVerbsIDMap[idCmd] = verb;
2123 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
2124 myIDMap[idCmd++] = ShellMenuIgnore;
2126 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK);
2127 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2128 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2129 verb = stdstring(ignorepath);
2130 myVerbsMap[verb] = idCmd - idCmdFirst;
2131 myVerbsMap[verb] = idCmd;
2132 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2133 myVerbsIDMap[idCmd] = verb;
2134 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2135 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2140 if (bShowIgnoreMenu)
2142 MENUITEMINFO menuiteminfo;
2143 SecureZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
2144 menuiteminfo.cbSize = sizeof(menuiteminfo);
2145 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
2146 if (icon)
2148 menuiteminfo.fMask |= MIIM_BITMAP;
2149 menuiteminfo.hbmpItem = SysInfo::Instance().IsVistaOrLater() ? m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, icon) : m_iconBitmapUtils.IconToBitmap(g_hResInst, icon);
2151 menuiteminfo.fType = MFT_STRING;
2152 menuiteminfo.hSubMenu = ignoresubmenu;
2153 menuiteminfo.wID = idCmd;
2154 SecureZeroMemory(stringtablebuffer, sizeof(stringtablebuffer));
2155 if (itemStates & ITEMIS_IGNORED)
2156 GetMenuTextFromResource(ShellMenuUnIgnoreSub);
2157 else if (itemStates & ITEMIS_INGIT)
2158 GetMenuTextFromResource(ShellMenuDeleteIgnoreSub);
2159 else
2160 GetMenuTextFromResource(ShellMenuIgnoreSub);
2161 menuiteminfo.dwTypeData = stringtablebuffer;
2162 menuiteminfo.cch = (UINT)min(_tcslen(menuiteminfo.dwTypeData), UINT_MAX);
2164 InsertMenuItem((topmenu & MENUIGNORE) ? hMenu : subMenu, (topmenu & MENUIGNORE) ? indexMenu++ : indexSubMenu++, TRUE, &menuiteminfo);
2165 if (itemStates & ITEMIS_IGNORED)
2167 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreSub;
2168 myIDMap[idCmd++] = ShellMenuUnIgnoreSub;
2170 else
2172 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreSub;
2173 myIDMap[idCmd++] = ShellMenuIgnoreSub;
2176 return bShowIgnoreMenu;
2179 void CShellExt::RunCommand(const tstring& path, const tstring& command, LPCTSTR errorMessage)
2181 if (CCreateProcessHelper::CreateProcessDetached(path.c_str(), const_cast<TCHAR*>(command.c_str())))
2183 // process started - exit
2184 return;
2187 MessageBox(NULL, CFormatMessageWrapper(), errorMessage, MB_OK | MB_ICONINFORMATION);
2190 bool CShellExt::ShouldInsertItem(const MenuInfo& item) const
2192 return ShouldEnableMenu(item.first) || ShouldEnableMenu(item.second) ||
2193 ShouldEnableMenu(item.third) || ShouldEnableMenu(item.fourth);
2196 bool CShellExt::ShouldEnableMenu(const YesNoPair& pair) const
2198 if (pair.yes && pair.no)
2200 if (((pair.yes & itemStates) == pair.yes) && ((pair.no & (~itemStates)) == pair.no))
2201 return true;
2203 else if ((pair.yes) && ((pair.yes & itemStates) == pair.yes))
2204 return true;
2205 else if ((pair.no) && ((pair.no & (~itemStates)) == pair.no))
2206 return true;
2207 return false;