1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012, 2014-2016, 2018 - TortoiseSVN
4 // Copyright (C) 2008-2018 - 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"
33 #include "LoadIconEx.h"
35 #define GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
36 #define GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
38 int g_shellidlist
=RegisterClipboardFormat(CFSTR_SHELLIDLIST
);
40 extern MenuInfo menuInfo
[];
41 static int g_syncSeq
= 0;
43 STDMETHODIMP
CShellExt::Initialize(LPCITEMIDLIST pIDFolder
, LPDATAOBJECT pDataObj
, HKEY
/* hRegKey */)
45 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: Initialize\n");
46 PreserveChdir preserveChdir
;
53 std::wstring statuspath
;
54 git_wc_status_kind fetchedstatus
= git_wc_status_none
;
55 // get selected files/folders
59 FORMATETC fmte
= {(CLIPFORMAT
)g_shellidlist
,
64 HRESULT hres
= pDataObj
->GetData(&fmte
, &medium
);
66 if (SUCCEEDED(hres
) && medium
.hGlobal
)
68 if (m_State
== FileStateDropHandler
)
70 if (!CRegStdDWORD(L
"Software\\TortoiseGit\\EnableDragContextMenu", TRUE
, false, HKEY_CURRENT_USER
, KEY_WOW64_64KEY
))
72 ReleaseStgMedium(&medium
);
76 FORMATETC etc
= { CF_HDROP
, nullptr, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
};
77 STGMEDIUM stg
= { TYMED_HGLOBAL
};
78 if ( FAILED( pDataObj
->GetData ( &etc
, &stg
)))
80 ReleaseStgMedium ( &medium
);
85 HDROP drop
= (HDROP
)GlobalLock(stg
.hGlobal
);
88 ReleaseStgMedium ( &stg
);
89 ReleaseStgMedium ( &medium
);
93 int count
= DragQueryFile(drop
, (UINT
)-1, nullptr, 0);
95 itemStates
|= ITEMIS_ONLYONE
;
96 for (int i
= 0; i
< count
; i
++)
98 // find the path length in chars
99 UINT len
= DragQueryFile(drop
, i
, nullptr, 0);
102 auto szFileName
= std::make_unique
<TCHAR
[]>(len
+ 1);
103 if (0 == DragQueryFile(drop
, i
, szFileName
.get(), len
+ 1))
105 auto str
= std::wstring(szFileName
.get());
106 if ((!str
.empty()) && (g_ShellCache
.IsContextPathAllowed(szFileName
.get())))
110 strpath
.SetFromWin(str
.c_str());
111 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".diff") == 0) ? ITEMIS_PATCHFILE
: 0;
112 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".patch") == 0) ? ITEMIS_PATCHFILE
: 0;
114 files_
.push_back(str
);
117 //get the git status of the item
118 git_wc_status_kind status
= git_wc_status_none
;
120 askedpath
.SetFromWin(str
.c_str());
121 CString workTreePath
;
122 askedpath
.HasAdminDir(&workTreePath
);
123 uuidSource
= workTreePath
;
126 if (g_ShellCache
.GetCacheType() == ShellCache::exe
&& g_ShellCache
.IsPathAllowed(str
.c_str()))
128 CTGitPath
tpath(str
.c_str());
129 if (!tpath
.HasAdminDir())
131 status
= git_wc_status_none
;
134 if (tpath
.IsAdminDir())
136 status
= git_wc_status_none
;
139 TGITCacheResponse itemStatus
;
140 SecureZeroMemory(&itemStatus
, sizeof(itemStatus
));
141 if (m_remoteCacheLink
.GetStatusFromRemoteCache(tpath
, &itemStatus
, true))
143 fetchedstatus
= status
= (git_wc_status_kind
)itemStatus
.m_status
;
144 if (askedpath
.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
146 itemStates
|= ITEMIS_FOLDER
;
147 if ((status
!= git_wc_status_unversioned
) && (status
!= git_wc_status_ignored
) && (status
!= git_wc_status_none
))
148 itemStates
|= ITEMIS_FOLDERINGIT
;
155 stat
.GetStatus(CTGitPath(str
.c_str()), false, false, true);
159 status
= stat
.status
->status
;
160 fetchedstatus
= status
;
161 if ( askedpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
163 itemStates
|= ITEMIS_FOLDER
;
164 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
165 itemStates
|= ITEMIS_FOLDERINGIT
;
167 //if ((stat.status->entry)&&(stat.status->entry->uuid))
168 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
172 // sometimes, git_client_status() returns with an error.
173 // in that case, we have to check if the working copy is versioned
174 // anyway to show the 'correct' context menu
175 if (askedpath
.HasAdminDir())
176 status
= git_wc_status_normal
;
182 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
185 // TODO: should we really assume any sub-directory to be versioned
186 // or only if it contains versioned files
187 itemStates
|= askedpath
.GetAdminDirMask();
189 if ((status
== git_wc_status_unversioned
) || (status
== git_wc_status_ignored
) || (status
== git_wc_status_none
))
190 itemStates
&= ~ITEMIS_INGIT
;
192 if (status
== git_wc_status_ignored
)
193 itemStates
|= ITEMIS_IGNORED
;
194 if (status
== git_wc_status_normal
)
195 itemStates
|= ITEMIS_NORMAL
;
196 if (status
== git_wc_status_conflicted
)
197 itemStates
|= ITEMIS_CONFLICTED
;
198 if (status
== git_wc_status_added
)
199 itemStates
|= ITEMIS_ADDED
;
200 if (status
== git_wc_status_deleted
)
201 itemStates
|= ITEMIS_DELETED
;
204 } // for (int i = 0; i < count; i++)
205 GlobalUnlock ( drop
);
206 ReleaseStgMedium ( &stg
);
208 } // if (m_State == FileStateDropHandler)
211 //Enumerate PIDLs which the user has selected
212 CIDA
* cida
= (CIDA
*)GlobalLock(medium
.hGlobal
);
213 ItemIDList
parent( GetPIDLFolder (cida
));
215 int count
= cida
->cidl
;
216 BOOL statfetched
= FALSE
;
217 for (int i
= 0; i
< count
; ++i
)
219 ItemIDList
child (GetPIDLItem (cida
, i
), &parent
);
220 std::wstring str
= child
.toString();
221 if ((str
.empty() == false)&&(g_ShellCache
.IsContextPathAllowed(str
.c_str())))
223 //check if our menu is requested for a git admin directory
224 if (GitAdminDir::IsAdminDirPath(str
.c_str()))
227 files_
.push_back(str
);
229 strpath
.SetFromWin(str
.c_str());
230 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".diff") == 0) ? ITEMIS_PATCHFILE
: 0;
231 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".patch") == 0) ? ITEMIS_PATCHFILE
: 0;
234 //get the git status of the item
235 git_wc_status_kind status
= git_wc_status_none
;
236 if ((g_ShellCache
.IsSimpleContext())&&(strpath
.IsDirectory()))
238 if (strpath
.HasAdminDir())
239 status
= git_wc_status_normal
;
245 if (g_ShellCache
.GetCacheType() == ShellCache::exe
&& g_ShellCache
.IsPathAllowed(str
.c_str()))
247 CTGitPath
tpath(str
.c_str());
248 if(!tpath
.HasAdminDir())
250 status
= git_wc_status_none
;
253 if(tpath
.IsAdminDir())
255 status
= git_wc_status_none
;
258 TGITCacheResponse itemStatus
;
259 SecureZeroMemory(&itemStatus
, sizeof(itemStatus
));
260 if (m_remoteCacheLink
.GetStatusFromRemoteCache(tpath
, &itemStatus
, true))
262 fetchedstatus
= status
= (git_wc_status_kind
)itemStatus
.m_status
;
263 if (strpath
.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
265 itemStates
|= ITEMIS_FOLDER
;
266 if ((status
!= git_wc_status_unversioned
) && (status
!= git_wc_status_ignored
) && (status
!= git_wc_status_none
))
267 itemStates
|= ITEMIS_FOLDERINGIT
;
269 if (status
== git_wc_status_conflicted
)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
270 itemStates
|= ITEMIS_CONFLICTED
;
276 if (strpath
.HasAdminDir())
277 stat
.GetStatus(strpath
, false, false, true);
281 status
= stat
.status
->status
;
282 fetchedstatus
= status
;
283 if ( strpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
285 itemStates
|= ITEMIS_FOLDER
;
286 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
287 itemStates
|= ITEMIS_FOLDERINGIT
;
289 // TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
290 if (status
== git_wc_status_conflicted
)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
291 itemStates
|= ITEMIS_CONFLICTED
;
292 //if ((stat.status->entry)&&(stat.status->entry->uuid))
293 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
297 // sometimes, git_client_status() returns with an error.
298 // in that case, we have to check if the working copy is versioned
299 // anyway to show the 'correct' context menu
300 if (strpath
.HasAdminDir())
302 status
= git_wc_status_normal
;
303 fetchedstatus
= status
;
311 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
315 itemStates
|= strpath
.GetAdminDirMask();
317 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
318 itemStates
&= ~ITEMIS_INGIT
;
319 if (status
== git_wc_status_ignored
)
321 itemStates
|= ITEMIS_IGNORED
;
324 if (status
== git_wc_status_normal
)
325 itemStates
|= ITEMIS_NORMAL
;
326 if (status
== git_wc_status_conflicted
)
327 itemStates
|= ITEMIS_CONFLICTED
;
328 if (status
== git_wc_status_added
)
329 itemStates
|= ITEMIS_ADDED
;
330 if (status
== git_wc_status_deleted
)
331 itemStates
|= ITEMIS_DELETED
;
334 } // for (int i = 0; i < count; ++i)
335 ItemIDList
child (GetPIDLItem (cida
, 0), &parent
);
336 if (g_ShellCache
.HasGITAdminDir(child
.toString().c_str(), FALSE
))
337 itemStates
|= ITEMIS_INVERSIONEDFOLDER
;
339 if (GitAdminDir::IsBareRepo(child
.toString().c_str()))
340 itemStates
= ITEMIS_BAREREPO
;
342 GlobalUnlock(medium
.hGlobal
);
344 // if the item is a versioned folder, check if there's a patch file
345 // in the clipboard to be used in "Apply Patch"
346 UINT cFormatDiff
= RegisterClipboardFormat(L
"TGIT_UNIFIEDDIFF");
349 if (IsClipboardFormatAvailable(cFormatDiff
))
350 itemStates
|= ITEMIS_PATCHINCLIPBOARD
;
352 if (IsClipboardFormatAvailable(CF_HDROP
))
353 itemStates
|= ITEMIS_PATHINCLIPBOARD
;
357 ReleaseStgMedium ( &medium
);
358 if (medium
.pUnkForRelease
)
360 IUnknown
* relInterface
= (IUnknown
*)medium
.pUnkForRelease
;
361 relInterface
->Release();
366 // get folder background
369 ItemIDList
list(pIDFolder
);
370 folder_
= list
.toString();
371 git_wc_status_kind status
= git_wc_status_none
;
372 if (IsClipboardFormatAvailable(CF_HDROP
))
373 itemStatesFolder
|= ITEMIS_PATHINCLIPBOARD
;
376 askedpath
.SetFromWin(folder_
.c_str());
378 if (g_ShellCache
.IsContextPathAllowed(folder_
.c_str()))
380 if (folder_
.compare(statuspath
)!=0)
382 CString worktreePath
;
383 askedpath
.HasAdminDir(&worktreePath
);
384 uuidTarget
= worktreePath
;
387 if (g_ShellCache
.GetCacheType() == ShellCache::exe
&& g_ShellCache
.IsPathAllowed(folder_
.c_str()))
389 CTGitPath
tpath(folder_
.c_str());
390 if(!tpath
.HasAdminDir())
391 status
= git_wc_status_none
;
392 else if(tpath
.IsAdminDir())
393 status
= git_wc_status_none
;
396 TGITCacheResponse itemStatus
;
397 SecureZeroMemory(&itemStatus
, sizeof(itemStatus
));
398 if (m_remoteCacheLink
.GetStatusFromRemoteCache(tpath
, &itemStatus
, true))
399 status
= (git_wc_status_kind
)itemStatus
.m_status
;
405 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
407 status
= stat
.status
->status
;
410 // sometimes, git_client_status() returns with an error.
411 // in that case, we have to check if the working copy is versioned
412 // anyway to show the 'correct' context menu
413 if (askedpath
.HasAdminDir())
414 status
= git_wc_status_normal
;
418 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
419 itemStatesFolder
|= askedpath
.GetAdminDirMask();
421 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
422 itemStates
&= ~ITEMIS_INGIT
;
424 if (status
== git_wc_status_normal
)
425 itemStatesFolder
|= ITEMIS_NORMAL
;
426 if (status
== git_wc_status_conflicted
)
427 itemStatesFolder
|= ITEMIS_CONFLICTED
;
428 if (status
== git_wc_status_added
)
429 itemStatesFolder
|= ITEMIS_ADDED
;
430 if (status
== git_wc_status_deleted
)
431 itemStatesFolder
|= ITEMIS_DELETED
;
433 if (GitAdminDir::IsBareRepo(askedpath
.GetWinPath()))
434 itemStatesFolder
= ITEMIS_BAREREPO
;
438 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
443 status
= fetchedstatus
;
445 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
446 itemStatesFolder
|= askedpath
.GetAdminDirMask();
448 if (status
== git_wc_status_ignored
)
449 itemStatesFolder
|= ITEMIS_IGNORED
;
450 itemStatesFolder
|= ITEMIS_FOLDER
;
452 itemStates
|= ITEMIS_ONLYONE
;
453 if (m_State
!= FileStateDropHandler
)
454 itemStates
|= itemStatesFolder
;
459 status
= fetchedstatus
;
462 if (files_
.size() == 2)
463 itemStates
|= ITEMIS_TWO
;
464 if ((files_
.size() == 1)&&(g_ShellCache
.IsContextPathAllowed(files_
.front().c_str())))
466 itemStates
|= ITEMIS_ONLYONE
;
467 if (m_State
!= FileStateDropHandler
)
469 if (PathIsDirectory(files_
.front().c_str()))
471 folder_
= files_
.front();
472 git_wc_status_kind status
= git_wc_status_none
;
474 askedpath
.SetFromWin(folder_
.c_str());
476 if (folder_
.compare(statuspath
)!=0)
481 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
483 status
= stat
.status
->status
;
487 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
492 status
= fetchedstatus
;
494 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
495 itemStates
|= askedpath
.GetAdminDirMask();
497 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
498 itemStates
&= ~ITEMIS_INGIT
;
500 if (status
== git_wc_status_ignored
)
501 itemStates
|= ITEMIS_IGNORED
;
502 itemStates
|= ITEMIS_FOLDER
;
503 if (status
== git_wc_status_added
)
504 itemStates
|= ITEMIS_ADDED
;
505 if (status
== git_wc_status_deleted
)
506 itemStates
|= ITEMIS_DELETED
;
516 void CShellExt::InsertGitMenu(BOOL istop
, HMENU menu
, UINT pos
, UINT_PTR id
, UINT stringid
, UINT icon
, UINT idCmdFirst
, GitCommands com
, UINT
/*uFlags*/)
518 TCHAR menutextbuffer
[512] = {0};
519 TCHAR verbsbuffer
[255] = {0};
520 MAKESTRING(stringid
);
524 //menu entry for the top context menu, so append an "Git " before
525 //the menu text to indicate where the entry comes from
526 wcscpy_s(menutextbuffer
, 255, L
"Git ");
527 if (!g_ShellCache
.HasShellMenuAccelerators())
529 // remove the accelerators
530 tstring temp
= stringtablebuffer
;
531 temp
.erase(std::remove(temp
.begin(), temp
.end(), '&'), temp
.end());
532 wcscpy_s(stringtablebuffer
, 255, temp
.c_str());
535 wcscat_s(menutextbuffer
, 255, stringtablebuffer
);
537 // insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
538 // so we have an easy and fast way to check the current branch
539 // (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
540 if (com
== ShellMenuCommit
)
543 CTGitPath
path(folder_
.empty() ? files_
.front().c_str() : folder_
.c_str());
544 CString sProjectRoot
;
547 if (path
.GetAdminDirMask() & ITEMIS_SUBMODULE
)
550 wcscpy_s(menutextbuffer
, 255, L
"Git ");
552 menutextbuffer
[0] = L
'\0';
553 MAKESTRING(IDS_MENUCOMMITSUBMODULE
);
554 wcscat_s(menutextbuffer
, 255, stringtablebuffer
);
557 if (path
.HasAdminDir(&sProjectRoot
) && !CGit::GetCurrentBranchFromFile(sProjectRoot
, sBranchName
))
559 if (sBranchName
.GetLength() == 2 * GIT_HASH_SIZE
)
561 // if SHA1 only show 4 first bytes
563 for (int i
= 0; i
< 2 * GIT_HASH_SIZE
; ++i
)
564 if ( !iswxdigit(sBranchName
[i
]) )
570 sBranchName
= sBranchName
.Left(g_Git
.GetShortHASHLength()) + L
"...";
574 if (sBranchName
.GetLength() > 64)
575 sBranchName
= sBranchName
.Left(64) + L
"...";
577 // scan to before "..."
578 LPTSTR s
= menutextbuffer
+ wcslen(menutextbuffer
)-1;
579 if (s
> menutextbuffer
)
581 while (s
> menutextbuffer
)
596 // append branch name and end with ...
597 wcscpy_s(s
, 255 - wcslen(menutextbuffer
) - 1, L
" -> \"" + sBranchName
+ L
"\"...");
601 if (com
== ShellMenuDiffLater
)
603 std::wstring sPath
= regDiffLater
;
606 // add the path of the saved file
607 wchar_t compact
[2 * GIT_HASH_SIZE
] = { 0 };
608 PathCompactPathEx(compact
, sPath
.c_str(), _countof(compact
) - 1, 0);
609 MAKESTRING(IDS_MENUDIFFNOW
);
611 sMenu
.Format(CString(stringtablebuffer
), compact
);
612 wcscpy_s(menutextbuffer
, sMenu
);
616 MENUITEMINFO menuiteminfo
= { 0 };
617 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
618 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_STRING
;
619 menuiteminfo
.fType
= MFT_STRING
;
620 menuiteminfo
.dwTypeData
= menutextbuffer
;
623 menuiteminfo
.fMask
|= MIIM_BITMAP
;
624 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, icon
);
626 menuiteminfo
.wID
= (UINT
)id
;
627 InsertMenuItem(menu
, pos
, TRUE
, &menuiteminfo
);
631 //menu entry for the top context menu, so append an "Git " before
632 //the menu text to indicate where the entry comes from
633 wcscpy_s(menutextbuffer
, 255, L
"Git ");
635 LoadString(g_hResInst
, stringid
, verbsbuffer
, _countof(verbsbuffer
));
636 wcscat_s(menutextbuffer
, 255, verbsbuffer
);
637 auto verb
= std::wstring(menutextbuffer
);
638 if (verb
.find('&') != -1)
640 verb
.erase(verb
.find('&'),1);
642 myVerbsMap
[verb
] = id
- idCmdFirst
;
643 myVerbsMap
[verb
] = id
;
644 myVerbsIDMap
[id
- idCmdFirst
] = verb
;
645 myVerbsIDMap
[id
] = verb
;
646 // We store the relative and absolute diameter
647 // (drawitem callback uses absolute, others relative)
648 myIDMap
[id
- idCmdFirst
] = com
;
651 mySubMenuMap
[pos
] = com
;
654 bool CShellExt::WriteClipboardPathsToTempFile(std::wstring
& tempfile
)
656 tempfile
= std::wstring();
657 //write all selected files and paths to a temporary file
658 //for TortoiseGitProc.exe to read out again.
660 DWORD pathlength
= GetTortoiseGitTempPath(0, nullptr);
661 auto path
= std::make_unique
<TCHAR
[]>(pathlength
+ 1);
662 auto tempFile
= std::make_unique
<TCHAR
[]>(pathlength
+ 100);
663 GetTortoiseGitTempPath(pathlength
+1, path
.get());
664 GetTempFileName(path
.get(), L
"git", 0, tempFile
.get());
665 tempfile
= std::wstring(tempFile
.get());
667 CAutoFile file
= ::CreateFile(tempFile
.get(),
672 FILE_ATTRIBUTE_TEMPORARY
,
678 if (!IsClipboardFormatAvailable(CF_HDROP
))
680 if (!OpenClipboard(nullptr))
683 HGLOBAL hglb
= GetClipboardData(CF_HDROP
);
689 HDROP hDrop
= (HDROP
)GlobalLock(hglb
);
692 SCOPE_EXIT
{ GlobalUnlock(hDrop
); };
694 TCHAR szFileName
[MAX_PATH
] = {0};
695 UINT cFiles
= DragQueryFile(hDrop
, 0xFFFFFFFF, nullptr, 0);
696 for(UINT i
= 0; i
< cFiles
; ++i
)
698 DragQueryFile(hDrop
, i
, szFileName
, _countof(szFileName
));
699 std::wstring filename
= szFileName
;
700 ::WriteFile (file
, filename
.c_str(), (DWORD
)filename
.size()*sizeof(TCHAR
), &written
, 0);
701 ::WriteFile(file
, L
"\n", 2, &written
, 0);
707 std::wstring
CShellExt::WriteFileListToTempFile()
709 //write all selected files and paths to a temporary file
710 //for TortoiseGitProc.exe to read out again.
711 DWORD pathlength
= GetTortoiseGitTempPath(0, nullptr);
712 auto path
= std::make_unique
<TCHAR
[]>(pathlength
+ 1);
713 auto tempFile
= std::make_unique
<TCHAR
[]>(pathlength
+ 100);
714 GetTortoiseGitTempPath(pathlength
+ 1, path
.get());
715 GetTempFileName(path
.get(), L
"git", 0, tempFile
.get());
716 auto retFilePath
= std::wstring(tempFile
.get());
718 CAutoFile file
= ::CreateFile (tempFile
.get(),
723 FILE_ATTRIBUTE_TEMPORARY
,
728 MessageBox(nullptr, L
"Could not create temporary file. Please check (the permissions of) your temp-folder: " + CString(tempFile
.get()), L
"TortoiseGit", MB_ICONERROR
);
729 return std::wstring();
735 ::WriteFile (file
, folder_
.c_str(), (DWORD
)folder_
.size()*sizeof(TCHAR
), &written
, 0);
736 ::WriteFile(file
, L
"\n", 2, &written
, 0);
739 for (const auto& file_
: files_
)
741 ::WriteFile(file
, file_
.c_str(), (DWORD
)file_
.size() * sizeof(TCHAR
), &written
, 0);
742 ::WriteFile(file
, L
"\n", 2, &written
, 0);
747 STDMETHODIMP
CShellExt::QueryDropContext(UINT uFlags
, UINT idCmdFirst
, HMENU hMenu
, UINT
&indexMenu
)
749 if (!CRegStdDWORD(L
"Software\\TortoiseGit\\EnableDragContextMenu", TRUE
, false, HKEY_CURRENT_USER
, KEY_WOW64_64KEY
))
752 PreserveChdir preserveChdir
;
755 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
756 return S_OK
; //we don't change the default action
758 if (files_
.empty() || folder_
.empty())
761 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
764 bool bSourceAndTargetFromSameRepository
= ((uuidSource
.size() == uuidTarget
.size() && _wcsnicmp(uuidSource
.c_str(), uuidTarget
.c_str(), uuidSource
.size()) == 0)) || uuidSource
.empty() || uuidTarget
.empty();
766 //the drop handler only has eight commands, but not all are visible at the same time:
767 //if the source file(s) are under version control then those files can be moved
768 //to the new location or they can be moved with a rename,
769 //if they are unversioned then they can be added to the working copy
770 //if they are versioned, they also can be exported to an unversioned location
771 UINT idCmd
= idCmdFirst
;
773 bool moveAvailable
= false;
775 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
776 if ((bSourceAndTargetFromSameRepository
|| (itemStatesFolder
& ITEMIS_ADDED
)) && (itemStatesFolder
& ITEMIS_FOLDERINGIT
) && ((itemStates
& (ITEMIS_NORMAL
| ITEMIS_INGIT
| ITEMIS_FOLDERINGIT
))))
778 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVEMENU
, 0, idCmdFirst
, ShellMenuDropMove
, uFlags
);
779 moveAvailable
= true;
782 // Git move and rename here
783 // 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
784 if ((bSourceAndTargetFromSameRepository
|| (itemStatesFolder
& ITEMIS_ADDED
)) && (itemStatesFolder
& ITEMIS_FOLDERINGIT
) && (itemStates
& (ITEMIS_NORMAL
| ITEMIS_INGIT
| ITEMIS_FOLDERINGIT
)) && (itemStates
& ITEMIS_ONLYONE
))
786 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVERENAMEMENU
, 0, idCmdFirst
, ShellMenuDropMoveRename
, uFlags
);
787 moveAvailable
= true;
791 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
792 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&((~itemStates) & ITEMIS_ADDED))
793 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYMENU, 0, idCmdFirst, ShellMenuDropCopy, uFlags);
795 // Git copy and rename here, source and target from same repository
796 // available if source is a single, versioned but not added item, target is versioned or target folder is added
797 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
798 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYRENAMEMENU, 0, idCmdFirst, ShellMenuDropCopyRename, uFlags);
801 // available if target is versioned and source is either unversioned or from another repository
802 if ((itemStatesFolder
& ITEMIS_FOLDERINGIT
) && (((~itemStates
) & ITEMIS_INGIT
) || !bSourceAndTargetFromSameRepository
) && !moveAvailable
)
803 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYADDMENU
, 0, idCmdFirst
, ShellMenuDropCopyAdd
, uFlags
);
806 // available if source is versioned and a folder
807 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
808 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
810 // Git export all here
811 // available if source is versioned and a folder
812 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
813 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
816 // available if source is a patchfile
817 if (itemStates
& ITEMIS_PATCHFILE
)
819 if (itemStates
& ITEMIS_ONLYONE
)
820 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_MENUAPPLYPATCH
, 0, idCmdFirst
, ShellMenuApplyPatch
, uFlags
);
821 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_MENUIMPORTPATCH
, 0, idCmdFirst
, ShellMenuImportPatchDrop
, uFlags
);
825 if (idCmd
!= idCmdFirst
)
826 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr);
830 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
833 STDMETHODIMP
CShellExt::QueryContextMenu(HMENU hMenu
, UINT indexMenu
, UINT idCmdFirst
, UINT
/*idCmdLast*/, UINT uFlags
)
835 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: QueryContextMenu itemStates=%ld\n", itemStates
);
836 PreserveChdir preserveChdir
;
838 //first check if our drop handler is called
839 //and then (if true) provide the context menu for the
841 if (m_State
== FileStateDropHandler
)
843 return QueryDropContext(uFlags
, idCmdFirst
, hMenu
, indexMenu
);
846 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
847 return S_OK
; //we don't change the default action
849 if (files_
.empty() && folder_
.empty())
852 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
859 CSIDL_COMMON_FAVORITES
,
860 CSIDL_COMMON_STARTMENU
,
861 CSIDL_COMPUTERSNEARME
,
869 CSIDL_INTERNET_CACHE
,
879 if (IsIllegalFolder(folder_
, csidlarray
))
884 // folder is empty, but maybe files are selected
886 return S_OK
; // nothing selected - we don't have a menu to show
887 // check whether a selected entry is an UID - those are namespace extensions
888 // which we can't handle
889 if (std::any_of(files_
.cbegin(), files_
.cend(), [](auto& file
) { return CStringUtils::StartsWith(file
.c_str(), L
"::{"); }))
894 if (CStringUtils::StartsWith(folder_
.c_str(), L
"::{"))
898 if (((uFlags
& CMF_EXTENDEDVERBS
) == 0) && g_ShellCache
.HideMenusForUnversionedItems())
900 if ((itemStates
& (ITEMIS_INGIT
| ITEMIS_INVERSIONEDFOLDER
| ITEMIS_FOLDERINGIT
| ITEMIS_BAREREPO
)) == 0)
904 //check if our menu is requested for a git admin directory
905 if (GitAdminDir::IsAdminDirPath(folder_
.c_str()))
908 if (uFlags
& CMF_EXTENDEDVERBS
)
909 itemStates
|= ITEMIS_EXTENDED
;
912 if (!std::wstring(regDiffLater
).empty())
913 itemStates
|= ITEMIS_HASDIFFLATER
;
915 const BOOL bShortcut
= !!(uFlags
& CMF_VERBSONLY
);
916 if ( bShortcut
&& (files_
.size()==1))
918 // Don't show the context menu for a link if the
919 // destination is not part of a working copy.
920 // It would only show the standard menu items
921 // which are already shown for the lnk-file.
922 CString path
= files_
.front().c_str();
923 if (!GitAdminDir::HasAdminDir(path
))
929 //check if we already added our menu entry for a folder.
930 //we check that by iterating through all menu entries and check if
931 //the dwItemData member points to our global ID string. That string is set
932 //by our shell extension when the folder menu is inserted.
933 TCHAR menubuf
[MAX_PATH
] = {0};
934 int count
= GetMenuItemCount(hMenu
);
935 for (int i
=0; i
<count
; ++i
)
937 MENUITEMINFO miif
= { 0 };
938 miif
.cbSize
= sizeof(MENUITEMINFO
);
939 miif
.fMask
= MIIM_DATA
;
940 miif
.dwTypeData
= menubuf
;
941 miif
.cch
= _countof(menubuf
);
942 GetMenuItemInfo(hMenu
, i
, TRUE
, &miif
);
943 if (miif
.dwItemData
== (ULONG_PTR
)g_MenuIDString
)
948 UINT idCmd
= idCmdFirst
;
950 //create the sub menu
951 HMENU subMenu
= CreateMenu();
952 int indexSubMenu
= 0;
954 unsigned __int64 topmenu
= g_ShellCache
.GetMenuLayout();
955 unsigned __int64 menumask
= g_ShellCache
.GetMenuMask();
956 unsigned __int64 menuex
= g_ShellCache
.GetMenuExt();
959 bool bAddSeparator
= false;
960 bool bMenuEntryAdded
= false;
961 bool bMenuEmpty
= true;
962 // insert separator at start
963 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
964 bool bShowIcons
= !!DWORD(CRegStdDWORD(L
"Software\\TortoiseGit\\ShowContextMenuIcons", TRUE
, false, HKEY_CURRENT_USER
, KEY_WOW64_64KEY
));
966 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
968 if (menuInfo
[menuIndex
].command
== ShellSeparator
)
970 // we don't add a separator immediately. Because there might not be
971 // another 'normal' menu entry after we insert a separator.
972 // we simply set a flag here, indicating that before the next
973 // 'normal' menu entry, a separator should be added.
975 bAddSeparator
= true;
977 bAddSeparator
= true;
981 // check the conditions whether to show the menu entry or not
982 bool bInsertMenu
= ShouldInsertItem(menuInfo
[menuIndex
]);
983 if (menuInfo
[menuIndex
].menuID
& menuex
)
985 if( !(itemStates
& ITEMIS_EXTENDED
) )
989 if (menuInfo
[menuIndex
].menuID
& (~menumask
))
993 bool bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
994 // insert a separator
995 if ((bMenuEntryAdded
)&&(bAddSeparator
)&&(!bIsTop
))
997 bAddSeparator
= false;
998 bMenuEntryAdded
= false;
999 InsertMenu(subMenu
, indexSubMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, nullptr);
1003 // handle special cases (sub menus)
1004 if ((menuInfo
[menuIndex
].command
== ShellMenuIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuUnIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuDeleteIgnoreSub
))
1006 if(InsertIgnoreSubmenus(idCmd
, idCmdFirst
, hMenu
, subMenu
, indexMenu
, indexSubMenu
, topmenu
, bShowIcons
, uFlags
))
1007 bMenuEntryAdded
= true;
1011 bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1013 // insert the menu entry
1014 InsertGitMenu( bIsTop
,
1015 bIsTop
? hMenu
: subMenu
,
1016 bIsTop
? indexMenu
++ : indexSubMenu
++,
1018 menuInfo
[menuIndex
].menuTextID
,
1019 bShowIcons
? menuInfo
[menuIndex
].iconID
: 0,
1021 menuInfo
[menuIndex
].command
,
1025 bMenuEntryAdded
= true;
1027 bAddSeparator
= false;
1036 // do not show TortoiseGit menu if it's empty
1039 if (idCmd
- idCmdFirst
> 0)
1042 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
1046 //return number of menu items added
1047 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
1050 //add sub menu to main context menu
1051 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1052 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1053 MAKESTRING(IDS_MENUSUBMENU
);
1054 if (!g_ShellCache
.HasShellMenuAccelerators())
1056 // remove the accelerators
1057 tstring temp
= stringtablebuffer
;
1058 temp
.erase(std::remove(temp
.begin(), temp
.end(), '&'), temp
.end());
1059 wcscpy_s(stringtablebuffer
, temp
.c_str());
1061 MENUITEMINFO menuiteminfo
= { 0 };
1062 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
1063 menuiteminfo
.fType
= MFT_STRING
;
1064 menuiteminfo
.dwTypeData
= stringtablebuffer
;
1066 UINT uIcon
= bShowIcons
? IDI_APP
: 0;
1067 if (!folder_
.empty())
1069 uIcon
= bShowIcons
? IDI_MENUFOLDER
: 0;
1070 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFolder
;
1071 myIDMap
[idCmd
] = ShellSubMenuFolder
;
1072 menuiteminfo
.dwItemData
= (ULONG_PTR
)g_MenuIDString
;
1074 else if (!bShortcut
&& (files_
.size()==1))
1076 uIcon
= bShowIcons
? IDI_MENUFILE
: 0;
1077 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFile
;
1078 myIDMap
[idCmd
] = ShellSubMenuFile
;
1080 else if (bShortcut
&& (files_
.size()==1))
1082 uIcon
= bShowIcons
? IDI_MENULINK
: 0;
1083 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuLink
;
1084 myIDMap
[idCmd
] = ShellSubMenuLink
;
1086 else if (!files_
.empty())
1088 uIcon
= bShowIcons
? IDI_MENUMULTIPLE
: 0;
1089 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuMultiple
;
1090 myIDMap
[idCmd
] = ShellSubMenuMultiple
;
1094 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenu
;
1095 myIDMap
[idCmd
] = ShellSubMenu
;
1097 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
1100 menuiteminfo
.fMask
|= MIIM_BITMAP
;
1101 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, uIcon
);
1103 menuiteminfo
.hSubMenu
= subMenu
;
1104 menuiteminfo
.wID
= idCmd
++;
1105 InsertMenuItem(hMenu
, indexMenu
++, TRUE
, &menuiteminfo
);
1108 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
1112 //return number of menu items added
1113 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
1116 void CShellExt::TweakMenu(HMENU hMenu
)
1118 MENUINFO MenuInfo
= {};
1119 MenuInfo
.cbSize
= sizeof(MenuInfo
);
1120 MenuInfo
.fMask
= MIM_STYLE
| MIM_APPLYTOSUBMENUS
;
1121 MenuInfo
.dwStyle
= MNS_CHECKORBMP
;
1122 SetMenuInfo(hMenu
, &MenuInfo
);
1125 void CShellExt::AddPathCommand(tstring
& gitCmd
, LPCTSTR command
, bool bFilesAllowed
)
1128 gitCmd
+= L
" /path:\"";
1129 if ((bFilesAllowed
) && !files_
.empty())
1130 gitCmd
+= files_
.front();
1136 void CShellExt::AddPathFileCommand(tstring
& gitCmd
, LPCTSTR command
)
1138 tstring tempfile
= WriteFileListToTempFile();
1140 gitCmd
+= L
" /pathfile:\"";
1143 gitCmd
+= L
" /deletepathfile";
1146 void CShellExt::AddPathFileDropCommand(tstring
& gitCmd
, LPCTSTR command
)
1148 tstring tempfile
= WriteFileListToTempFile();
1150 gitCmd
+= L
" /pathfile:\"";
1153 gitCmd
+= L
" /deletepathfile";
1154 gitCmd
+= L
" /droptarget:\"";
1159 // This is called when you invoke a command on the menu:
1160 STDMETHODIMP
CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi
)
1162 PreserveChdir preserveChdir
;
1163 HRESULT hr
= E_INVALIDARG
;
1167 if (!files_
.empty() || !folder_
.empty())
1169 UINT_PTR idCmd
= LOWORD(lpcmi
->lpVerb
);
1171 if (HIWORD(lpcmi
->lpVerb
))
1173 auto verb
= std::wstring(MultibyteToWide(lpcmi
->lpVerb
));
1174 const auto verb_it
= myVerbsMap
.lower_bound(verb
);
1175 if (verb_it
!= myVerbsMap
.end() && verb_it
->first
== verb
)
1176 idCmd
= verb_it
->second
;
1181 // See if we have a handler interface for this id
1182 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1183 if (id_it
!= myIDMap
.end() && id_it
->first
== idCmd
)
1185 tstring
tortoiseProcPath(CPathUtils::GetAppDirectory(g_hmodThisDll
) + L
"TortoiseGitProc.exe");
1186 tstring
tortoiseMergePath(CPathUtils::GetAppDirectory(g_hmodThisDll
) + L
"TortoiseGitMerge.exe");
1188 //TortoiseGitProc expects a command line of the form:
1189 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1191 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1193 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1194 //* pathfile is a path to a temporary file which contains a list of file paths
1195 std::wstring gitCmd
= L
" /command:";
1196 std::wstring tempfile
;
1197 switch (id_it
->second
)
1202 TCHAR syncSeq
[12] = { 0 };
1203 swprintf_s(syncSeq
, L
"%d", g_syncSeq
++);
1204 AddPathCommand(gitCmd
, L
"sync", false);
1205 gitCmd
+= L
" /seq:";
1209 case ShellMenuSubSync
:
1210 AddPathFileCommand(gitCmd
, L
"subsync");
1211 if (itemStatesFolder
& ITEMIS_SUBMODULECONTAINER
|| (itemStates
& ITEMIS_SUBMODULECONTAINER
&& itemStates
& ITEMIS_WCROOT
&& itemStates
& ITEMIS_ONLYONE
))
1213 gitCmd
+= L
" /bkpath:\"";
1218 case ShellMenuUpdateExt
:
1219 AddPathFileCommand(gitCmd
, L
"subupdate");
1220 if (itemStatesFolder
& ITEMIS_SUBMODULECONTAINER
|| (itemStates
& ITEMIS_SUBMODULECONTAINER
&& itemStates
& ITEMIS_WCROOT
&& itemStates
& ITEMIS_ONLYONE
))
1222 gitCmd
+= L
" /bkpath:\"";
1227 case ShellMenuCommit
:
1228 AddPathFileCommand(gitCmd
, L
"commit");
1231 AddPathFileCommand(gitCmd
, L
"add");
1233 case ShellMenuIgnore
:
1234 AddPathFileCommand(gitCmd
, L
"ignore");
1236 case ShellMenuIgnoreCaseSensitive
:
1237 AddPathFileCommand(gitCmd
, L
"ignore");
1238 gitCmd
+= L
" /onlymask";
1240 case ShellMenuDeleteIgnore
:
1241 AddPathFileCommand(gitCmd
, L
"ignore");
1242 gitCmd
+= L
" /delete";
1244 case ShellMenuDeleteIgnoreCaseSensitive
:
1245 AddPathFileCommand(gitCmd
, L
"ignore");
1246 gitCmd
+= L
" /delete /onlymask";
1248 case ShellMenuUnIgnore
:
1249 AddPathFileCommand(gitCmd
, L
"unignore");
1251 case ShellMenuUnIgnoreCaseSensitive
:
1252 AddPathFileCommand(gitCmd
, L
"unignore");
1253 gitCmd
+= L
" /onlymask";
1255 case ShellMenuMergeAbort
:
1256 AddPathCommand(gitCmd
, L
"merge", false);
1257 gitCmd
+= L
" /abort";
1259 case ShellMenuRevert
:
1260 AddPathFileCommand(gitCmd
, L
"revert");
1262 case ShellMenuCleanup
:
1263 AddPathFileCommand(gitCmd
, L
"cleanup");
1265 case ShellMenuSendMail
:
1266 AddPathFileCommand(gitCmd
, L
"sendmail");
1268 case ShellMenuResolve
:
1269 AddPathFileCommand(gitCmd
, L
"resolve");
1271 case ShellMenuSwitch
:
1272 AddPathCommand(gitCmd
, L
"switch", false);
1274 case ShellMenuExport
:
1275 AddPathCommand(gitCmd
, L
"export", false);
1277 case ShellMenuAbout
:
1280 case ShellMenuCreateRepos
:
1281 AddPathCommand(gitCmd
, L
"repocreate", false);
1283 case ShellMenuMerge
:
1284 AddPathCommand(gitCmd
, L
"merge", false);
1287 AddPathCommand(gitCmd
, L
"copy", true);
1289 case ShellMenuSettings
:
1290 AddPathCommand(gitCmd
, L
"settings", true);
1295 case ShellMenuRename
:
1296 AddPathCommand(gitCmd
, L
"rename", true);
1298 case ShellMenuRemove
:
1299 AddPathFileCommand(gitCmd
, L
"remove");
1300 if (itemStates
& ITEMIS_SUBMODULE
)
1301 gitCmd
+= L
" /submodule";
1303 case ShellMenuRemoveKeep
:
1304 AddPathFileCommand(gitCmd
, L
"remove");
1305 gitCmd
+= L
" /keep";
1308 gitCmd
+= L
"diff /path:\"";
1309 if (files_
.size() == 1)
1310 gitCmd
+= files_
.front();
1311 else if (files_
.size() == 2)
1313 auto I
= files_
.cbegin();
1316 gitCmd
+= L
"\" /path2:\"";
1322 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1323 gitCmd
+= L
" /alternative";
1325 case ShellMenuDiffLater
:
1326 if (lpcmi
->fMask
& CMIC_MASK_CONTROL_DOWN
)
1329 regDiffLater
.removeValue();
1331 else if (files_
.size() == 1)
1333 if (std::wstring(regDiffLater
).empty())
1336 regDiffLater
= files_
[0];
1340 AddPathCommand(gitCmd
, L
"diff", true);
1341 gitCmd
+= L
" /path2:\"";
1342 gitCmd
+= std::wstring(regDiffLater
);
1344 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1345 gitCmd
+= L
" /alternative";
1346 regDiffLater
.removeValue();
1352 case ShellMenuPrevDiff
:
1353 AddPathCommand(gitCmd
, L
"prevdiff", true);
1354 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1355 gitCmd
+= L
" /alternative";
1357 case ShellMenuDiffTwo
:
1358 AddPathCommand(gitCmd
, L
"diffcommits", true);
1360 case ShellMenuDropCopyAdd
:
1361 AddPathFileDropCommand(gitCmd
, L
"dropcopyadd");
1363 case ShellMenuDropCopy
:
1364 AddPathFileDropCommand(gitCmd
, L
"dropcopy");
1366 case ShellMenuDropCopyRename
:
1367 AddPathFileDropCommand(gitCmd
, L
"dropcopy");
1368 gitCmd
+= L
" /rename";
1370 case ShellMenuDropMove
:
1371 AddPathFileDropCommand(gitCmd
, L
"dropmove");
1373 case ShellMenuDropMoveRename
:
1374 AddPathFileDropCommand(gitCmd
, L
"dropmove");
1375 gitCmd
+= L
" /rename";
1377 case ShellMenuDropExport
:
1378 AddPathFileDropCommand(gitCmd
, L
"dropexport");
1380 case ShellMenuDropExportExtended
:
1381 AddPathFileDropCommand(gitCmd
, L
"dropexport");
1382 gitCmd
+= L
" /extended";
1385 case ShellMenuLogSubmoduleFolder
:
1386 AddPathCommand(gitCmd
, L
"log", true);
1387 if (id_it
->second
== ShellMenuLogSubmoduleFolder
)
1388 gitCmd
+= L
" /submodule";
1390 case ShellMenuDaemon
:
1391 AddPathCommand(gitCmd
, L
"daemon", true);
1393 case ShellMenuRevisionGraph
:
1394 AddPathCommand(gitCmd
, L
"revisiongraph", true);
1396 case ShellMenuConflictEditor
:
1397 AddPathCommand(gitCmd
, L
"conflicteditor", true);
1398 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1399 gitCmd
+= L
" /alternative";
1401 case ShellMenuGitSVNRebase
:
1402 AddPathCommand(gitCmd
, L
"svnrebase", false);
1404 case ShellMenuGitSVNDCommit
:
1405 AddPathCommand(gitCmd
, L
"svndcommit", true);
1407 case ShellMenuGitSVNDFetch
:
1408 AddPathCommand(gitCmd
, L
"svnfetch", false);
1410 case ShellMenuGitSVNIgnore
:
1411 AddPathCommand(gitCmd
, L
"svnignore", false);
1413 case ShellMenuRebase
:
1414 AddPathCommand(gitCmd
, L
"rebase", false);
1416 case ShellMenuShowChanged
:
1417 if (files_
.size() > 1)
1418 AddPathFileCommand(gitCmd
, L
"repostatus");
1420 AddPathCommand(gitCmd
, L
"repostatus", true);
1422 case ShellMenuRepoBrowse
:
1423 AddPathCommand(gitCmd
, L
"repobrowser", false);
1425 case ShellMenuRefBrowse
:
1426 AddPathCommand(gitCmd
, L
"refbrowse", false);
1428 case ShellMenuRefLog
:
1429 AddPathCommand(gitCmd
, L
"reflog", false);
1431 case ShellMenuStashSave
:
1432 AddPathCommand(gitCmd
, L
"stashsave", true);
1434 case ShellMenuStashApply
:
1435 AddPathCommand(gitCmd
, L
"stashapply", false);
1437 case ShellMenuStashPop
:
1438 AddPathCommand(gitCmd
, L
"stashpop", false);
1440 case ShellMenuStashList
:
1441 AddPathCommand(gitCmd
, L
"reflog", false);
1442 gitCmd
+= L
" /ref:refs/stash";
1444 case ShellMenuBisectStart
:
1445 AddPathCommand(gitCmd
, L
"bisect", false);
1446 gitCmd
+= L
" /start";
1448 case ShellMenuBisectGood
:
1449 AddPathCommand(gitCmd
, L
"bisect", false);
1450 gitCmd
+= L
" /good";
1452 case ShellMenuBisectBad
:
1453 AddPathCommand(gitCmd
, L
"bisect", false);
1456 case ShellMenuBisectSkip
:
1457 AddPathCommand(gitCmd
, L
"bisect", false);
1458 gitCmd
+= L
" /skip";
1460 case ShellMenuBisectReset
:
1461 AddPathCommand(gitCmd
, L
"bisect", false);
1462 gitCmd
+= L
" /reset";
1464 case ShellMenuSubAdd
:
1465 AddPathCommand(gitCmd
, L
"subadd", false);
1467 case ShellMenuBlame
:
1468 AddPathCommand(gitCmd
, L
"blame", true);
1470 case ShellMenuApplyPatch
:
1471 if ((itemStates
& ITEMIS_PATCHINCLIPBOARD
) && ((~itemStates
) & ITEMIS_PATCHFILE
))
1473 // if there's a patch file in the clipboard, we save it
1474 // to a temporary file and tell TortoiseGitMerge to use that one
1475 UINT cFormat
= RegisterClipboardFormat(L
"TGIT_UNIFIEDDIFF");
1476 if (cFormat
&& OpenClipboard(nullptr))
1478 HGLOBAL hglb
= GetClipboardData(cFormat
);
1479 LPCSTR lpstr
= (LPCSTR
)GlobalLock(hglb
);
1481 DWORD len
= GetTortoiseGitTempPath(0, nullptr);
1482 auto path
= std::make_unique
<TCHAR
[]>(len
+ 1);
1483 auto tempF
= std::make_unique
<TCHAR
[]>(len
+ 100);
1484 GetTortoiseGitTempPath(len
+ 1, path
.get());
1485 GetTempFileName(path
.get(), TEXT("git"), 0, tempF
.get());
1486 std::wstring sTempFile
= std::wstring(tempF
.get());
1489 size_t patchlen
= strlen(lpstr
);
1490 _wfopen_s(&outFile
, sTempFile
.c_str(), L
"wb");
1493 size_t size
= fwrite(lpstr
, sizeof(char), patchlen
, outFile
);
1494 if (size
== patchlen
)
1496 itemStates
|= ITEMIS_PATCHFILE
;
1498 files_
.push_back(sTempFile
);
1506 if (itemStates
& ITEMIS_PATCHFILE
)
1508 gitCmd
= L
" /diff:\"";
1509 if (!files_
.empty())
1511 gitCmd
+= files_
.front();
1512 if (itemStatesFolder
& ITEMIS_FOLDERINGIT
)
1514 gitCmd
+= L
"\" /patchpath:\"";
1520 if (itemStates
& ITEMIS_INVERSIONEDFOLDER
)
1521 gitCmd
+= L
"\" /wc";
1527 gitCmd
= L
" /patchpath:\"";
1528 if (!files_
.empty())
1529 gitCmd
+= files_
.front();
1535 myVerbsIDMap
.clear();
1537 RunCommand(tortoiseMergePath
, gitCmd
, L
"TortoiseGitMerge launch failed");
1540 case ShellMenuClipPaste
:
1541 if (WriteClipboardPathsToTempFile(tempfile
))
1544 UINT cPrefDropFormat
= RegisterClipboardFormat(L
"Preferred DropEffect");
1545 if (cPrefDropFormat
)
1547 if (OpenClipboard(lpcmi
->hwnd
))
1549 HGLOBAL hglb
= GetClipboardData(cPrefDropFormat
);
1552 DWORD
* effect
= (DWORD
*) GlobalLock(hglb
);
1553 if (*effect
== DROPEFFECT_MOVE
)
1562 gitCmd
+= L
"pastecopy /pathfile:\"";
1564 gitCmd
+= L
"pastemove /pathfile:\"";
1567 gitCmd
+= L
" /deletepathfile";
1568 gitCmd
+= L
" /droptarget:\"";
1574 case ShellMenuClone
:
1575 AddPathCommand(gitCmd
, L
"clone", false);
1578 AddPathCommand(gitCmd
, L
"pull", false);
1581 AddPathCommand(gitCmd
, L
"push", false);
1583 case ShellMenuBranch
:
1584 AddPathCommand(gitCmd
, L
"branch", false);
1587 AddPathCommand(gitCmd
, L
"tag", false);
1589 case ShellMenuFormatPatch
:
1590 AddPathCommand(gitCmd
, L
"formatpatch", false);
1592 case ShellMenuImportPatch
:
1593 AddPathFileCommand(gitCmd
, L
"importpatch");
1595 case ShellMenuImportPatchDrop
:
1596 AddPathFileDropCommand(gitCmd
, L
"importpatch");
1598 case ShellMenuFetch
:
1599 AddPathCommand(gitCmd
, L
"fetch", false);
1605 } // switch (id_it->second)
1606 if (!gitCmd
.empty())
1608 gitCmd
+= L
" /hwnd:";
1609 TCHAR buf
[30] = { 0 };
1610 swprintf_s(buf
, L
"%p", (void*)lpcmi
->hwnd
);
1613 myVerbsIDMap
.clear();
1615 RunCommand(tortoiseProcPath
, gitCmd
, L
"TortoiseProc launch failed");
1618 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1619 } // if (files_.empty() || folder_.empty())
1623 // This is for the status bar and things like that:
1624 STDMETHODIMP
CShellExt::GetCommandString(UINT_PTR idCmd
, UINT uFlags
, UINT FAR
* /*reserved*/, LPSTR pszName
, UINT cchMax
)
1626 PreserveChdir preserveChdir
;
1627 //do we know the id?
1628 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1629 if (id_it
== myIDMap
.end() || id_it
->first
!= idCmd
)
1631 return E_INVALIDARG
; //no, we don't
1635 HRESULT hr
= E_INVALIDARG
;
1637 MAKESTRING(IDS_MENUDESCDEFAULT
);
1639 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1641 if (menuInfo
[menuIndex
].command
== (GitCommands
)id_it
->second
)
1643 MAKESTRING(menuInfo
[menuIndex
].menuDescID
);
1649 const TCHAR
* desc
= stringtablebuffer
;
1654 std::string help
= WideToMultibyte(desc
);
1655 lstrcpynA(pszName
, help
.c_str(), cchMax
- 1);
1661 std::wstring help
= desc
;
1662 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
- 1);
1668 const auto verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
1669 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
1671 std::string help
= WideToMultibyte(verb_id_it
->second
);
1672 lstrcpynA(pszName
, help
.c_str(), cchMax
- 1);
1679 const auto verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
1680 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
1682 std::wstring help
= verb_id_it
->second
;
1683 CTraceToOutputDebugString::Instance()(__FUNCTION__
": verb : %ws\n", help
.c_str());
1684 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
- 1);
1693 STDMETHODIMP
CShellExt::HandleMenuMsg(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
1696 return HandleMenuMsg2(uMsg
, wParam
, lParam
, &res
);
1699 STDMETHODIMP
CShellExt::HandleMenuMsg2(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*pResult
)
1701 PreserveChdir preserveChdir
;
1711 case WM_MEASUREITEM
:
1713 MEASUREITEMSTRUCT
* lpmis
= (MEASUREITEMSTRUCT
*)lParam
;
1716 lpmis
->itemWidth
= 16;
1717 lpmis
->itemHeight
= 16;
1724 DRAWITEMSTRUCT
* lpdis
= (DRAWITEMSTRUCT
*)lParam
;
1725 if (!lpdis
|| lpdis
->CtlType
!= ODT_MENU
)
1726 return S_OK
; //not for a menu
1727 resource
= GetMenuTextFromResource((int)myIDMap
[lpdis
->itemID
]);
1730 int iconWidth
= GetSystemMetrics(SM_CXSMICON
);
1731 int iconHeight
= GetSystemMetrics(SM_CYSMICON
);
1732 auto hIcon
= LoadIconEx(g_hResInst
, resource
, iconWidth
, iconHeight
);
1735 DrawIconEx(lpdis
->hDC
,
1737 lpdis
->rcItem
.top
+ (lpdis
->rcItem
.bottom
- lpdis
->rcItem
.top
- iconHeight
) / 2,
1738 hIcon
, iconWidth
, iconHeight
,
1739 0, nullptr, DI_NORMAL
);
1747 if (HIWORD(wParam
) != MF_POPUP
)
1749 int nChar
= LOWORD(wParam
);
1750 if (_istascii((wint_t)nChar
) && _istupper((wint_t)nChar
))
1751 nChar
= tolower(nChar
);
1752 // we have the char the user pressed, now search that char in all our
1754 std::vector
<UINT_PTR
> accmenus
;
1755 for (auto It
= mySubMenuMap
.cbegin(); It
!= mySubMenuMap
.cend(); ++It
)
1757 LPCTSTR resource
= GetMenuTextFromResource((int)mySubMenuMap
[It
->first
]);
1760 szItem
= stringtablebuffer
;
1761 TCHAR
* amp
= wcschr(szItem
, L
'&');
1765 int ampChar
= LOWORD(*amp
);
1766 if (_istascii((wint_t)ampChar
) && _istupper((wint_t)ampChar
))
1767 ampChar
= tolower(ampChar
);
1768 if (ampChar
== nChar
)
1770 // yep, we found a menu which has the pressed key
1771 // as an accelerator. Add that menu to the list to
1773 accmenus
.push_back(It
->first
);
1776 if (accmenus
.empty())
1778 // no menu with that accelerator key.
1779 *pResult
= MAKELONG(0, MNC_IGNORE
);
1782 if (accmenus
.size() == 1)
1784 // Only one menu with that accelerator key. We're lucky!
1785 // So just execute that menu entry.
1786 *pResult
= MAKELONG(accmenus
[0], MNC_EXECUTE
);
1789 if (accmenus
.size() > 1)
1791 // we have more than one menu item with this accelerator key!
1793 mif
.cbSize
= sizeof(MENUITEMINFO
);
1794 mif
.fMask
= MIIM_STATE
;
1795 for (auto it
= accmenus
.cbegin(); it
!= accmenus
.cend(); ++it
)
1797 GetMenuItemInfo((HMENU
)lParam
, (UINT
)*it
, TRUE
, &mif
);
1798 if (mif
.fState
== MFS_HILITE
)
1800 // this is the selected item, so select the next one
1802 if (it
== accmenus
.end())
1803 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
1805 *pResult
= MAKELONG(*it
, MNC_SELECT
);
1809 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
1820 LPCTSTR
CShellExt::GetMenuTextFromResource(int id
)
1822 TCHAR textbuf
[255] = { 0 };
1823 LPCTSTR resource
= nullptr;
1824 unsigned __int64 layout
= g_ShellCache
.GetMenuLayout();
1828 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1830 if (menuInfo
[menuIndex
].command
== id
)
1832 MAKESTRING(menuInfo
[menuIndex
].menuTextID
);
1833 resource
= MAKEINTRESOURCE(menuInfo
[menuIndex
].iconID
);
1836 case ShellSubMenuMultiple
:
1837 case ShellSubMenuLink
:
1838 case ShellSubMenuFolder
:
1839 case ShellSubMenuFile
:
1844 space
= (layout
& menuInfo
[menuIndex
].menuID
) ? 0 : 6;
1845 if (layout
& menuInfo
[menuIndex
].menuID
)
1847 wcscpy_s(textbuf
, 255, L
"Git ");
1848 wcscat_s(textbuf
, 255, stringtablebuffer
);
1849 wcscpy_s(stringtablebuffer
, 255, textbuf
);
1860 bool CShellExt::IsIllegalFolder(const std::wstring
& folder
, int* cslidarray
)
1863 TCHAR buf
[MAX_PATH
] = {0}; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
1864 LPITEMIDLIST pidl
= nullptr;
1865 while (cslidarray
[i
])
1869 if (SHGetFolderLocation(nullptr, cslidarray
[i
- 1], nullptr, 0, &pidl
) != S_OK
)
1871 if (!SHGetPathFromIDList(pidl
, buf
))
1873 // not a file system path, definitely illegal for our use
1874 CoTaskMemFree(pidl
);
1877 CoTaskMemFree(pidl
);
1880 if (wcscmp(buf
, folder
.c_str()) == 0)
1886 bool CShellExt::InsertIgnoreSubmenus(UINT
&idCmd
, UINT idCmdFirst
, HMENU hMenu
, HMENU subMenu
, UINT
&indexMenu
, int &indexSubMenu
, unsigned __int64 topmenu
, bool bShowIcons
, UINT
/*uFlags*/)
1888 HMENU ignoresubmenu
= nullptr;
1889 int indexignoresub
= 0;
1890 bool bShowIgnoreMenu
= false;
1891 TCHAR maskbuf
[MAX_PATH
] = {0}; // MAX_PATH is ok, since this only holds a filename
1892 TCHAR ignorepath
[MAX_PATH
] = {0}; // MAX_PATH is ok, since this only holds a filename
1895 UINT icon
= bShowIcons
? IDI_IGNORE
: 0;
1897 auto I
= files_
.cbegin();
1898 if (wcsrchr(I
->c_str(), L
'\\'))
1899 wcscpy_s(ignorepath
, wcsrchr(I
->c_str(), L
'\\') + 1);
1901 wcscpy_s(ignorepath
, I
->c_str());
1902 if ((itemStates
& ITEMIS_IGNORED
) && (!ignoredprops
.empty()))
1904 // check if the item name is ignored or the mask
1906 const size_t pathLength
= wcslen(ignorepath
);
1907 while ( (p
=ignoredprops
.find( ignorepath
,p
)) != -1 )
1909 if ( (p
==0 || ignoredprops
[p
-1]==TCHAR('\n'))
1910 && (p
+ pathLength
== ignoredprops
.length() || ignoredprops
[p
+ pathLength
+ 1] == TCHAR('\n')))
1918 ignoresubmenu
= CreateMenu();
1919 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
1920 auto verb
= std::wstring(ignorepath
);
1921 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
1922 myVerbsMap
[verb
] = idCmd
;
1923 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
1924 myVerbsIDMap
[idCmd
] = verb
;
1925 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnore
;
1926 myIDMap
[idCmd
++] = ShellMenuUnIgnore
;
1927 bShowIgnoreMenu
= true;
1929 wcscpy_s(maskbuf
, L
"*");
1930 if (wcsrchr(ignorepath
, L
'.'))
1932 wcscat_s(maskbuf
, wcsrchr(ignorepath
, L
'.'));
1933 p
= ignoredprops
.find(maskbuf
);
1935 ((ignoredprops
.compare(maskbuf
) == 0) || (ignoredprops
.find(L
'\n', p
) == p
+ wcslen(maskbuf
) + 1) || (ignoredprops
.rfind(L
'\n', p
) == p
- 1)))
1938 ignoresubmenu
= CreateMenu();
1940 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
1941 auto verb
= std::wstring(maskbuf
);
1942 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
1943 myVerbsMap
[verb
] = idCmd
;
1944 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
1945 myVerbsIDMap
[idCmd
] = verb
;
1946 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreCaseSensitive
;
1947 myIDMap
[idCmd
++] = ShellMenuUnIgnoreCaseSensitive
;
1948 bShowIgnoreMenu
= true;
1952 else if ((itemStates
& ITEMIS_IGNORED
) == 0)
1954 bShowIgnoreMenu
= true;
1955 ignoresubmenu
= CreateMenu();
1956 if (itemStates
& ITEMIS_ONLYONE
)
1958 if (itemStates
& ITEMIS_INGIT
)
1960 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
1961 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnore
;
1962 myIDMap
[idCmd
++] = ShellMenuDeleteIgnore
;
1964 wcscpy_s(maskbuf
, L
"*");
1965 if (!(itemStates
& ITEMIS_FOLDER
) && wcsrchr(ignorepath
, L
'.'))
1967 wcscat_s(maskbuf
, wcsrchr(ignorepath
, L
'.'));
1968 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
1969 auto verb
= std::wstring(maskbuf
);
1970 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
1971 myVerbsMap
[verb
] = idCmd
;
1972 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
1973 myVerbsIDMap
[idCmd
] = verb
;
1974 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnoreCaseSensitive
;
1975 myIDMap
[idCmd
++] = ShellMenuDeleteIgnoreCaseSensitive
;
1980 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
1981 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
1982 myIDMap
[idCmd
++] = ShellMenuIgnore
;
1984 wcscpy_s(maskbuf
, L
"*");
1985 if (!(itemStates
& ITEMIS_FOLDER
) && wcsrchr(ignorepath
, L
'.'))
1987 wcscat_s(maskbuf
, wcsrchr(ignorepath
, L
'.'));
1988 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
1989 auto verb
= std::wstring(maskbuf
);
1990 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
1991 myVerbsMap
[verb
] = idCmd
;
1992 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
1993 myVerbsIDMap
[idCmd
] = verb
;
1994 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
1995 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2001 // note: as of Windows 7, the shell does not pass more than 16 items from a multiselection
2002 // in the Initialize() call before the QueryContextMenu() call. Which means even if the user
2003 // has selected more than 16 files, we won't know about that here.
2004 // Note: after QueryContextMenu() exits, Initialize() is called again with all selected files.
2005 if (itemStates
& ITEMIS_INGIT
)
2007 if (files_
.size() >= 16)
2009 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE2
);
2010 wcscpy_s(ignorepath
, stringtablebuffer
);
2014 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE
);
2015 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2017 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2018 auto verb
= std::wstring(ignorepath
);
2019 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2020 myVerbsMap
[verb
] = idCmd
;
2021 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2022 myVerbsIDMap
[idCmd
] = verb
;
2023 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnore
;
2024 myIDMap
[idCmd
++] = ShellMenuDeleteIgnore
;
2026 if (files_
.size() >= 16)
2028 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK2
);
2029 wcscpy_s(ignorepath
, stringtablebuffer
);
2033 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK
);
2034 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2036 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2037 verb
= std::wstring(ignorepath
);
2038 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2039 myVerbsMap
[verb
] = idCmd
;
2040 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2041 myVerbsIDMap
[idCmd
] = verb
;
2042 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnoreCaseSensitive
;
2043 myIDMap
[idCmd
++] = ShellMenuDeleteIgnoreCaseSensitive
;
2047 if (files_
.size() >= 16)
2049 MAKESTRING(IDS_MENUIGNOREMULTIPLE2
);
2050 wcscpy_s(ignorepath
, stringtablebuffer
);
2054 MAKESTRING(IDS_MENUIGNOREMULTIPLE
);
2055 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2057 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2058 auto verb
= std::wstring(ignorepath
);
2059 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2060 myVerbsMap
[verb
] = idCmd
;
2061 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2062 myVerbsIDMap
[idCmd
] = verb
;
2063 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
2064 myIDMap
[idCmd
++] = ShellMenuIgnore
;
2066 if (files_
.size() >= 16)
2068 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK2
);
2069 wcscpy_s(ignorepath
, stringtablebuffer
);
2073 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK
);
2074 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2076 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2077 verb
= std::wstring(ignorepath
);
2078 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2079 myVerbsMap
[verb
] = idCmd
;
2080 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2081 myVerbsIDMap
[idCmd
] = verb
;
2082 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2083 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2088 if (bShowIgnoreMenu
)
2090 MENUITEMINFO menuiteminfo
= { 0 };
2091 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
2092 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
2095 menuiteminfo
.fMask
|= MIIM_BITMAP
;
2096 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, icon
);
2098 menuiteminfo
.fType
= MFT_STRING
;
2099 menuiteminfo
.hSubMenu
= ignoresubmenu
;
2100 menuiteminfo
.wID
= idCmd
;
2101 SecureZeroMemory(stringtablebuffer
, sizeof(stringtablebuffer
));
2102 if (itemStates
& ITEMIS_IGNORED
)
2103 GetMenuTextFromResource(ShellMenuUnIgnoreSub
);
2104 else if (itemStates
& ITEMIS_INGIT
)
2105 GetMenuTextFromResource(ShellMenuDeleteIgnoreSub
);
2107 GetMenuTextFromResource(ShellMenuIgnoreSub
);
2108 menuiteminfo
.dwTypeData
= stringtablebuffer
;
2109 menuiteminfo
.cch
= (UINT
)min(wcslen(menuiteminfo
.dwTypeData
), (size_t)UINT_MAX
);
2111 InsertMenuItem((topmenu
& MENUIGNORE
) ? hMenu
: subMenu
, (topmenu
& MENUIGNORE
) ? indexMenu
++ : indexSubMenu
++, TRUE
, &menuiteminfo
);
2112 if (itemStates
& ITEMIS_IGNORED
)
2114 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreSub
;
2115 myIDMap
[idCmd
++] = ShellMenuUnIgnoreSub
;
2119 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreSub
;
2120 myIDMap
[idCmd
++] = ShellMenuIgnoreSub
;
2123 return bShowIgnoreMenu
;
2126 void CShellExt::RunCommand(const tstring
& path
, const tstring
& command
, LPCTSTR errorMessage
)
2128 if (CCreateProcessHelper::CreateProcessDetached(path
.c_str(), command
.c_str()))
2130 // process started - exit
2134 MessageBox(nullptr, CFormatMessageWrapper(), errorMessage
, MB_OK
| MB_ICONERROR
);
2137 bool CShellExt::ShouldInsertItem(const MenuInfo
& item
) const
2139 return ShouldEnableMenu(item
.first
) || ShouldEnableMenu(item
.second
) ||
2140 ShouldEnableMenu(item
.third
) || ShouldEnableMenu(item
.fourth
);
2143 bool CShellExt::ShouldEnableMenu(const YesNoPair
& pair
) const
2145 if (pair
.yes
&& pair
.no
)
2147 if (((pair
.yes
& itemStates
) == pair
.yes
) && ((pair
.no
& (~itemStates
)) == pair
.no
))
2150 else if ((pair
.yes
) && ((pair
.yes
& itemStates
) == pair
.yes
))
2152 else if ((pair
.no
) && ((pair
.no
& (~itemStates
)) == pair
.no
))