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"
26 #include "GitStatus.h"
28 #include "PathUtils.h"
29 #include "CreateProcessHelper.h"
30 #include "FormatMessageWrapper.h"
31 #include "..\TGitCache\CacheInterface.h"
34 #define GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
35 #define GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
37 int g_shellidlist
=RegisterClipboardFormat(CFSTR_SHELLIDLIST
);
39 extern MenuInfo menuInfo
[];
40 static int g_syncSeq
= 0;
42 STDMETHODIMP
CShellExt::Initialize(LPCITEMIDLIST pIDFolder
,
43 LPDATAOBJECT pDataObj
,
48 return Initialize_Wrap(pIDFolder
, pDataObj
, hRegKey
);
50 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
56 STDMETHODIMP
CShellExt::Initialize_Wrap(LPCITEMIDLIST pIDFolder
,
57 LPDATAOBJECT pDataObj
,
60 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: Initialize\n");
61 PreserveChdir preserveChdir
;
68 std::wstring statuspath
;
69 git_wc_status_kind fetchedstatus
= git_wc_status_none
;
70 // get selected files/folders
74 FORMATETC fmte
= {(CLIPFORMAT
)g_shellidlist
,
79 HRESULT hres
= pDataObj
->GetData(&fmte
, &medium
);
81 if (SUCCEEDED(hres
) && medium
.hGlobal
)
83 if (m_State
== FileStateDropHandler
)
85 if (!CRegStdDWORD(L
"Software\\TortoiseGit\\EnableDragContextMenu", TRUE
, false, HKEY_CURRENT_USER
, KEY_WOW64_64KEY
))
87 ReleaseStgMedium(&medium
);
91 FORMATETC etc
= { CF_HDROP
, nullptr, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
};
92 STGMEDIUM stg
= { TYMED_HGLOBAL
};
93 if ( FAILED( pDataObj
->GetData ( &etc
, &stg
)))
95 ReleaseStgMedium ( &medium
);
100 HDROP drop
= (HDROP
)GlobalLock(stg
.hGlobal
);
103 ReleaseStgMedium ( &stg
);
104 ReleaseStgMedium ( &medium
);
108 int count
= DragQueryFile(drop
, (UINT
)-1, nullptr, 0);
110 itemStates
|= ITEMIS_ONLYONE
;
111 for (int i
= 0; i
< count
; i
++)
113 // find the path length in chars
114 UINT len
= DragQueryFile(drop
, i
, nullptr, 0);
117 auto szFileName
= std::make_unique
<TCHAR
[]>(len
+ 1);
118 if (0 == DragQueryFile(drop
, i
, szFileName
.get(), len
+ 1))
120 auto str
= std::wstring(szFileName
.get());
121 if ((!str
.empty()) && (g_ShellCache
.IsContextPathAllowed(szFileName
.get())))
125 strpath
.SetFromWin(str
.c_str());
126 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".diff") == 0) ? ITEMIS_PATCHFILE
: 0;
127 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".patch") == 0) ? ITEMIS_PATCHFILE
: 0;
129 files_
.push_back(str
);
132 //get the git status of the item
133 git_wc_status_kind status
= git_wc_status_none
;
135 askedpath
.SetFromWin(str
.c_str());
136 CString workTreePath
;
137 askedpath
.HasAdminDir(&workTreePath
);
138 uuidSource
= workTreePath
;
141 if (g_ShellCache
.GetCacheType() == ShellCache::exe
&& g_ShellCache
.IsPathAllowed(str
.c_str()))
143 CTGitPath
tpath(str
.c_str());
144 if (!tpath
.HasAdminDir())
146 status
= git_wc_status_none
;
149 if (tpath
.IsAdminDir())
151 status
= git_wc_status_none
;
154 TGITCacheResponse itemStatus
;
155 SecureZeroMemory(&itemStatus
, sizeof(itemStatus
));
156 if (m_remoteCacheLink
.GetStatusFromRemoteCache(tpath
, &itemStatus
, true))
158 fetchedstatus
= status
= GitStatus::GetMoreImportant(itemStatus
.m_status
.text_status
, itemStatus
.m_status
.prop_status
);
159 if (askedpath
.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
161 itemStates
|= ITEMIS_FOLDER
;
162 if ((status
!= git_wc_status_unversioned
) && (status
!= git_wc_status_ignored
) && (status
!= git_wc_status_none
))
163 itemStates
|= ITEMIS_FOLDERINGIT
;
170 stat
.GetStatus(CTGitPath(str
.c_str()), false, false, true);
174 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
175 fetchedstatus
= status
;
176 if ( askedpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
178 itemStates
|= ITEMIS_FOLDER
;
179 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
180 itemStates
|= ITEMIS_FOLDERINGIT
;
182 //if ((stat.status->entry)&&(stat.status->entry->uuid))
183 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
187 // sometimes, git_client_status() returns with an error.
188 // in that case, we have to check if the working copy is versioned
189 // anyway to show the 'correct' context menu
190 if (askedpath
.HasAdminDir())
191 status
= git_wc_status_normal
;
197 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
200 // TODO: should we really assume any sub-directory to be versioned
201 // or only if it contains versioned files
202 itemStates
|= askedpath
.GetAdminDirMask();
204 if ((status
== git_wc_status_unversioned
) || (status
== git_wc_status_ignored
) || (status
== git_wc_status_none
))
205 itemStates
&= ~ITEMIS_INGIT
;
207 if (status
== git_wc_status_ignored
)
208 itemStates
|= ITEMIS_IGNORED
;
209 if (status
== git_wc_status_normal
)
210 itemStates
|= ITEMIS_NORMAL
;
211 if (status
== git_wc_status_conflicted
)
212 itemStates
|= ITEMIS_CONFLICTED
;
213 if (status
== git_wc_status_added
)
214 itemStates
|= ITEMIS_ADDED
;
215 if (status
== git_wc_status_deleted
)
216 itemStates
|= ITEMIS_DELETED
;
219 } // for (int i = 0; i < count; i++)
220 GlobalUnlock ( drop
);
221 ReleaseStgMedium ( &stg
);
223 } // if (m_State == FileStateDropHandler)
226 //Enumerate PIDLs which the user has selected
227 CIDA
* cida
= (CIDA
*)GlobalLock(medium
.hGlobal
);
228 ItemIDList
parent( GetPIDLFolder (cida
));
230 int count
= cida
->cidl
;
231 BOOL statfetched
= FALSE
;
232 for (int i
= 0; i
< count
; ++i
)
234 ItemIDList
child (GetPIDLItem (cida
, i
), &parent
);
235 std::wstring str
= child
.toString();
236 if ((str
.empty() == false)&&(g_ShellCache
.IsContextPathAllowed(str
.c_str())))
238 //check if our menu is requested for a git admin directory
239 if (GitAdminDir::IsAdminDirPath(str
.c_str()))
242 files_
.push_back(str
);
244 strpath
.SetFromWin(str
.c_str());
245 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".diff") == 0) ? ITEMIS_PATCHFILE
: 0;
246 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".patch") == 0) ? ITEMIS_PATCHFILE
: 0;
249 //get the git status of the item
250 git_wc_status_kind status
= git_wc_status_none
;
251 if ((g_ShellCache
.IsSimpleContext())&&(strpath
.IsDirectory()))
253 if (strpath
.HasAdminDir())
254 status
= git_wc_status_normal
;
260 if (g_ShellCache
.GetCacheType() == ShellCache::exe
&& g_ShellCache
.IsPathAllowed(str
.c_str()))
262 CTGitPath
tpath(str
.c_str());
263 if(!tpath
.HasAdminDir())
265 status
= git_wc_status_none
;
268 if(tpath
.IsAdminDir())
270 status
= git_wc_status_none
;
273 TGITCacheResponse itemStatus
;
274 SecureZeroMemory(&itemStatus
, sizeof(itemStatus
));
275 if (m_remoteCacheLink
.GetStatusFromRemoteCache(tpath
, &itemStatus
, true))
277 fetchedstatus
= status
= GitStatus::GetMoreImportant(itemStatus
.m_status
.text_status
, itemStatus
.m_status
.prop_status
);
278 if (strpath
.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
280 itemStates
|= ITEMIS_FOLDER
;
281 if ((status
!= git_wc_status_unversioned
) && (status
!= git_wc_status_ignored
) && (status
!= git_wc_status_none
))
282 itemStates
|= ITEMIS_FOLDERINGIT
;
284 if (status
== git_wc_status_conflicted
)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
285 itemStates
|= ITEMIS_CONFLICTED
;
291 if (strpath
.HasAdminDir())
292 stat
.GetStatus(strpath
, false, false, true);
296 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
297 fetchedstatus
= status
;
298 if ( strpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
300 itemStates
|= ITEMIS_FOLDER
;
301 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
302 itemStates
|= ITEMIS_FOLDERINGIT
;
304 // TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
305 if (status
== git_wc_status_conflicted
)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
306 itemStates
|= ITEMIS_CONFLICTED
;
307 //if ((stat.status->entry)&&(stat.status->entry->uuid))
308 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
312 // sometimes, git_client_status() returns with an error.
313 // in that case, we have to check if the working copy is versioned
314 // anyway to show the 'correct' context menu
315 if (strpath
.HasAdminDir())
317 status
= git_wc_status_normal
;
318 fetchedstatus
= status
;
326 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
330 itemStates
|= strpath
.GetAdminDirMask();
332 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
333 itemStates
&= ~ITEMIS_INGIT
;
334 if (status
== git_wc_status_ignored
)
336 itemStates
|= ITEMIS_IGNORED
;
339 if (status
== git_wc_status_normal
)
340 itemStates
|= ITEMIS_NORMAL
;
341 if (status
== git_wc_status_conflicted
)
342 itemStates
|= ITEMIS_CONFLICTED
;
343 if (status
== git_wc_status_added
)
344 itemStates
|= ITEMIS_ADDED
;
345 if (status
== git_wc_status_deleted
)
346 itemStates
|= ITEMIS_DELETED
;
349 } // for (int i = 0; i < count; ++i)
350 ItemIDList
child (GetPIDLItem (cida
, 0), &parent
);
351 if (g_ShellCache
.HasGITAdminDir(child
.toString().c_str(), FALSE
))
352 itemStates
|= ITEMIS_INVERSIONEDFOLDER
;
354 if (GitAdminDir::IsBareRepo(child
.toString().c_str()))
355 itemStates
= ITEMIS_BAREREPO
;
357 GlobalUnlock(medium
.hGlobal
);
359 // if the item is a versioned folder, check if there's a patch file
360 // in the clipboard to be used in "Apply Patch"
361 UINT cFormatDiff
= RegisterClipboardFormat(L
"TGIT_UNIFIEDDIFF");
364 if (IsClipboardFormatAvailable(cFormatDiff
))
365 itemStates
|= ITEMIS_PATCHINCLIPBOARD
;
367 if (IsClipboardFormatAvailable(CF_HDROP
))
368 itemStates
|= ITEMIS_PATHINCLIPBOARD
;
372 ReleaseStgMedium ( &medium
);
373 if (medium
.pUnkForRelease
)
375 IUnknown
* relInterface
= (IUnknown
*)medium
.pUnkForRelease
;
376 relInterface
->Release();
381 // get folder background
384 ItemIDList
list(pIDFolder
);
385 folder_
= list
.toString();
386 git_wc_status_kind status
= git_wc_status_none
;
387 if (IsClipboardFormatAvailable(CF_HDROP
))
388 itemStatesFolder
|= ITEMIS_PATHINCLIPBOARD
;
391 askedpath
.SetFromWin(folder_
.c_str());
393 if (g_ShellCache
.IsContextPathAllowed(folder_
.c_str()))
395 if (folder_
.compare(statuspath
)!=0)
397 CString worktreePath
;
398 askedpath
.HasAdminDir(&worktreePath
);
399 uuidTarget
= worktreePath
;
402 if (g_ShellCache
.GetCacheType() == ShellCache::exe
&& g_ShellCache
.IsPathAllowed(folder_
.c_str()))
404 CTGitPath
tpath(folder_
.c_str());
405 if(!tpath
.HasAdminDir())
406 status
= git_wc_status_none
;
407 else if(tpath
.IsAdminDir())
408 status
= git_wc_status_none
;
411 TGITCacheResponse itemStatus
;
412 SecureZeroMemory(&itemStatus
, sizeof(itemStatus
));
413 if (m_remoteCacheLink
.GetStatusFromRemoteCache(tpath
, &itemStatus
, true))
414 status
= GitStatus::GetMoreImportant(itemStatus
.m_status
.text_status
, itemStatus
.m_status
.prop_status
);
420 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
423 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
424 // if ((stat.status->entry)&&(stat.status->entry->uuid))
425 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
430 // sometimes, git_client_status() returns with an error.
431 // in that case, we have to check if the working copy is versioned
432 // anyway to show the 'correct' context menu
433 if (askedpath
.HasAdminDir())
434 status
= git_wc_status_normal
;
438 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
439 itemStatesFolder
|= askedpath
.GetAdminDirMask();
441 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
442 itemStates
&= ~ITEMIS_INGIT
;
444 if (status
== git_wc_status_normal
)
445 itemStatesFolder
|= ITEMIS_NORMAL
;
446 if (status
== git_wc_status_conflicted
)
447 itemStatesFolder
|= ITEMIS_CONFLICTED
;
448 if (status
== git_wc_status_added
)
449 itemStatesFolder
|= ITEMIS_ADDED
;
450 if (status
== git_wc_status_deleted
)
451 itemStatesFolder
|= ITEMIS_DELETED
;
456 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
461 status
= fetchedstatus
;
463 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
464 itemStatesFolder
|= askedpath
.GetAdminDirMask();
466 if (status
== git_wc_status_ignored
)
467 itemStatesFolder
|= ITEMIS_IGNORED
;
468 itemStatesFolder
|= ITEMIS_FOLDER
;
470 itemStates
|= ITEMIS_ONLYONE
;
471 if (m_State
!= FileStateDropHandler
)
472 itemStates
|= itemStatesFolder
;
477 status
= fetchedstatus
;
480 if (files_
.size() == 2)
481 itemStates
|= ITEMIS_TWO
;
482 if ((files_
.size() == 1)&&(g_ShellCache
.IsContextPathAllowed(files_
.front().c_str())))
484 itemStates
|= ITEMIS_ONLYONE
;
485 if (m_State
!= FileStateDropHandler
)
487 if (PathIsDirectory(files_
.front().c_str()))
489 folder_
= files_
.front();
490 git_wc_status_kind status
= git_wc_status_none
;
492 askedpath
.SetFromWin(folder_
.c_str());
494 if (folder_
.compare(statuspath
)!=0)
499 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
502 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
503 // if ((stat.status->entry)&&(stat.status->entry->uuid))
504 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
509 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
514 status
= fetchedstatus
;
516 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
517 itemStates
|= askedpath
.GetAdminDirMask();
519 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
520 itemStates
&= ~ITEMIS_INGIT
;
522 if (status
== git_wc_status_ignored
)
523 itemStates
|= ITEMIS_IGNORED
;
524 itemStates
|= ITEMIS_FOLDER
;
525 if (status
== git_wc_status_added
)
526 itemStates
|= ITEMIS_ADDED
;
527 if (status
== git_wc_status_deleted
)
528 itemStates
|= ITEMIS_DELETED
;
538 void CShellExt::InsertGitMenu(BOOL istop
, HMENU menu
, UINT pos
, UINT_PTR id
, UINT stringid
, UINT icon
, UINT idCmdFirst
, GitCommands com
, UINT
/*uFlags*/)
540 TCHAR menutextbuffer
[512] = {0};
541 TCHAR verbsbuffer
[255] = {0};
542 MAKESTRING(stringid
);
546 //menu entry for the top context menu, so append an "Git " before
547 //the menu text to indicate where the entry comes from
548 wcscpy_s(menutextbuffer
, 255, L
"Git ");
549 if (!g_ShellCache
.HasShellMenuAccelerators())
551 // remove the accelerators
552 tstring temp
= stringtablebuffer
;
553 temp
.erase(std::remove(temp
.begin(), temp
.end(), '&'), temp
.end());
554 wcscpy_s(stringtablebuffer
, 255, temp
.c_str());
557 wcscat_s(menutextbuffer
, 255, stringtablebuffer
);
559 // insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
560 // so we have an easy and fast way to check the current branch
561 // (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
562 if (com
== ShellMenuCommit
)
565 CTGitPath
path(folder_
.empty() ? files_
.front().c_str() : folder_
.c_str());
566 CString sProjectRoot
;
569 if (path
.GetAdminDirMask() & ITEMIS_SUBMODULE
)
572 wcscpy_s(menutextbuffer
, 255, L
"Git ");
574 menutextbuffer
[0] = L
'\0';
575 MAKESTRING(IDS_MENUCOMMITSUBMODULE
);
576 wcscat_s(menutextbuffer
, 255, stringtablebuffer
);
579 if (path
.HasAdminDir(&sProjectRoot
) && !CGit::GetCurrentBranchFromFile(sProjectRoot
, sBranchName
))
581 if (sBranchName
.GetLength() == 40)
583 // if SHA1 only show 4 first bytes
585 for (int i
=0; i
<40; i
++)
586 if ( !iswxdigit(sBranchName
[i
]) )
592 sBranchName
= sBranchName
.Left(8) + L
"...";
596 if (sBranchName
.GetLength() > 64)
597 sBranchName
= sBranchName
.Left(64) + L
"...";
599 // scan to before "..."
600 LPTSTR s
= menutextbuffer
+ wcslen(menutextbuffer
)-1;
601 if (s
> menutextbuffer
)
603 while (s
> menutextbuffer
)
618 // append branch name and end with ...
619 wcscpy_s(s
, 255 - wcslen(menutextbuffer
) - 1, L
" -> \"" + sBranchName
+ L
"\"...");
623 if (com
== ShellMenuDiffLater
)
625 std::wstring sPath
= regDiffLater
;
628 // add the path of the saved file
629 wchar_t compact
[40] = {0};
630 PathCompactPathEx(compact
, sPath
.c_str(), _countof(compact
) - 1, 0);
631 MAKESTRING(IDS_MENUDIFFNOW
);
633 sMenu
.Format(CString(stringtablebuffer
), compact
);
634 wcscpy_s(menutextbuffer
, sMenu
);
638 MENUITEMINFO menuiteminfo
= { 0 };
639 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
640 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_STRING
;
641 menuiteminfo
.fType
= MFT_STRING
;
642 menuiteminfo
.dwTypeData
= menutextbuffer
;
645 menuiteminfo
.fMask
|= MIIM_BITMAP
;
646 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, icon
);
648 menuiteminfo
.wID
= (UINT
)id
;
649 InsertMenuItem(menu
, pos
, TRUE
, &menuiteminfo
);
653 //menu entry for the top context menu, so append an "Git " before
654 //the menu text to indicate where the entry comes from
655 wcscpy_s(menutextbuffer
, 255, L
"Git ");
657 LoadString(g_hResInst
, stringid
, verbsbuffer
, _countof(verbsbuffer
));
658 wcscat_s(menutextbuffer
, 255, verbsbuffer
);
659 auto verb
= std::wstring(menutextbuffer
);
660 if (verb
.find('&') != -1)
662 verb
.erase(verb
.find('&'),1);
664 myVerbsMap
[verb
] = id
- idCmdFirst
;
665 myVerbsMap
[verb
] = id
;
666 myVerbsIDMap
[id
- idCmdFirst
] = verb
;
667 myVerbsIDMap
[id
] = verb
;
668 // We store the relative and absolute diameter
669 // (drawitem callback uses absolute, others relative)
670 myIDMap
[id
- idCmdFirst
] = com
;
673 mySubMenuMap
[pos
] = com
;
676 bool CShellExt::WriteClipboardPathsToTempFile(std::wstring
& tempfile
)
678 tempfile
= std::wstring();
679 //write all selected files and paths to a temporary file
680 //for TortoiseGitProc.exe to read out again.
682 DWORD pathlength
= GetTortoiseGitTempPath(0, nullptr);
683 auto path
= std::make_unique
<TCHAR
[]>(pathlength
+ 1);
684 auto tempFile
= std::make_unique
<TCHAR
[]>(pathlength
+ 100);
685 GetTortoiseGitTempPath(pathlength
+1, path
.get());
686 GetTempFileName(path
.get(), L
"git", 0, tempFile
.get());
687 tempfile
= std::wstring(tempFile
.get());
689 CAutoFile file
= ::CreateFile(tempFile
.get(),
694 FILE_ATTRIBUTE_TEMPORARY
,
700 if (!IsClipboardFormatAvailable(CF_HDROP
))
702 if (!OpenClipboard(nullptr))
705 std::wstring sClipboardText
;
706 HGLOBAL hglb
= GetClipboardData(CF_HDROP
);
712 HDROP hDrop
= (HDROP
)GlobalLock(hglb
);
715 SCOPE_EXIT
{ GlobalUnlock(hDrop
); };
717 TCHAR szFileName
[MAX_PATH
] = {0};
718 UINT cFiles
= DragQueryFile(hDrop
, 0xFFFFFFFF, nullptr, 0);
719 for(UINT i
= 0; i
< cFiles
; ++i
)
721 DragQueryFile(hDrop
, i
, szFileName
, _countof(szFileName
));
722 std::wstring filename
= szFileName
;
723 ::WriteFile (file
, filename
.c_str(), (DWORD
)filename
.size()*sizeof(TCHAR
), &written
, 0);
724 ::WriteFile(file
, L
"\n", 2, &written
, 0);
730 std::wstring
CShellExt::WriteFileListToTempFile()
732 //write all selected files and paths to a temporary file
733 //for TortoiseGitProc.exe to read out again.
734 DWORD pathlength
= GetTortoiseGitTempPath(0, nullptr);
735 auto path
= std::make_unique
<TCHAR
[]>(pathlength
+ 1);
736 auto tempFile
= std::make_unique
<TCHAR
[]>(pathlength
+ 100);
737 GetTortoiseGitTempPath(pathlength
+ 1, path
.get());
738 GetTempFileName(path
.get(), L
"git", 0, tempFile
.get());
739 auto retFilePath
= std::wstring(tempFile
.get());
741 CAutoFile file
= ::CreateFile (tempFile
.get(),
746 FILE_ATTRIBUTE_TEMPORARY
,
751 MessageBox(nullptr, L
"Could not create temporary file. Please check (the permissions of) your temp-folder: " + CString(tempFile
.get()), L
"TortoiseGit", MB_ICONERROR
);
752 return std::wstring();
758 ::WriteFile (file
, folder_
.c_str(), (DWORD
)folder_
.size()*sizeof(TCHAR
), &written
, 0);
759 ::WriteFile(file
, L
"\n", 2, &written
, 0);
762 for (const auto& file_
: files_
)
764 ::WriteFile(file
, file_
.c_str(), (DWORD
)file_
.size() * sizeof(TCHAR
), &written
, 0);
765 ::WriteFile(file
, L
"\n", 2, &written
, 0);
770 STDMETHODIMP
CShellExt::QueryDropContext(UINT uFlags
, UINT idCmdFirst
, HMENU hMenu
, UINT
&indexMenu
)
772 if (!CRegStdDWORD(L
"Software\\TortoiseGit\\EnableDragContextMenu", TRUE
, false, HKEY_CURRENT_USER
, KEY_WOW64_64KEY
))
775 PreserveChdir preserveChdir
;
778 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
779 return S_OK
; //we don't change the default action
781 if (files_
.empty() || folder_
.empty())
784 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
787 bool bSourceAndTargetFromSameRepository
= ((uuidSource
.size() == uuidTarget
.size() && _wcsnicmp(uuidSource
.c_str(), uuidTarget
.c_str(), uuidSource
.size()) == 0)) || uuidSource
.empty() || uuidTarget
.empty();
789 //the drop handler only has eight commands, but not all are visible at the same time:
790 //if the source file(s) are under version control then those files can be moved
791 //to the new location or they can be moved with a rename,
792 //if they are unversioned then they can be added to the working copy
793 //if they are versioned, they also can be exported to an unversioned location
794 UINT idCmd
= idCmdFirst
;
796 bool moveAvailable
= false;
798 // available if source is versioned but not added, 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_ADDED
)))
801 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVEMENU
, 0, idCmdFirst
, ShellMenuDropMove
, uFlags
);
802 moveAvailable
= true;
805 // Git move and rename here
806 // 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
807 if ((bSourceAndTargetFromSameRepository
|| (itemStatesFolder
& ITEMIS_ADDED
)) && (itemStatesFolder
& ITEMIS_FOLDERINGIT
) && (itemStates
& (ITEMIS_NORMAL
| ITEMIS_INGIT
| ITEMIS_FOLDERINGIT
)) && (itemStates
& ITEMIS_ONLYONE
) && ((~itemStates
) & ITEMIS_ADDED
))
809 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVERENAMEMENU
, 0, idCmdFirst
, ShellMenuDropMoveRename
, uFlags
);
810 moveAvailable
= true;
814 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
815 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&((~itemStates) & ITEMIS_ADDED))
816 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYMENU, 0, idCmdFirst, ShellMenuDropCopy, uFlags);
818 // Git copy and rename here, source and target from same repository
819 // available if source is a single, versioned but not added item, target is versioned or target folder is added
820 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
821 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYRENAMEMENU, 0, idCmdFirst, ShellMenuDropCopyRename, uFlags);
824 // available if target is versioned and source is either unversioned or from another repository
825 if ((itemStatesFolder
& ITEMIS_FOLDERINGIT
) && (((~itemStates
) & ITEMIS_INGIT
) || !bSourceAndTargetFromSameRepository
) && !moveAvailable
)
826 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYADDMENU
, 0, idCmdFirst
, ShellMenuDropCopyAdd
, uFlags
);
829 // available if source is versioned and a folder
830 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
831 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
833 // Git export all here
834 // available if source is versioned and a folder
835 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
836 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
839 // available if source is a patchfile
840 if (itemStates
& ITEMIS_PATCHFILE
)
842 if (itemStates
& ITEMIS_ONLYONE
)
843 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_MENUAPPLYPATCH
, 0, idCmdFirst
, ShellMenuApplyPatch
, uFlags
);
844 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_MENUIMPORTPATCH
, 0, idCmdFirst
, ShellMenuImportPatchDrop
, uFlags
);
848 if (idCmd
!= idCmdFirst
)
849 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr);
853 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
856 STDMETHODIMP
CShellExt::QueryContextMenu(HMENU hMenu
,
864 return QueryContextMenu_Wrap(hMenu
, indexMenu
, idCmdFirst
, idCmdLast
, uFlags
);
866 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
872 STDMETHODIMP
CShellExt::QueryContextMenu_Wrap(HMENU hMenu
,
878 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: QueryContextMenu itemStates=%ld\n", itemStates
);
879 PreserveChdir preserveChdir
;
881 //first check if our drop handler is called
882 //and then (if true) provide the context menu for the
884 if (m_State
== FileStateDropHandler
)
886 return QueryDropContext(uFlags
, idCmdFirst
, hMenu
, indexMenu
);
889 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
890 return S_OK
; //we don't change the default action
892 if (files_
.empty() && folder_
.empty())
895 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
902 CSIDL_COMMON_FAVORITES
,
903 CSIDL_COMMON_STARTMENU
,
904 CSIDL_COMPUTERSNEARME
,
912 CSIDL_INTERNET_CACHE
,
922 if (IsIllegalFolder(folder_
, csidlarray
))
927 // folder is empty, but maybe files are selected
929 return S_OK
; // nothing selected - we don't have a menu to show
930 // check whether a selected entry is an UID - those are namespace extensions
931 // which we can't handle
932 for (const auto& file
: files_
)
934 if (CStringUtils::StartsWith(file
.c_str(), L
"::{"))
940 if (CStringUtils::StartsWith(folder_
.c_str(), L
"::{"))
944 if (((uFlags
& CMF_EXTENDEDVERBS
) == 0) && g_ShellCache
.HideMenusForUnversionedItems())
946 if ((itemStates
& (ITEMIS_INGIT
| ITEMIS_INVERSIONEDFOLDER
| ITEMIS_FOLDERINGIT
| ITEMIS_BAREREPO
)) == 0)
950 //check if our menu is requested for a git admin directory
951 if (GitAdminDir::IsAdminDirPath(folder_
.c_str()))
954 if (uFlags
& CMF_EXTENDEDVERBS
)
955 itemStates
|= ITEMIS_EXTENDED
;
958 if (!std::wstring(regDiffLater
).empty())
959 itemStates
|= ITEMIS_HASDIFFLATER
;
961 const BOOL bShortcut
= !!(uFlags
& CMF_VERBSONLY
);
962 if ( bShortcut
&& (files_
.size()==1))
964 // Don't show the context menu for a link if the
965 // destination is not part of a working copy.
966 // It would only show the standard menu items
967 // which are already shown for the lnk-file.
968 CString path
= files_
.front().c_str();
969 if (!GitAdminDir::HasAdminDir(path
))
975 //check if we already added our menu entry for a folder.
976 //we check that by iterating through all menu entries and check if
977 //the dwItemData member points to our global ID string. That string is set
978 //by our shell extension when the folder menu is inserted.
979 TCHAR menubuf
[MAX_PATH
] = {0};
980 int count
= GetMenuItemCount(hMenu
);
981 for (int i
=0; i
<count
; ++i
)
983 MENUITEMINFO miif
= { 0 };
984 miif
.cbSize
= sizeof(MENUITEMINFO
);
985 miif
.fMask
= MIIM_DATA
;
986 miif
.dwTypeData
= menubuf
;
987 miif
.cch
= _countof(menubuf
);
988 GetMenuItemInfo(hMenu
, i
, TRUE
, &miif
);
989 if (miif
.dwItemData
== (ULONG_PTR
)g_MenuIDString
)
994 UINT idCmd
= idCmdFirst
;
996 //create the sub menu
997 HMENU subMenu
= CreateMenu();
998 int indexSubMenu
= 0;
1000 unsigned __int64 topmenu
= g_ShellCache
.GetMenuLayout();
1001 unsigned __int64 menumask
= g_ShellCache
.GetMenuMask();
1002 unsigned __int64 menuex
= g_ShellCache
.GetMenuExt();
1005 bool bAddSeparator
= false;
1006 bool bMenuEntryAdded
= false;
1007 bool bMenuEmpty
= true;
1008 // insert separator at start
1009 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
1010 bool bShowIcons
= !!DWORD(CRegStdDWORD(L
"Software\\TortoiseGit\\ShowContextMenuIcons", TRUE
, false, HKEY_CURRENT_USER
, KEY_WOW64_64KEY
));
1012 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1014 if (menuInfo
[menuIndex
].command
== ShellSeparator
)
1016 // we don't add a separator immediately. Because there might not be
1017 // another 'normal' menu entry after we insert a separator.
1018 // we simply set a flag here, indicating that before the next
1019 // 'normal' menu entry, a separator should be added.
1021 bAddSeparator
= true;
1022 if (bMenuEntryAdded
)
1023 bAddSeparator
= true;
1027 // check the conditions whether to show the menu entry or not
1028 bool bInsertMenu
= ShouldInsertItem(menuInfo
[menuIndex
]);
1029 if (menuInfo
[menuIndex
].menuID
& menuex
)
1031 if( !(itemStates
& ITEMIS_EXTENDED
) )
1032 bInsertMenu
= false;
1035 if (menuInfo
[menuIndex
].menuID
& (~menumask
))
1039 bool bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1040 // insert a separator
1041 if ((bMenuEntryAdded
)&&(bAddSeparator
)&&(!bIsTop
))
1043 bAddSeparator
= false;
1044 bMenuEntryAdded
= false;
1045 InsertMenu(subMenu
, indexSubMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, nullptr);
1049 // handle special cases (sub menus)
1050 if ((menuInfo
[menuIndex
].command
== ShellMenuIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuUnIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuDeleteIgnoreSub
))
1052 if(InsertIgnoreSubmenus(idCmd
, idCmdFirst
, hMenu
, subMenu
, indexMenu
, indexSubMenu
, topmenu
, bShowIcons
, uFlags
))
1053 bMenuEntryAdded
= true;
1057 bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1059 // insert the menu entry
1060 InsertGitMenu( bIsTop
,
1061 bIsTop
? hMenu
: subMenu
,
1062 bIsTop
? indexMenu
++ : indexSubMenu
++,
1064 menuInfo
[menuIndex
].menuTextID
,
1065 bShowIcons
? menuInfo
[menuIndex
].iconID
: 0,
1067 menuInfo
[menuIndex
].command
,
1071 bMenuEntryAdded
= true;
1073 bAddSeparator
= false;
1082 // do not show TortoiseGit menu if it's empty
1085 if (idCmd
- idCmdFirst
> 0)
1088 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
1092 //return number of menu items added
1093 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
1096 //add sub menu to main context menu
1097 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1098 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1099 MAKESTRING(IDS_MENUSUBMENU
);
1100 if (!g_ShellCache
.HasShellMenuAccelerators())
1102 // remove the accelerators
1103 tstring temp
= stringtablebuffer
;
1104 temp
.erase(std::remove(temp
.begin(), temp
.end(), '&'), temp
.end());
1105 wcscpy_s(stringtablebuffer
, temp
.c_str());
1107 MENUITEMINFO menuiteminfo
= { 0 };
1108 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
1109 menuiteminfo
.fType
= MFT_STRING
;
1110 menuiteminfo
.dwTypeData
= stringtablebuffer
;
1112 UINT uIcon
= bShowIcons
? IDI_APP
: 0;
1113 if (!folder_
.empty())
1115 uIcon
= bShowIcons
? IDI_MENUFOLDER
: 0;
1116 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFolder
;
1117 myIDMap
[idCmd
] = ShellSubMenuFolder
;
1118 menuiteminfo
.dwItemData
= (ULONG_PTR
)g_MenuIDString
;
1120 else if (!bShortcut
&& (files_
.size()==1))
1122 uIcon
= bShowIcons
? IDI_MENUFILE
: 0;
1123 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFile
;
1124 myIDMap
[idCmd
] = ShellSubMenuFile
;
1126 else if (bShortcut
&& (files_
.size()==1))
1128 uIcon
= bShowIcons
? IDI_MENULINK
: 0;
1129 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuLink
;
1130 myIDMap
[idCmd
] = ShellSubMenuLink
;
1132 else if (!files_
.empty())
1134 uIcon
= bShowIcons
? IDI_MENUMULTIPLE
: 0;
1135 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuMultiple
;
1136 myIDMap
[idCmd
] = ShellSubMenuMultiple
;
1140 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenu
;
1141 myIDMap
[idCmd
] = ShellSubMenu
;
1143 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
1146 menuiteminfo
.fMask
|= MIIM_BITMAP
;
1147 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, uIcon
);
1149 menuiteminfo
.hSubMenu
= subMenu
;
1150 menuiteminfo
.wID
= idCmd
++;
1151 InsertMenuItem(hMenu
, indexMenu
++, TRUE
, &menuiteminfo
);
1154 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
1158 //return number of menu items added
1159 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
1162 void CShellExt::TweakMenu(HMENU hMenu
)
1164 MENUINFO MenuInfo
= {};
1165 MenuInfo
.cbSize
= sizeof(MenuInfo
);
1166 MenuInfo
.fMask
= MIM_STYLE
| MIM_APPLYTOSUBMENUS
;
1167 MenuInfo
.dwStyle
= MNS_CHECKORBMP
;
1168 SetMenuInfo(hMenu
, &MenuInfo
);
1171 void CShellExt::AddPathCommand(tstring
& gitCmd
, LPCTSTR command
, bool bFilesAllowed
)
1174 gitCmd
+= L
" /path:\"";
1175 if ((bFilesAllowed
) && !files_
.empty())
1176 gitCmd
+= files_
.front();
1182 void CShellExt::AddPathFileCommand(tstring
& gitCmd
, LPCTSTR command
)
1184 tstring tempfile
= WriteFileListToTempFile();
1186 gitCmd
+= L
" /pathfile:\"";
1189 gitCmd
+= L
" /deletepathfile";
1192 void CShellExt::AddPathFileDropCommand(tstring
& gitCmd
, LPCTSTR command
)
1194 tstring tempfile
= WriteFileListToTempFile();
1196 gitCmd
+= L
" /pathfile:\"";
1199 gitCmd
+= L
" /deletepathfile";
1200 gitCmd
+= L
" /droptarget:\"";
1205 STDMETHODIMP
CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi
)
1209 return InvokeCommand_Wrap(lpcmi
);
1211 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1217 // This is called when you invoke a command on the menu:
1218 STDMETHODIMP
CShellExt::InvokeCommand_Wrap(LPCMINVOKECOMMANDINFO lpcmi
)
1220 PreserveChdir preserveChdir
;
1221 HRESULT hr
= E_INVALIDARG
;
1225 if (!files_
.empty() || !folder_
.empty())
1227 UINT_PTR idCmd
= LOWORD(lpcmi
->lpVerb
);
1229 if (HIWORD(lpcmi
->lpVerb
))
1231 auto verb
= std::wstring(MultibyteToWide(lpcmi
->lpVerb
));
1232 const auto verb_it
= myVerbsMap
.lower_bound(verb
);
1233 if (verb_it
!= myVerbsMap
.end() && verb_it
->first
== verb
)
1234 idCmd
= verb_it
->second
;
1239 // See if we have a handler interface for this id
1240 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1241 if (id_it
!= myIDMap
.end() && id_it
->first
== idCmd
)
1243 tstring
tortoiseProcPath(CPathUtils::GetAppDirectory(g_hmodThisDll
) + L
"TortoiseGitProc.exe");
1244 tstring
tortoiseMergePath(CPathUtils::GetAppDirectory(g_hmodThisDll
) + L
"TortoiseGitMerge.exe");
1246 //TortoiseGitProc expects a command line of the form:
1247 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1249 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1251 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1252 //* pathfile is a path to a temporary file which contains a list of file paths
1253 std::wstring gitCmd
= L
" /command:";
1254 std::wstring tempfile
;
1255 switch (id_it
->second
)
1260 TCHAR syncSeq
[12] = { 0 };
1261 swprintf_s(syncSeq
, L
"%d", g_syncSeq
++);
1262 AddPathCommand(gitCmd
, L
"sync", false);
1263 gitCmd
+= L
" /seq:";
1267 case ShellMenuSubSync
:
1268 AddPathFileCommand(gitCmd
, L
"subsync");
1269 if (itemStatesFolder
& ITEMIS_SUBMODULECONTAINER
|| (itemStates
& ITEMIS_SUBMODULECONTAINER
&& itemStates
& ITEMIS_WCROOT
&& itemStates
& ITEMIS_ONLYONE
))
1271 gitCmd
+= L
" /bkpath:\"";
1276 case ShellMenuUpdateExt
:
1277 AddPathFileCommand(gitCmd
, L
"subupdate");
1278 if (itemStatesFolder
& ITEMIS_SUBMODULECONTAINER
|| (itemStates
& ITEMIS_SUBMODULECONTAINER
&& itemStates
& ITEMIS_WCROOT
&& itemStates
& ITEMIS_ONLYONE
))
1280 gitCmd
+= L
" /bkpath:\"";
1285 case ShellMenuCommit
:
1286 AddPathFileCommand(gitCmd
, L
"commit");
1289 AddPathFileCommand(gitCmd
, L
"add");
1291 case ShellMenuIgnore
:
1292 AddPathFileCommand(gitCmd
, L
"ignore");
1294 case ShellMenuIgnoreCaseSensitive
:
1295 AddPathFileCommand(gitCmd
, L
"ignore");
1296 gitCmd
+= L
" /onlymask";
1298 case ShellMenuDeleteIgnore
:
1299 AddPathFileCommand(gitCmd
, L
"ignore");
1300 gitCmd
+= L
" /delete";
1302 case ShellMenuDeleteIgnoreCaseSensitive
:
1303 AddPathFileCommand(gitCmd
, L
"ignore");
1304 gitCmd
+= L
" /delete /onlymask";
1306 case ShellMenuUnIgnore
:
1307 AddPathFileCommand(gitCmd
, L
"unignore");
1309 case ShellMenuUnIgnoreCaseSensitive
:
1310 AddPathFileCommand(gitCmd
, L
"unignore");
1311 gitCmd
+= L
" /onlymask";
1313 case ShellMenuMergeAbort
:
1314 AddPathCommand(gitCmd
, L
"merge", false);
1315 gitCmd
+= L
" /abort";
1317 case ShellMenuRevert
:
1318 AddPathFileCommand(gitCmd
, L
"revert");
1320 case ShellMenuCleanup
:
1321 AddPathFileCommand(gitCmd
, L
"cleanup");
1323 case ShellMenuSendMail
:
1324 AddPathFileCommand(gitCmd
, L
"sendmail");
1326 case ShellMenuResolve
:
1327 AddPathFileCommand(gitCmd
, L
"resolve");
1329 case ShellMenuSwitch
:
1330 AddPathCommand(gitCmd
, L
"switch", false);
1332 case ShellMenuExport
:
1333 AddPathCommand(gitCmd
, L
"export", false);
1335 case ShellMenuAbout
:
1338 case ShellMenuCreateRepos
:
1339 AddPathCommand(gitCmd
, L
"repocreate", false);
1341 case ShellMenuMerge
:
1342 AddPathCommand(gitCmd
, L
"merge", false);
1345 AddPathCommand(gitCmd
, L
"copy", true);
1347 case ShellMenuSettings
:
1348 AddPathCommand(gitCmd
, L
"settings", true);
1353 case ShellMenuRename
:
1354 AddPathCommand(gitCmd
, L
"rename", true);
1356 case ShellMenuRemove
:
1357 AddPathFileCommand(gitCmd
, L
"remove");
1358 if (itemStates
& ITEMIS_SUBMODULE
)
1359 gitCmd
+= L
" /submodule";
1361 case ShellMenuRemoveKeep
:
1362 AddPathFileCommand(gitCmd
, L
"remove");
1363 gitCmd
+= L
" /keep";
1366 gitCmd
+= L
"diff /path:\"";
1367 if (files_
.size() == 1)
1368 gitCmd
+= files_
.front();
1369 else if (files_
.size() == 2)
1371 auto I
= files_
.cbegin();
1374 gitCmd
+= L
"\" /path2:\"";
1380 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1381 gitCmd
+= L
" /alternative";
1383 case ShellMenuDiffLater
:
1384 if (lpcmi
->fMask
& CMIC_MASK_CONTROL_DOWN
)
1387 regDiffLater
.removeValue();
1389 else if (files_
.size() == 1)
1391 if (std::wstring(regDiffLater
).empty())
1394 regDiffLater
= files_
[0];
1398 AddPathCommand(gitCmd
, L
"diff", true);
1399 gitCmd
+= L
" /path2:\"";
1400 gitCmd
+= std::wstring(regDiffLater
);
1402 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1403 gitCmd
+= L
" /alternative";
1404 regDiffLater
.removeValue();
1410 case ShellMenuPrevDiff
:
1411 AddPathCommand(gitCmd
, L
"prevdiff", true);
1412 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1413 gitCmd
+= L
" /alternative";
1415 case ShellMenuDiffTwo
:
1416 AddPathCommand(gitCmd
, L
"diffcommits", true);
1418 case ShellMenuDropCopyAdd
:
1419 AddPathFileDropCommand(gitCmd
, L
"dropcopyadd");
1421 case ShellMenuDropCopy
:
1422 AddPathFileDropCommand(gitCmd
, L
"dropcopy");
1424 case ShellMenuDropCopyRename
:
1425 AddPathFileDropCommand(gitCmd
, L
"dropcopy");
1426 gitCmd
+= L
" /rename";
1428 case ShellMenuDropMove
:
1429 AddPathFileDropCommand(gitCmd
, L
"dropmove");
1431 case ShellMenuDropMoveRename
:
1432 AddPathFileDropCommand(gitCmd
, L
"dropmove");
1433 gitCmd
+= L
" /rename";
1435 case ShellMenuDropExport
:
1436 AddPathFileDropCommand(gitCmd
, L
"dropexport");
1438 case ShellMenuDropExportExtended
:
1439 AddPathFileDropCommand(gitCmd
, L
"dropexport");
1440 gitCmd
+= L
" /extended";
1443 case ShellMenuLogSubmoduleFolder
:
1444 AddPathCommand(gitCmd
, L
"log", true);
1445 if (id_it
->second
== ShellMenuLogSubmoduleFolder
)
1446 gitCmd
+= L
" /submodule";
1448 case ShellMenuDaemon
:
1449 AddPathCommand(gitCmd
, L
"daemon", true);
1451 case ShellMenuRevisionGraph
:
1452 AddPathCommand(gitCmd
, L
"revisiongraph", true);
1454 case ShellMenuConflictEditor
:
1455 AddPathCommand(gitCmd
, L
"conflicteditor", true);
1456 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1457 gitCmd
+= L
" /alternative";
1459 case ShellMenuGitSVNRebase
:
1460 AddPathCommand(gitCmd
, L
"svnrebase", false);
1462 case ShellMenuGitSVNDCommit
:
1463 AddPathCommand(gitCmd
, L
"svndcommit", true);
1465 case ShellMenuGitSVNDFetch
:
1466 AddPathCommand(gitCmd
, L
"svnfetch", false);
1468 case ShellMenuGitSVNIgnore
:
1469 AddPathCommand(gitCmd
, L
"svnignore", false);
1471 case ShellMenuRebase
:
1472 AddPathCommand(gitCmd
, L
"rebase", false);
1474 case ShellMenuShowChanged
:
1475 if (files_
.size() > 1)
1476 AddPathFileCommand(gitCmd
, L
"repostatus");
1478 AddPathCommand(gitCmd
, L
"repostatus", true);
1480 case ShellMenuRepoBrowse
:
1481 AddPathCommand(gitCmd
, L
"repobrowser", false);
1483 case ShellMenuRefBrowse
:
1484 AddPathCommand(gitCmd
, L
"refbrowse", false);
1486 case ShellMenuRefLog
:
1487 AddPathCommand(gitCmd
, L
"reflog", false);
1489 case ShellMenuStashSave
:
1490 AddPathCommand(gitCmd
, L
"stashsave", true);
1492 case ShellMenuStashApply
:
1493 AddPathCommand(gitCmd
, L
"stashapply", false);
1495 case ShellMenuStashPop
:
1496 AddPathCommand(gitCmd
, L
"stashpop", false);
1498 case ShellMenuStashList
:
1499 AddPathCommand(gitCmd
, L
"reflog", false);
1500 gitCmd
+= L
" /ref:refs/stash";
1502 case ShellMenuBisectStart
:
1503 AddPathCommand(gitCmd
, L
"bisect", false);
1504 gitCmd
+= L
" /start";
1506 case ShellMenuBisectGood
:
1507 AddPathCommand(gitCmd
, L
"bisect", false);
1508 gitCmd
+= L
" /good";
1510 case ShellMenuBisectBad
:
1511 AddPathCommand(gitCmd
, L
"bisect", false);
1514 case ShellMenuBisectSkip
:
1515 AddPathCommand(gitCmd
, L
"bisect", false);
1516 gitCmd
+= L
" /skip";
1518 case ShellMenuBisectReset
:
1519 AddPathCommand(gitCmd
, L
"bisect", false);
1520 gitCmd
+= L
" /reset";
1522 case ShellMenuSubAdd
:
1523 AddPathCommand(gitCmd
, L
"subadd", false);
1525 case ShellMenuBlame
:
1526 AddPathCommand(gitCmd
, L
"blame", true);
1528 case ShellMenuApplyPatch
:
1529 if ((itemStates
& ITEMIS_PATCHINCLIPBOARD
) && ((~itemStates
) & ITEMIS_PATCHFILE
))
1531 // if there's a patch file in the clipboard, we save it
1532 // to a temporary file and tell TortoiseGitMerge to use that one
1533 UINT cFormat
= RegisterClipboardFormat(L
"TGIT_UNIFIEDDIFF");
1534 if (cFormat
&& OpenClipboard(nullptr))
1536 HGLOBAL hglb
= GetClipboardData(cFormat
);
1537 LPCSTR lpstr
= (LPCSTR
)GlobalLock(hglb
);
1539 DWORD len
= GetTortoiseGitTempPath(0, nullptr);
1540 auto path
= std::make_unique
<TCHAR
[]>(len
+ 1);
1541 auto tempF
= std::make_unique
<TCHAR
[]>(len
+ 100);
1542 GetTortoiseGitTempPath(len
+ 1, path
.get());
1543 GetTempFileName(path
.get(), TEXT("git"), 0, tempF
.get());
1544 std::wstring sTempFile
= std::wstring(tempF
.get());
1547 size_t patchlen
= strlen(lpstr
);
1548 _wfopen_s(&outFile
, sTempFile
.c_str(), L
"wb");
1551 size_t size
= fwrite(lpstr
, sizeof(char), patchlen
, outFile
);
1552 if (size
== patchlen
)
1554 itemStates
|= ITEMIS_PATCHFILE
;
1556 files_
.push_back(sTempFile
);
1564 if (itemStates
& ITEMIS_PATCHFILE
)
1566 gitCmd
= L
" /diff:\"";
1567 if (!files_
.empty())
1569 gitCmd
+= files_
.front();
1570 if (itemStatesFolder
& ITEMIS_FOLDERINGIT
)
1572 gitCmd
+= L
"\" /patchpath:\"";
1578 if (itemStates
& ITEMIS_INVERSIONEDFOLDER
)
1579 gitCmd
+= L
"\" /wc";
1585 gitCmd
= L
" /patchpath:\"";
1586 if (!files_
.empty())
1587 gitCmd
+= files_
.front();
1593 myVerbsIDMap
.clear();
1595 RunCommand(tortoiseMergePath
, gitCmd
, L
"TortoiseGitMerge launch failed");
1598 case ShellMenuClipPaste
:
1599 if (WriteClipboardPathsToTempFile(tempfile
))
1602 UINT cPrefDropFormat
= RegisterClipboardFormat(L
"Preferred DropEffect");
1603 if (cPrefDropFormat
)
1605 if (OpenClipboard(lpcmi
->hwnd
))
1607 HGLOBAL hglb
= GetClipboardData(cPrefDropFormat
);
1610 DWORD
* effect
= (DWORD
*) GlobalLock(hglb
);
1611 if (*effect
== DROPEFFECT_MOVE
)
1620 gitCmd
+= L
"pastecopy /pathfile:\"";
1622 gitCmd
+= L
"pastemove /pathfile:\"";
1625 gitCmd
+= L
" /deletepathfile";
1626 gitCmd
+= L
" /droptarget:\"";
1632 case ShellMenuClone
:
1633 AddPathCommand(gitCmd
, L
"clone", false);
1636 AddPathCommand(gitCmd
, L
"pull", false);
1639 AddPathCommand(gitCmd
, L
"push", false);
1641 case ShellMenuBranch
:
1642 AddPathCommand(gitCmd
, L
"branch", false);
1645 AddPathCommand(gitCmd
, L
"tag", false);
1647 case ShellMenuFormatPatch
:
1648 AddPathCommand(gitCmd
, L
"formatpatch", false);
1650 case ShellMenuImportPatch
:
1651 AddPathFileCommand(gitCmd
, L
"importpatch");
1653 case ShellMenuImportPatchDrop
:
1654 AddPathFileDropCommand(gitCmd
, L
"importpatch");
1656 case ShellMenuFetch
:
1657 AddPathCommand(gitCmd
, L
"fetch", false);
1663 } // switch (id_it->second)
1664 if (!gitCmd
.empty())
1666 gitCmd
+= L
" /hwnd:";
1667 TCHAR buf
[30] = { 0 };
1668 swprintf_s(buf
, L
"%p", (void*)lpcmi
->hwnd
);
1671 myVerbsIDMap
.clear();
1673 RunCommand(tortoiseProcPath
, gitCmd
, L
"TortoiseProc launch failed");
1676 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1677 } // if (files_.empty() || folder_.empty())
1681 // This is for the status bar and things like that:
1682 STDMETHODIMP
CShellExt::GetCommandString(UINT_PTR idCmd
,
1684 UINT FAR
* reserved
,
1690 return GetCommandString_Wrap(idCmd
, uFlags
, reserved
, pszName
, cchMax
);
1692 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1698 // This is for the status bar and things like that:
1699 STDMETHODIMP
CShellExt::GetCommandString_Wrap(UINT_PTR idCmd
,
1701 UINT FAR
* /*reserved*/,
1705 PreserveChdir preserveChdir
;
1706 //do we know the id?
1707 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1708 if (id_it
== myIDMap
.end() || id_it
->first
!= idCmd
)
1710 return E_INVALIDARG
; //no, we don't
1714 HRESULT hr
= E_INVALIDARG
;
1716 MAKESTRING(IDS_MENUDESCDEFAULT
);
1718 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1720 if (menuInfo
[menuIndex
].command
== (GitCommands
)id_it
->second
)
1722 MAKESTRING(menuInfo
[menuIndex
].menuDescID
);
1728 const TCHAR
* desc
= stringtablebuffer
;
1733 std::string help
= WideToMultibyte(desc
);
1734 lstrcpynA(pszName
, help
.c_str(), cchMax
- 1);
1740 std::wstring help
= desc
;
1741 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
- 1);
1747 const auto verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
1748 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
1750 std::string help
= WideToMultibyte(verb_id_it
->second
);
1751 lstrcpynA(pszName
, help
.c_str(), cchMax
- 1);
1758 const auto verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
1759 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
1761 std::wstring help
= verb_id_it
->second
;
1762 CTraceToOutputDebugString::Instance()(__FUNCTION__
": verb : %ws\n", help
.c_str());
1763 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
- 1);
1772 STDMETHODIMP
CShellExt::HandleMenuMsg(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
1776 return HandleMenuMsg_Wrap(uMsg
, wParam
, lParam
);
1778 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1784 STDMETHODIMP
CShellExt::HandleMenuMsg_Wrap(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
1787 return HandleMenuMsg2(uMsg
, wParam
, lParam
, &res
);
1790 STDMETHODIMP
CShellExt::HandleMenuMsg2(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*pResult
)
1794 return HandleMenuMsg2_Wrap(uMsg
, wParam
, lParam
, pResult
);
1796 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1802 STDMETHODIMP
CShellExt::HandleMenuMsg2_Wrap(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*pResult
)
1804 PreserveChdir preserveChdir
;
1814 case WM_MEASUREITEM
:
1816 MEASUREITEMSTRUCT
* lpmis
= (MEASUREITEMSTRUCT
*)lParam
;
1819 lpmis
->itemWidth
= 16;
1820 lpmis
->itemHeight
= 16;
1827 DRAWITEMSTRUCT
* lpdis
= (DRAWITEMSTRUCT
*)lParam
;
1828 if (!lpdis
|| lpdis
->CtlType
!= ODT_MENU
)
1829 return S_OK
; //not for a menu
1830 resource
= GetMenuTextFromResource((int)myIDMap
[lpdis
->itemID
]);
1833 int iconWidth
= GetSystemMetrics(SM_CXSMICON
);
1834 int iconHeight
= GetSystemMetrics(SM_CYSMICON
);
1835 HICON hIcon
= (HICON
)LoadImage(g_hResInst
, resource
, IMAGE_ICON
, iconWidth
, iconHeight
, LR_DEFAULTCOLOR
);
1838 DrawIconEx(lpdis
->hDC
,
1840 lpdis
->rcItem
.top
+ (lpdis
->rcItem
.bottom
- lpdis
->rcItem
.top
- iconHeight
) / 2,
1841 hIcon
, iconWidth
, iconHeight
,
1842 0, nullptr, DI_NORMAL
);
1850 if (HIWORD(wParam
) != MF_POPUP
)
1852 int nChar
= LOWORD(wParam
);
1853 if (_istascii((wint_t)nChar
) && _istupper((wint_t)nChar
))
1854 nChar
= tolower(nChar
);
1855 // we have the char the user pressed, now search that char in all our
1857 std::vector
<UINT_PTR
> accmenus
;
1858 for (auto It
= mySubMenuMap
.cbegin(); It
!= mySubMenuMap
.cend(); ++It
)
1860 LPCTSTR resource
= GetMenuTextFromResource((int)mySubMenuMap
[It
->first
]);
1863 szItem
= stringtablebuffer
;
1864 TCHAR
* amp
= wcschr(szItem
, L
'&');
1868 int ampChar
= LOWORD(*amp
);
1869 if (_istascii((wint_t)ampChar
) && _istupper((wint_t)ampChar
))
1870 ampChar
= tolower(ampChar
);
1871 if (ampChar
== nChar
)
1873 // yep, we found a menu which has the pressed key
1874 // as an accelerator. Add that menu to the list to
1876 accmenus
.push_back(It
->first
);
1879 if (accmenus
.empty())
1881 // no menu with that accelerator key.
1882 *pResult
= MAKELONG(0, MNC_IGNORE
);
1885 if (accmenus
.size() == 1)
1887 // Only one menu with that accelerator key. We're lucky!
1888 // So just execute that menu entry.
1889 *pResult
= MAKELONG(accmenus
[0], MNC_EXECUTE
);
1892 if (accmenus
.size() > 1)
1894 // we have more than one menu item with this accelerator key!
1896 mif
.cbSize
= sizeof(MENUITEMINFO
);
1897 mif
.fMask
= MIIM_STATE
;
1898 for (auto it
= accmenus
.cbegin(); it
!= accmenus
.cend(); ++it
)
1900 GetMenuItemInfo((HMENU
)lParam
, (UINT
)*it
, TRUE
, &mif
);
1901 if (mif
.fState
== MFS_HILITE
)
1903 // this is the selected item, so select the next one
1905 if (it
== accmenus
.end())
1906 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
1908 *pResult
= MAKELONG(*it
, MNC_SELECT
);
1912 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
1923 LPCTSTR
CShellExt::GetMenuTextFromResource(int id
)
1925 TCHAR textbuf
[255] = { 0 };
1926 LPCTSTR resource
= nullptr;
1927 unsigned __int64 layout
= g_ShellCache
.GetMenuLayout();
1931 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1933 if (menuInfo
[menuIndex
].command
== id
)
1935 MAKESTRING(menuInfo
[menuIndex
].menuTextID
);
1936 resource
= MAKEINTRESOURCE(menuInfo
[menuIndex
].iconID
);
1939 case ShellSubMenuMultiple
:
1940 case ShellSubMenuLink
:
1941 case ShellSubMenuFolder
:
1942 case ShellSubMenuFile
:
1947 space
= (layout
& menuInfo
[menuIndex
].menuID
) ? 0 : 6;
1948 if (layout
& menuInfo
[menuIndex
].menuID
)
1950 wcscpy_s(textbuf
, 255, L
"Git ");
1951 wcscat_s(textbuf
, 255, stringtablebuffer
);
1952 wcscpy_s(stringtablebuffer
, 255, textbuf
);
1963 bool CShellExt::IsIllegalFolder(const std::wstring
& folder
, int* cslidarray
)
1966 TCHAR buf
[MAX_PATH
] = {0}; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
1967 LPITEMIDLIST pidl
= nullptr;
1968 while (cslidarray
[i
])
1972 if (SHGetFolderLocation(nullptr, cslidarray
[i
- 1], nullptr, 0, &pidl
) != S_OK
)
1974 if (!SHGetPathFromIDList(pidl
, buf
))
1976 // not a file system path, definitely illegal for our use
1977 CoTaskMemFree(pidl
);
1980 CoTaskMemFree(pidl
);
1983 if (wcscmp(buf
, folder
.c_str()) == 0)
1989 bool CShellExt::InsertIgnoreSubmenus(UINT
&idCmd
, UINT idCmdFirst
, HMENU hMenu
, HMENU subMenu
, UINT
&indexMenu
, int &indexSubMenu
, unsigned __int64 topmenu
, bool bShowIcons
, UINT
/*uFlags*/)
1991 HMENU ignoresubmenu
= nullptr;
1992 int indexignoresub
= 0;
1993 bool bShowIgnoreMenu
= false;
1994 TCHAR maskbuf
[MAX_PATH
] = {0}; // MAX_PATH is ok, since this only holds a filename
1995 TCHAR ignorepath
[MAX_PATH
] = {0}; // MAX_PATH is ok, since this only holds a filename
1998 UINT icon
= bShowIcons
? IDI_IGNORE
: 0;
2000 auto I
= files_
.cbegin();
2001 if (wcsrchr(I
->c_str(), L
'\\'))
2002 wcscpy_s(ignorepath
, wcsrchr(I
->c_str(), L
'\\') + 1);
2004 wcscpy_s(ignorepath
, I
->c_str());
2005 if ((itemStates
& ITEMIS_IGNORED
) && (!ignoredprops
.empty()))
2007 // check if the item name is ignored or the mask
2009 const size_t pathLength
= wcslen(ignorepath
);
2010 while ( (p
=ignoredprops
.find( ignorepath
,p
)) != -1 )
2012 if ( (p
==0 || ignoredprops
[p
-1]==TCHAR('\n'))
2013 && (p
+ pathLength
== ignoredprops
.length() || ignoredprops
[p
+ pathLength
+ 1] == TCHAR('\n')))
2021 ignoresubmenu
= CreateMenu();
2022 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2023 auto verb
= std::wstring(ignorepath
);
2024 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2025 myVerbsMap
[verb
] = idCmd
;
2026 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2027 myVerbsIDMap
[idCmd
] = verb
;
2028 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnore
;
2029 myIDMap
[idCmd
++] = ShellMenuUnIgnore
;
2030 bShowIgnoreMenu
= true;
2032 wcscpy_s(maskbuf
, L
"*");
2033 if (wcsrchr(ignorepath
, L
'.'))
2035 wcscat_s(maskbuf
, wcsrchr(ignorepath
, L
'.'));
2036 p
= ignoredprops
.find(maskbuf
);
2038 ((ignoredprops
.compare(maskbuf
) == 0) || (ignoredprops
.find(L
'\n', p
) == p
+ wcslen(maskbuf
) + 1) || (ignoredprops
.rfind(L
'\n', p
) == p
- 1)))
2041 ignoresubmenu
= CreateMenu();
2043 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2044 auto verb
= std::wstring(maskbuf
);
2045 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2046 myVerbsMap
[verb
] = idCmd
;
2047 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2048 myVerbsIDMap
[idCmd
] = verb
;
2049 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreCaseSensitive
;
2050 myIDMap
[idCmd
++] = ShellMenuUnIgnoreCaseSensitive
;
2051 bShowIgnoreMenu
= true;
2055 else if ((itemStates
& ITEMIS_IGNORED
) == 0)
2057 bShowIgnoreMenu
= true;
2058 ignoresubmenu
= CreateMenu();
2059 if (itemStates
& ITEMIS_ONLYONE
)
2061 if (itemStates
& ITEMIS_INGIT
)
2063 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2064 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnore
;
2065 myIDMap
[idCmd
++] = ShellMenuDeleteIgnore
;
2067 wcscpy_s(maskbuf
, L
"*");
2068 if (!(itemStates
& ITEMIS_FOLDER
) && wcsrchr(ignorepath
, L
'.'))
2070 wcscat_s(maskbuf
, wcsrchr(ignorepath
, L
'.'));
2071 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2072 auto verb
= std::wstring(maskbuf
);
2073 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2074 myVerbsMap
[verb
] = idCmd
;
2075 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2076 myVerbsIDMap
[idCmd
] = verb
;
2077 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnoreCaseSensitive
;
2078 myIDMap
[idCmd
++] = ShellMenuDeleteIgnoreCaseSensitive
;
2083 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2084 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
2085 myIDMap
[idCmd
++] = ShellMenuIgnore
;
2087 wcscpy_s(maskbuf
, L
"*");
2088 if (!(itemStates
& ITEMIS_FOLDER
) && wcsrchr(ignorepath
, L
'.'))
2090 wcscat_s(maskbuf
, wcsrchr(ignorepath
, L
'.'));
2091 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2092 auto verb
= std::wstring(maskbuf
);
2093 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2094 myVerbsMap
[verb
] = idCmd
;
2095 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2096 myVerbsIDMap
[idCmd
] = verb
;
2097 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2098 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2104 // note: as of Windows 7, the shell does not pass more than 16 items from a multiselection
2105 // in the Initialize() call before the QueryContextMenu() call. Which means even if the user
2106 // has selected more than 16 files, we won't know about that here.
2107 // Note: after QueryContextMenu() exits, Initialize() is called again with all selected files.
2108 if (itemStates
& ITEMIS_INGIT
)
2110 if (files_
.size() >= 16)
2112 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE2
);
2113 wcscpy_s(ignorepath
, stringtablebuffer
);
2117 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE
);
2118 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2120 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2121 auto verb
= std::wstring(ignorepath
);
2122 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2123 myVerbsMap
[verb
] = idCmd
;
2124 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2125 myVerbsIDMap
[idCmd
] = verb
;
2126 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnore
;
2127 myIDMap
[idCmd
++] = ShellMenuDeleteIgnore
;
2129 if (files_
.size() >= 16)
2131 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK2
);
2132 wcscpy_s(ignorepath
, stringtablebuffer
);
2136 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK
);
2137 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2139 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2140 verb
= std::wstring(ignorepath
);
2141 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2142 myVerbsMap
[verb
] = idCmd
;
2143 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2144 myVerbsIDMap
[idCmd
] = verb
;
2145 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnoreCaseSensitive
;
2146 myIDMap
[idCmd
++] = ShellMenuDeleteIgnoreCaseSensitive
;
2150 if (files_
.size() >= 16)
2152 MAKESTRING(IDS_MENUIGNOREMULTIPLE2
);
2153 wcscpy_s(ignorepath
, stringtablebuffer
);
2157 MAKESTRING(IDS_MENUIGNOREMULTIPLE
);
2158 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2160 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2161 auto verb
= std::wstring(ignorepath
);
2162 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2163 myVerbsMap
[verb
] = idCmd
;
2164 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2165 myVerbsIDMap
[idCmd
] = verb
;
2166 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
2167 myIDMap
[idCmd
++] = ShellMenuIgnore
;
2169 if (files_
.size() >= 16)
2171 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK2
);
2172 wcscpy_s(ignorepath
, stringtablebuffer
);
2176 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK
);
2177 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2179 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2180 verb
= std::wstring(ignorepath
);
2181 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2182 myVerbsMap
[verb
] = idCmd
;
2183 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2184 myVerbsIDMap
[idCmd
] = verb
;
2185 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2186 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2191 if (bShowIgnoreMenu
)
2193 MENUITEMINFO menuiteminfo
= { 0 };
2194 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
2195 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
2198 menuiteminfo
.fMask
|= MIIM_BITMAP
;
2199 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, icon
);
2201 menuiteminfo
.fType
= MFT_STRING
;
2202 menuiteminfo
.hSubMenu
= ignoresubmenu
;
2203 menuiteminfo
.wID
= idCmd
;
2204 SecureZeroMemory(stringtablebuffer
, sizeof(stringtablebuffer
));
2205 if (itemStates
& ITEMIS_IGNORED
)
2206 GetMenuTextFromResource(ShellMenuUnIgnoreSub
);
2207 else if (itemStates
& ITEMIS_INGIT
)
2208 GetMenuTextFromResource(ShellMenuDeleteIgnoreSub
);
2210 GetMenuTextFromResource(ShellMenuIgnoreSub
);
2211 menuiteminfo
.dwTypeData
= stringtablebuffer
;
2212 menuiteminfo
.cch
= (UINT
)min(wcslen(menuiteminfo
.dwTypeData
), UINT_MAX
);
2214 InsertMenuItem((topmenu
& MENUIGNORE
) ? hMenu
: subMenu
, (topmenu
& MENUIGNORE
) ? indexMenu
++ : indexSubMenu
++, TRUE
, &menuiteminfo
);
2215 if (itemStates
& ITEMIS_IGNORED
)
2217 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreSub
;
2218 myIDMap
[idCmd
++] = ShellMenuUnIgnoreSub
;
2222 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreSub
;
2223 myIDMap
[idCmd
++] = ShellMenuIgnoreSub
;
2226 return bShowIgnoreMenu
;
2229 void CShellExt::RunCommand(const tstring
& path
, const tstring
& command
, LPCTSTR errorMessage
)
2231 if (CCreateProcessHelper::CreateProcessDetached(path
.c_str(), command
.c_str()))
2233 // process started - exit
2237 MessageBox(nullptr, CFormatMessageWrapper(), errorMessage
, MB_OK
| MB_ICONERROR
);
2240 bool CShellExt::ShouldInsertItem(const MenuInfo
& item
) const
2242 return ShouldEnableMenu(item
.first
) || ShouldEnableMenu(item
.second
) ||
2243 ShouldEnableMenu(item
.third
) || ShouldEnableMenu(item
.fourth
);
2246 bool CShellExt::ShouldEnableMenu(const YesNoPair
& pair
) const
2248 if (pair
.yes
&& pair
.no
)
2250 if (((pair
.yes
& itemStates
) == pair
.yes
) && ((pair
.no
& (~itemStates
)) == pair
.no
))
2253 else if ((pair
.yes
) && ((pair
.yes
& itemStates
) == pair
.yes
))
2255 else if ((pair
.no
) && ((pair
.no
& (~itemStates
)) == pair
.no
))