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.
22 #include "ItemIDList.h"
23 #include "PreserveChdir.h"
24 #include "UnicodeUtils.h"
25 #include "GitStatus.h"
27 #include "PathUtils.h"
28 #include "CreateProcessHelper.h"
29 #include "FormatMessageWrapper.h"
30 #include "..\TGitCache\CacheInterface.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
,
47 return Initialize_Wrap(pIDFolder
, pDataObj
, hRegKey
);
49 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
55 STDMETHODIMP
CShellExt::Initialize_Wrap(LPCITEMIDLIST pIDFolder
,
56 LPDATAOBJECT pDataObj
,
59 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: Initialize\n");
60 PreserveChdir preserveChdir
;
68 git_wc_status_kind fetchedstatus
= git_wc_status_none
;
69 // get selected files/folders
73 FORMATETC fmte
= {(CLIPFORMAT
)g_shellidlist
,
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
);
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
);
99 HDROP drop
= (HDROP
)GlobalLock(stg
.hGlobal
);
102 ReleaseStgMedium ( &stg
);
103 ReleaseStgMedium ( &medium
);
107 int count
= DragQueryFile(drop
, (UINT
)-1, nullptr, 0);
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);
116 auto szFileName
= std::make_unique
<TCHAR
[]>(len
+ 1);
117 if (0 == DragQueryFile(drop
, i
, szFileName
.get(), len
+ 1))
119 tstring str
= tstring(szFileName
.get());
120 if ((!str
.empty()) && (g_ShellCache
.IsContextPathAllowed(szFileName
.get())))
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
);
131 //get the git status of the item
132 git_wc_status_kind status
= git_wc_status_none
;
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
;
148 if (tpath
.IsAdminDir())
150 status
= git_wc_status_none
;
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
;
169 stat
.GetStatus(CTGitPath(str
.c_str()), false, false, true);
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);
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
;
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)
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()))
241 files_
.push_back(str
);
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;
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
;
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
;
267 if(tpath
.IsAdminDir())
269 status
= git_wc_status_none
;
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
;
290 if (strpath
.HasAdminDir())
291 stat
.GetStatus(strpath
, false, false, true);
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);
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
;
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"));
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
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
;
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
;
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
);
419 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
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);
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
;
455 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
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
;
469 itemStates
|= ITEMIS_ONLYONE
;
470 if (m_State
!= FileStateDropHandler
)
471 itemStates
|= itemStatesFolder
;
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
;
491 askedpath
.SetFromWin(folder_
.c_str());
493 if (folder_
.compare(statuspath
)!=0)
498 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
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);
508 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
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
;
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
);
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
)
564 CTGitPath
path(folder_
.empty() ? files_
.front().c_str() : folder_
.c_str());
565 CString sProjectRoot
;
568 if (path
.GetAdminDirMask() & ITEMIS_SUBMODULE
)
571 _tcscpy_s(menutextbuffer
, 255, _T("Git "));
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
584 for (int i
=0; i
<40; i
++)
585 if ( !iswxdigit(sBranchName
[i
]) )
591 sBranchName
= sBranchName
.Left(8) + _T("....");
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
)
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
;
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
);
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
;
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
);
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
;
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.
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(),
693 FILE_ATTRIBUTE_TEMPORARY
,
699 if (!IsClipboardFormatAvailable(CF_HDROP
))
701 if (!OpenClipboard(nullptr))
704 tstring sClipboardText
;
705 HGLOBAL hglb
= GetClipboardData(CF_HDROP
);
711 HDROP hDrop
= (HDROP
)GlobalLock(hglb
);
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);
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(),
745 FILE_ATTRIBUTE_TEMPORARY
,
750 MessageBox(nullptr, L
"Could not create temporary file. Please check (the permissions of) your temp-folder: " + CString(tempFile
.get()), L
"TortoiseGit", MB_ICONERROR
);
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);
769 STDMETHODIMP
CShellExt::QueryDropContext(UINT uFlags
, UINT idCmdFirst
, HMENU hMenu
, UINT
&indexMenu
)
771 if (!CRegStdDWORD(L
"Software\\TortoiseGit\\EnableDragContextMenu", TRUE
))
774 PreserveChdir preserveChdir
;
777 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
778 return S_OK
; //we don't change the default action
780 if (files_
.empty() || folder_
.empty())
783 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
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;
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;
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);
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
);
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);
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
);
847 if (idCmd
!= idCmdFirst
)
848 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr);
852 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
855 STDMETHODIMP
CShellExt::QueryContextMenu(HMENU hMenu
,
863 return QueryContextMenu_Wrap(hMenu
, indexMenu
, idCmdFirst
, idCmdLast
, uFlags
);
865 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
871 STDMETHODIMP
CShellExt::QueryContextMenu_Wrap(HMENU hMenu
,
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
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())
894 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
901 CSIDL_COMMON_FAVORITES
,
902 CSIDL_COMMON_STARTMENU
,
903 CSIDL_COMPUTERSNEARME
,
911 CSIDL_INTERNET_CACHE
,
921 if (IsIllegalFolder(folder_
, csidlarray
))
926 // folder is empty, but maybe files are selected
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)
939 if (_tcsncmp(folder_
.c_str(), _T("::{"), 3) == 0)
943 if (((uFlags
& CMF_EXTENDEDVERBS
) == 0) && g_ShellCache
.HideMenusForUnversionedItems())
945 if ((itemStates
& (ITEMIS_INGIT
| ITEMIS_INVERSIONEDFOLDER
| ITEMIS_FOLDERINGIT
| ITEMIS_BAREREPO
)) == 0)
949 //check if our menu is requested for a git admin directory
950 if (GitAdminDir::IsAdminDirPath(folder_
.c_str()))
953 if (uFlags
& CMF_EXTENDEDVERBS
)
954 itemStates
|= ITEMIS_EXTENDED
;
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
))
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
)
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();
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.
1020 bAddSeparator
= true;
1021 if (bMenuEntryAdded
)
1022 bAddSeparator
= true;
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
))
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);
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;
1056 bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1058 // insert the menu entry
1059 InsertGitMenu( bIsTop
,
1060 bIsTop
? hMenu
: subMenu
,
1061 bIsTop
? indexMenu
++ : indexSubMenu
++,
1063 menuInfo
[menuIndex
].menuTextID
,
1064 bShowIcons
? menuInfo
[menuIndex
].iconID
: 0,
1066 menuInfo
[menuIndex
].command
,
1070 bMenuEntryAdded
= true;
1072 bAddSeparator
= false;
1081 // do not show TortoiseGit menu if it's empty
1084 if (idCmd
- idCmdFirst
> 0)
1087 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
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
;
1139 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenu
;
1140 myIDMap
[idCmd
] = ShellSubMenu
;
1142 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
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
);
1153 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
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
)
1173 gitCmd
+= _T(" /path:\"");
1174 if ((bFilesAllowed
) && !files_
.empty())
1175 gitCmd
+= files_
.front();
1181 void CShellExt::AddPathFileCommand(tstring
& gitCmd
, LPCTSTR command
)
1183 tstring tempfile
= WriteFileListToTempFile();
1185 gitCmd
+= _T(" /pathfile:\"");
1188 gitCmd
+= _T(" /deletepathfile");
1191 void CShellExt::AddPathFileDropCommand(tstring
& gitCmd
, LPCTSTR command
)
1193 tstring tempfile
= WriteFileListToTempFile();
1195 gitCmd
+= _T(" /pathfile:\"");
1198 gitCmd
+= _T(" /deletepathfile");
1199 gitCmd
+= _T(" /droptarget:\"");
1204 STDMETHODIMP
CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi
)
1208 return InvokeCommand_Wrap(lpcmi
);
1210 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
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
;
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
;
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
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:");
1254 switch (id_it
->second
)
1259 TCHAR syncSeq
[12] = { 0 };
1260 _stprintf_s(syncSeq
, _T("%d"), g_syncSeq
++);
1261 AddPathCommand(gitCmd
, L
"sync", false);
1262 gitCmd
+= _T(" /seq:");
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:\"");
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:\"");
1284 case ShellMenuCommit
:
1285 AddPathFileCommand(gitCmd
, L
"commit");
1288 AddPathFileCommand(gitCmd
, L
"add");
1290 case ShellMenuIgnore
:
1291 AddPathFileCommand(gitCmd
, L
"ignore");
1293 case ShellMenuIgnoreCaseSensitive
:
1294 AddPathFileCommand(gitCmd
, L
"ignore");
1295 gitCmd
+= _T(" /onlymask");
1297 case ShellMenuDeleteIgnore
:
1298 AddPathFileCommand(gitCmd
, L
"ignore");
1299 gitCmd
+= _T(" /delete");
1301 case ShellMenuDeleteIgnoreCaseSensitive
:
1302 AddPathFileCommand(gitCmd
, L
"ignore");
1303 gitCmd
+= _T(" /delete /onlymask");
1305 case ShellMenuUnIgnore
:
1306 AddPathFileCommand(gitCmd
, L
"unignore");
1308 case ShellMenuUnIgnoreCaseSensitive
:
1309 AddPathFileCommand(gitCmd
, L
"unignore");
1310 gitCmd
+= _T(" /onlymask");
1312 case ShellMenuMergeAbort
:
1313 AddPathCommand(gitCmd
, L
"merge", false);
1314 gitCmd
+= _T(" /abort");
1316 case ShellMenuRevert
:
1317 AddPathFileCommand(gitCmd
, L
"revert");
1319 case ShellMenuCleanup
:
1320 AddPathFileCommand(gitCmd
, L
"cleanup");
1322 case ShellMenuSendMail
:
1323 AddPathFileCommand(gitCmd
, L
"sendmail");
1325 case ShellMenuResolve
:
1326 AddPathFileCommand(gitCmd
, L
"resolve");
1328 case ShellMenuSwitch
:
1329 AddPathCommand(gitCmd
, L
"switch", false);
1331 case ShellMenuExport
:
1332 AddPathCommand(gitCmd
, L
"export", false);
1334 case ShellMenuAbout
:
1335 gitCmd
+= _T("about");
1337 case ShellMenuCreateRepos
:
1338 AddPathCommand(gitCmd
, L
"repocreate", false);
1340 case ShellMenuMerge
:
1341 AddPathCommand(gitCmd
, L
"merge", false);
1344 AddPathCommand(gitCmd
, L
"copy", true);
1346 case ShellMenuSettings
:
1347 AddPathCommand(gitCmd
, L
"settings", true);
1350 gitCmd
+= _T("help");
1352 case ShellMenuRename
:
1353 AddPathCommand(gitCmd
, L
"rename", true);
1355 case ShellMenuRemove
:
1356 AddPathFileCommand(gitCmd
, L
"remove");
1358 case ShellMenuRemoveKeep
:
1359 AddPathFileCommand(gitCmd
, L
"remove");
1360 gitCmd
+= _T(" /keep");
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();
1371 gitCmd
+= _T("\" /path2:\"");
1377 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1378 gitCmd
+= _T(" /alternative");
1380 case ShellMenuDiffLater
:
1381 if (lpcmi
->fMask
& CMIC_MASK_CONTROL_DOWN
)
1384 regDiffLater
.removeValue();
1386 else if (files_
.size() == 1)
1388 if (std::wstring(regDiffLater
).empty())
1391 regDiffLater
= files_
[0];
1395 AddPathCommand(gitCmd
, L
"diff", true);
1396 gitCmd
+= _T(" /path2:\"");
1397 gitCmd
+= std::wstring(regDiffLater
);
1399 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1400 gitCmd
+= _T(" /alternative");
1401 regDiffLater
.removeValue();
1407 case ShellMenuPrevDiff
:
1408 AddPathCommand(gitCmd
, L
"prevdiff", true);
1409 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1410 gitCmd
+= _T(" /alternative");
1412 case ShellMenuDiffTwo
:
1413 AddPathCommand(gitCmd
, L
"diffcommits", true);
1415 case ShellMenuDropCopyAdd
:
1416 AddPathFileDropCommand(gitCmd
, L
"dropcopyadd");
1418 case ShellMenuDropCopy
:
1419 AddPathFileDropCommand(gitCmd
, L
"dropcopy");
1421 case ShellMenuDropCopyRename
:
1422 AddPathFileDropCommand(gitCmd
, L
"dropcopy");
1423 gitCmd
+= _T("\" /rename";)
1425 case ShellMenuDropMove
:
1426 AddPathFileDropCommand(gitCmd
, L
"dropmove");
1428 case ShellMenuDropMoveRename
:
1429 AddPathFileDropCommand(gitCmd
, L
"dropmove");
1430 gitCmd
+= _T("\" /rename";)
1432 case ShellMenuDropExport
:
1433 AddPathFileDropCommand(gitCmd
, L
"dropexport");
1435 case ShellMenuDropExportExtended
:
1436 AddPathFileDropCommand(gitCmd
, L
"dropexport");
1437 gitCmd
+= _T(" /extended");
1440 case ShellMenuLogSubmoduleFolder
:
1441 AddPathCommand(gitCmd
, L
"log", true);
1442 if (id_it
->second
== ShellMenuLogSubmoduleFolder
)
1443 gitCmd
+= _T(" /submodule");
1445 case ShellMenuDaemon
:
1446 AddPathCommand(gitCmd
, L
"daemon", true);
1448 case ShellMenuRevisionGraph
:
1449 AddPathCommand(gitCmd
, L
"revisiongraph", true);
1451 case ShellMenuConflictEditor
:
1452 AddPathCommand(gitCmd
, L
"conflicteditor", true);
1453 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1454 gitCmd
+= L
" /alternative";
1456 case ShellMenuGitSVNRebase
:
1457 AddPathCommand(gitCmd
, L
"svnrebase", false);
1459 case ShellMenuGitSVNDCommit
:
1460 AddPathCommand(gitCmd
, L
"svndcommit", true);
1462 case ShellMenuGitSVNDFetch
:
1463 AddPathCommand(gitCmd
, L
"svnfetch", false);
1465 case ShellMenuGitSVNIgnore
:
1466 AddPathCommand(gitCmd
, L
"svnignore", false);
1468 case ShellMenuRebase
:
1469 AddPathCommand(gitCmd
, L
"rebase", false);
1471 case ShellMenuShowChanged
:
1472 if (files_
.size() > 1)
1473 AddPathFileCommand(gitCmd
, L
"repostatus");
1475 AddPathCommand(gitCmd
, L
"repostatus", true);
1477 case ShellMenuRepoBrowse
:
1478 AddPathCommand(gitCmd
, L
"repobrowser", false);
1480 case ShellMenuRefBrowse
:
1481 AddPathCommand(gitCmd
, L
"refbrowse", false);
1483 case ShellMenuRefLog
:
1484 AddPathCommand(gitCmd
, L
"reflog", false);
1486 case ShellMenuStashSave
:
1487 AddPathCommand(gitCmd
, L
"stashsave", true);
1489 case ShellMenuStashApply
:
1490 AddPathCommand(gitCmd
, L
"stashapply", false);
1492 case ShellMenuStashPop
:
1493 AddPathCommand(gitCmd
, L
"stashpop", false);
1495 case ShellMenuStashList
:
1496 AddPathCommand(gitCmd
, L
"reflog", false);
1497 gitCmd
+= _T(" /ref:refs/stash");
1499 case ShellMenuBisectStart
:
1500 AddPathCommand(gitCmd
, L
"bisect", false);
1501 gitCmd
+= _T("\" /start");
1503 case ShellMenuBisectGood
:
1504 AddPathCommand(gitCmd
, L
"bisect", false);
1505 gitCmd
+= _T("\" /good");
1507 case ShellMenuBisectBad
:
1508 AddPathCommand(gitCmd
, L
"bisect", false);
1509 gitCmd
+= _T("\" /bad");
1511 case ShellMenuBisectReset
:
1512 AddPathCommand(gitCmd
, L
"bisect", false);
1513 gitCmd
+= _T("\" /reset");
1515 case ShellMenuSubAdd
:
1516 AddPathCommand(gitCmd
, L
"subadd", false);
1518 case ShellMenuBlame
:
1519 AddPathCommand(gitCmd
, L
"blame", true);
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());
1540 size_t patchlen
= strlen(lpstr
);
1541 _tfopen_s(&outFile
, sTempFile
.c_str(), _T("wb"));
1544 size_t size
= fwrite(lpstr
, sizeof(char), patchlen
, outFile
);
1545 if (size
== patchlen
)
1547 itemStates
|= ITEMIS_PATCHFILE
;
1549 files_
.push_back(sTempFile
);
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:\"");
1571 if (itemStates
& ITEMIS_INVERSIONEDFOLDER
)
1572 gitCmd
+= _T("\" /wc");
1578 gitCmd
= _T(" /patchpath:\"");
1579 if (!files_
.empty())
1580 gitCmd
+= files_
.front();
1586 myVerbsIDMap
.clear();
1588 RunCommand(tortoiseMergePath
, gitCmd
, _T("TortoiseGitMerge launch failed"));
1591 case ShellMenuClipPaste
:
1592 if (WriteClipboardPathsToTempFile(tempfile
))
1595 UINT cPrefDropFormat
= RegisterClipboardFormat(_T("Preferred DropEffect"));
1596 if (cPrefDropFormat
)
1598 if (OpenClipboard(lpcmi
->hwnd
))
1600 HGLOBAL hglb
= GetClipboardData(cPrefDropFormat
);
1603 DWORD
* effect
= (DWORD
*) GlobalLock(hglb
);
1604 if (*effect
== DROPEFFECT_MOVE
)
1613 gitCmd
+= _T("pastecopy /pathfile:\"");
1615 gitCmd
+= _T("pastemove /pathfile:\"");
1618 gitCmd
+= _T(" /deletepathfile");
1619 gitCmd
+= _T(" /droptarget:\"");
1625 case ShellMenuClone
:
1626 AddPathCommand(gitCmd
, L
"clone", false);
1629 AddPathCommand(gitCmd
, L
"pull", false);
1632 AddPathCommand(gitCmd
, L
"push", false);
1634 case ShellMenuBranch
:
1635 AddPathCommand(gitCmd
, L
"branch", false);
1638 AddPathCommand(gitCmd
, L
"tag", false);
1640 case ShellMenuFormatPatch
:
1641 AddPathCommand(gitCmd
, L
"formatpatch", false);
1643 case ShellMenuImportPatch
:
1644 AddPathFileCommand(gitCmd
, L
"importpatch");
1646 case ShellMenuImportPatchDrop
:
1647 AddPathFileDropCommand(gitCmd
, L
"importpatch");
1649 case ShellMenuFetch
:
1650 AddPathCommand(gitCmd
, L
"fetch", false);
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
);
1664 myVerbsIDMap
.clear();
1666 RunCommand(tortoiseProcPath
, gitCmd
, _T("TortoiseProc launch failed"));
1669 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1670 } // if (files_.empty() || folder_.empty())
1674 // This is for the status bar and things like that:
1675 STDMETHODIMP
CShellExt::GetCommandString(UINT_PTR idCmd
,
1677 UINT FAR
* reserved
,
1683 return GetCommandString_Wrap(idCmd
, uFlags
, reserved
, pszName
, cchMax
);
1685 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1691 // This is for the status bar and things like that:
1692 STDMETHODIMP
CShellExt::GetCommandString_Wrap(UINT_PTR idCmd
,
1694 UINT FAR
* /*reserved*/,
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
1707 HRESULT hr
= E_INVALIDARG
;
1709 MAKESTRING(IDS_MENUDESCDEFAULT
);
1711 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1713 if (menuInfo
[menuIndex
].command
== (GitCommands
)id_it
->second
)
1715 MAKESTRING(menuInfo
[menuIndex
].menuDescID
);
1721 const TCHAR
* desc
= stringtablebuffer
;
1726 std::string help
= WideToMultibyte(desc
);
1727 lstrcpynA(pszName
, help
.c_str(), cchMax
- 1);
1733 std::wstring help
= desc
;
1734 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
- 1);
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);
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);
1765 STDMETHODIMP
CShellExt::HandleMenuMsg(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
1769 return HandleMenuMsg_Wrap(uMsg
, wParam
, lParam
);
1771 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1777 STDMETHODIMP
CShellExt::HandleMenuMsg_Wrap(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
1780 return HandleMenuMsg2(uMsg
, wParam
, lParam
, &res
);
1783 STDMETHODIMP
CShellExt::HandleMenuMsg2(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*pResult
)
1787 return HandleMenuMsg2_Wrap(uMsg
, wParam
, lParam
, pResult
);
1789 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1795 STDMETHODIMP
CShellExt::HandleMenuMsg2_Wrap(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*pResult
)
1797 PreserveChdir preserveChdir
;
1807 case WM_MEASUREITEM
:
1809 MEASUREITEMSTRUCT
* lpmis
= (MEASUREITEMSTRUCT
*)lParam
;
1812 lpmis
->itemWidth
= 16;
1813 lpmis
->itemHeight
= 16;
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
]);
1826 HICON hIcon
= (HICON
)LoadImage(g_hResInst
, resource
, IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
1829 DrawIconEx(lpdis
->hDC
,
1831 lpdis
->rcItem
.top
+ (lpdis
->rcItem
.bottom
- lpdis
->rcItem
.top
- 16) / 2,
1833 0, nullptr, DI_NORMAL
);
1841 if (HIWORD(wParam
) != MF_POPUP
)
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
1848 std::vector
<UINT_PTR
> accmenus
;
1849 for (auto It
= mySubMenuMap
.cbegin(); It
!= mySubMenuMap
.cend(); ++It
)
1851 LPCTSTR resource
= GetMenuTextFromResource((int)mySubMenuMap
[It
->first
]);
1854 szItem
= stringtablebuffer
;
1855 TCHAR
* amp
= _tcschr(szItem
, '&');
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
1867 accmenus
.push_back(It
->first
);
1870 if (accmenus
.empty())
1872 // no menu with that accelerator key.
1873 *pResult
= MAKELONG(0, MNC_IGNORE
);
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
);
1883 if (accmenus
.size() > 1)
1885 // we have more than one menu item with this accelerator key!
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
1896 if (it
== accmenus
.end())
1897 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
1899 *pResult
= MAKELONG(*it
, MNC_SELECT
);
1903 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
1914 LPCTSTR
CShellExt::GetMenuTextFromResource(int id
)
1916 TCHAR textbuf
[255] = { 0 };
1917 LPCTSTR resource
= nullptr;
1918 unsigned __int64 layout
= g_ShellCache
.GetMenuLayout();
1922 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1924 if (menuInfo
[menuIndex
].command
== id
)
1926 MAKESTRING(menuInfo
[menuIndex
].menuTextID
);
1927 resource
= MAKEINTRESOURCE(menuInfo
[menuIndex
].iconID
);
1930 case ShellSubMenuMultiple
:
1931 case ShellSubMenuLink
:
1932 case ShellSubMenuFolder
:
1933 case ShellSubMenuFile
:
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
);
1954 bool CShellExt::IsIllegalFolder(std::wstring folder
, int * cslidarray
)
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
])
1963 if (SHGetFolderLocation(nullptr, cslidarray
[i
- 1], nullptr, 0, &pidl
) != S_OK
)
1965 if (!SHGetPathFromIDList(pidl
, buf
))
1967 // not a file system path, definitely illegal for our use
1968 CoTaskMemFree(pidl
);
1971 CoTaskMemFree(pidl
);
1972 if (_tcslen(buf
)==0)
1974 if (_tcscmp(buf
, folder
.c_str())==0)
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
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);
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
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')) )
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
);
2028 ((ignoredprops
.compare(maskbuf
)==0) || (ignoredprops
.find('\n', p
)==p
+_tcslen(maskbuf
)+1) || (ignoredprops
.rfind('\n', p
)==p
-1)))
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
;
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
;
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
);
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
);
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
;
2140 if (files_
.size() >= 16)
2142 MAKESTRING(IDS_MENUIGNOREMULTIPLE2
);
2143 wcscpy_s(ignorepath
, stringtablebuffer
);
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
);
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
;
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
);
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
;
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
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
))
2243 else if ((pair
.yes
) && ((pair
.yes
& itemStates
) == pair
.yes
))
2245 else if ((pair
.no
) && ((pair
.no
& (~itemStates
)) == pair
.no
))