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
,
44 LPDATAOBJECT pDataObj
,
47 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: Initialize\n");
48 PreserveChdir preserveChdir
;
55 std::wstring statuspath
;
56 git_wc_status_kind fetchedstatus
= git_wc_status_none
;
57 // get selected files/folders
61 FORMATETC fmte
= {(CLIPFORMAT
)g_shellidlist
,
66 HRESULT hres
= pDataObj
->GetData(&fmte
, &medium
);
68 if (SUCCEEDED(hres
) && medium
.hGlobal
)
70 if (m_State
== FileStateDropHandler
)
72 if (!CRegStdDWORD(L
"Software\\TortoiseGit\\EnableDragContextMenu", TRUE
, false, HKEY_CURRENT_USER
, KEY_WOW64_64KEY
))
74 ReleaseStgMedium(&medium
);
78 FORMATETC etc
= { CF_HDROP
, nullptr, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
};
79 STGMEDIUM stg
= { TYMED_HGLOBAL
};
80 if ( FAILED( pDataObj
->GetData ( &etc
, &stg
)))
82 ReleaseStgMedium ( &medium
);
87 HDROP drop
= (HDROP
)GlobalLock(stg
.hGlobal
);
90 ReleaseStgMedium ( &stg
);
91 ReleaseStgMedium ( &medium
);
95 int count
= DragQueryFile(drop
, (UINT
)-1, nullptr, 0);
97 itemStates
|= ITEMIS_ONLYONE
;
98 for (int i
= 0; i
< count
; i
++)
100 // find the path length in chars
101 UINT len
= DragQueryFile(drop
, i
, nullptr, 0);
104 auto szFileName
= std::make_unique
<TCHAR
[]>(len
+ 1);
105 if (0 == DragQueryFile(drop
, i
, szFileName
.get(), len
+ 1))
107 auto str
= std::wstring(szFileName
.get());
108 if ((!str
.empty()) && (g_ShellCache
.IsContextPathAllowed(szFileName
.get())))
112 strpath
.SetFromWin(str
.c_str());
113 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".diff") == 0) ? ITEMIS_PATCHFILE
: 0;
114 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".patch") == 0) ? ITEMIS_PATCHFILE
: 0;
116 files_
.push_back(str
);
119 //get the git status of the item
120 git_wc_status_kind status
= git_wc_status_none
;
122 askedpath
.SetFromWin(str
.c_str());
123 CString workTreePath
;
124 askedpath
.HasAdminDir(&workTreePath
);
125 uuidSource
= workTreePath
;
128 if (g_ShellCache
.GetCacheType() == ShellCache::exe
&& g_ShellCache
.IsPathAllowed(str
.c_str()))
130 CTGitPath
tpath(str
.c_str());
131 if (!tpath
.HasAdminDir())
133 status
= git_wc_status_none
;
136 if (tpath
.IsAdminDir())
138 status
= git_wc_status_none
;
141 TGITCacheResponse itemStatus
;
142 SecureZeroMemory(&itemStatus
, sizeof(itemStatus
));
143 if (m_remoteCacheLink
.GetStatusFromRemoteCache(tpath
, &itemStatus
, true))
145 fetchedstatus
= status
= (git_wc_status_kind
)itemStatus
.m_status
;
146 if (askedpath
.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
148 itemStates
|= ITEMIS_FOLDER
;
149 if ((status
!= git_wc_status_unversioned
) && (status
!= git_wc_status_ignored
) && (status
!= git_wc_status_none
))
150 itemStates
|= ITEMIS_FOLDERINGIT
;
157 stat
.GetStatus(CTGitPath(str
.c_str()), false, false, true);
161 status
= stat
.status
->status
;
162 fetchedstatus
= status
;
163 if ( askedpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
165 itemStates
|= ITEMIS_FOLDER
;
166 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
167 itemStates
|= ITEMIS_FOLDERINGIT
;
169 //if ((stat.status->entry)&&(stat.status->entry->uuid))
170 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
174 // sometimes, git_client_status() returns with an error.
175 // in that case, we have to check if the working copy is versioned
176 // anyway to show the 'correct' context menu
177 if (askedpath
.HasAdminDir())
178 status
= git_wc_status_normal
;
184 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
187 // TODO: should we really assume any sub-directory to be versioned
188 // or only if it contains versioned files
189 itemStates
|= askedpath
.GetAdminDirMask();
191 if ((status
== git_wc_status_unversioned
) || (status
== git_wc_status_ignored
) || (status
== git_wc_status_none
))
192 itemStates
&= ~ITEMIS_INGIT
;
194 if (status
== git_wc_status_ignored
)
195 itemStates
|= ITEMIS_IGNORED
;
196 if (status
== git_wc_status_normal
)
197 itemStates
|= ITEMIS_NORMAL
;
198 if (status
== git_wc_status_conflicted
)
199 itemStates
|= ITEMIS_CONFLICTED
;
200 if (status
== git_wc_status_added
)
201 itemStates
|= ITEMIS_ADDED
;
202 if (status
== git_wc_status_deleted
)
203 itemStates
|= ITEMIS_DELETED
;
206 } // for (int i = 0; i < count; i++)
207 GlobalUnlock ( drop
);
208 ReleaseStgMedium ( &stg
);
210 } // if (m_State == FileStateDropHandler)
213 //Enumerate PIDLs which the user has selected
214 CIDA
* cida
= (CIDA
*)GlobalLock(medium
.hGlobal
);
215 ItemIDList
parent( GetPIDLFolder (cida
));
217 int count
= cida
->cidl
;
218 BOOL statfetched
= FALSE
;
219 for (int i
= 0; i
< count
; ++i
)
221 ItemIDList
child (GetPIDLItem (cida
, i
), &parent
);
222 std::wstring str
= child
.toString();
223 if ((str
.empty() == false)&&(g_ShellCache
.IsContextPathAllowed(str
.c_str())))
225 //check if our menu is requested for a git admin directory
226 if (GitAdminDir::IsAdminDirPath(str
.c_str()))
229 files_
.push_back(str
);
231 strpath
.SetFromWin(str
.c_str());
232 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".diff") == 0) ? ITEMIS_PATCHFILE
: 0;
233 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".patch") == 0) ? ITEMIS_PATCHFILE
: 0;
236 //get the git status of the item
237 git_wc_status_kind status
= git_wc_status_none
;
238 if ((g_ShellCache
.IsSimpleContext())&&(strpath
.IsDirectory()))
240 if (strpath
.HasAdminDir())
241 status
= git_wc_status_normal
;
247 if (g_ShellCache
.GetCacheType() == ShellCache::exe
&& g_ShellCache
.IsPathAllowed(str
.c_str()))
249 CTGitPath
tpath(str
.c_str());
250 if(!tpath
.HasAdminDir())
252 status
= git_wc_status_none
;
255 if(tpath
.IsAdminDir())
257 status
= git_wc_status_none
;
260 TGITCacheResponse itemStatus
;
261 SecureZeroMemory(&itemStatus
, sizeof(itemStatus
));
262 if (m_remoteCacheLink
.GetStatusFromRemoteCache(tpath
, &itemStatus
, true))
264 fetchedstatus
= status
= (git_wc_status_kind
)itemStatus
.m_status
;
265 if (strpath
.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
267 itemStates
|= ITEMIS_FOLDER
;
268 if ((status
!= git_wc_status_unversioned
) && (status
!= git_wc_status_ignored
) && (status
!= git_wc_status_none
))
269 itemStates
|= ITEMIS_FOLDERINGIT
;
271 if (status
== git_wc_status_conflicted
)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
272 itemStates
|= ITEMIS_CONFLICTED
;
278 if (strpath
.HasAdminDir())
279 stat
.GetStatus(strpath
, false, false, true);
283 status
= stat
.status
->status
;
284 fetchedstatus
= status
;
285 if ( strpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
287 itemStates
|= ITEMIS_FOLDER
;
288 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
289 itemStates
|= ITEMIS_FOLDERINGIT
;
291 // TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
292 if (status
== git_wc_status_conflicted
)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
293 itemStates
|= ITEMIS_CONFLICTED
;
294 //if ((stat.status->entry)&&(stat.status->entry->uuid))
295 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
299 // sometimes, git_client_status() returns with an error.
300 // in that case, we have to check if the working copy is versioned
301 // anyway to show the 'correct' context menu
302 if (strpath
.HasAdminDir())
304 status
= git_wc_status_normal
;
305 fetchedstatus
= status
;
313 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
317 itemStates
|= strpath
.GetAdminDirMask();
319 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
320 itemStates
&= ~ITEMIS_INGIT
;
321 if (status
== git_wc_status_ignored
)
323 itemStates
|= ITEMIS_IGNORED
;
326 if (status
== git_wc_status_normal
)
327 itemStates
|= ITEMIS_NORMAL
;
328 if (status
== git_wc_status_conflicted
)
329 itemStates
|= ITEMIS_CONFLICTED
;
330 if (status
== git_wc_status_added
)
331 itemStates
|= ITEMIS_ADDED
;
332 if (status
== git_wc_status_deleted
)
333 itemStates
|= ITEMIS_DELETED
;
336 } // for (int i = 0; i < count; ++i)
337 ItemIDList
child (GetPIDLItem (cida
, 0), &parent
);
338 if (g_ShellCache
.HasGITAdminDir(child
.toString().c_str(), FALSE
))
339 itemStates
|= ITEMIS_INVERSIONEDFOLDER
;
341 if (GitAdminDir::IsBareRepo(child
.toString().c_str()))
342 itemStates
= ITEMIS_BAREREPO
;
344 GlobalUnlock(medium
.hGlobal
);
346 // if the item is a versioned folder, check if there's a patch file
347 // in the clipboard to be used in "Apply Patch"
348 UINT cFormatDiff
= RegisterClipboardFormat(L
"TGIT_UNIFIEDDIFF");
351 if (IsClipboardFormatAvailable(cFormatDiff
))
352 itemStates
|= ITEMIS_PATCHINCLIPBOARD
;
354 if (IsClipboardFormatAvailable(CF_HDROP
))
355 itemStates
|= ITEMIS_PATHINCLIPBOARD
;
359 ReleaseStgMedium ( &medium
);
360 if (medium
.pUnkForRelease
)
362 IUnknown
* relInterface
= (IUnknown
*)medium
.pUnkForRelease
;
363 relInterface
->Release();
368 // get folder background
371 ItemIDList
list(pIDFolder
);
372 folder_
= list
.toString();
373 git_wc_status_kind status
= git_wc_status_none
;
374 if (IsClipboardFormatAvailable(CF_HDROP
))
375 itemStatesFolder
|= ITEMIS_PATHINCLIPBOARD
;
378 askedpath
.SetFromWin(folder_
.c_str());
380 if (g_ShellCache
.IsContextPathAllowed(folder_
.c_str()))
382 if (folder_
.compare(statuspath
)!=0)
384 CString worktreePath
;
385 askedpath
.HasAdminDir(&worktreePath
);
386 uuidTarget
= worktreePath
;
389 if (g_ShellCache
.GetCacheType() == ShellCache::exe
&& g_ShellCache
.IsPathAllowed(folder_
.c_str()))
391 CTGitPath
tpath(folder_
.c_str());
392 if(!tpath
.HasAdminDir())
393 status
= git_wc_status_none
;
394 else if(tpath
.IsAdminDir())
395 status
= git_wc_status_none
;
398 TGITCacheResponse itemStatus
;
399 SecureZeroMemory(&itemStatus
, sizeof(itemStatus
));
400 if (m_remoteCacheLink
.GetStatusFromRemoteCache(tpath
, &itemStatus
, true))
401 status
= (git_wc_status_kind
)itemStatus
.m_status
;
407 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
409 status
= stat
.status
->status
;
412 // sometimes, git_client_status() returns with an error.
413 // in that case, we have to check if the working copy is versioned
414 // anyway to show the 'correct' context menu
415 if (askedpath
.HasAdminDir())
416 status
= git_wc_status_normal
;
420 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
421 itemStatesFolder
|= askedpath
.GetAdminDirMask();
423 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
424 itemStates
&= ~ITEMIS_INGIT
;
426 if (status
== git_wc_status_normal
)
427 itemStatesFolder
|= ITEMIS_NORMAL
;
428 if (status
== git_wc_status_conflicted
)
429 itemStatesFolder
|= ITEMIS_CONFLICTED
;
430 if (status
== git_wc_status_added
)
431 itemStatesFolder
|= ITEMIS_ADDED
;
432 if (status
== git_wc_status_deleted
)
433 itemStatesFolder
|= ITEMIS_DELETED
;
435 if (GitAdminDir::IsBareRepo(askedpath
.GetWinPath()))
436 itemStatesFolder
= ITEMIS_BAREREPO
;
440 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
445 status
= fetchedstatus
;
447 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
448 itemStatesFolder
|= askedpath
.GetAdminDirMask();
450 if (status
== git_wc_status_ignored
)
451 itemStatesFolder
|= ITEMIS_IGNORED
;
452 itemStatesFolder
|= ITEMIS_FOLDER
;
454 itemStates
|= ITEMIS_ONLYONE
;
455 if (m_State
!= FileStateDropHandler
)
456 itemStates
|= itemStatesFolder
;
461 status
= fetchedstatus
;
464 if (files_
.size() == 2)
465 itemStates
|= ITEMIS_TWO
;
466 if ((files_
.size() == 1)&&(g_ShellCache
.IsContextPathAllowed(files_
.front().c_str())))
468 itemStates
|= ITEMIS_ONLYONE
;
469 if (m_State
!= FileStateDropHandler
)
471 if (PathIsDirectory(files_
.front().c_str()))
473 folder_
= files_
.front();
474 git_wc_status_kind status
= git_wc_status_none
;
476 askedpath
.SetFromWin(folder_
.c_str());
478 if (folder_
.compare(statuspath
)!=0)
483 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
485 status
= stat
.status
->status
;
489 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
494 status
= fetchedstatus
;
496 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
497 itemStates
|= askedpath
.GetAdminDirMask();
499 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
500 itemStates
&= ~ITEMIS_INGIT
;
502 if (status
== git_wc_status_ignored
)
503 itemStates
|= ITEMIS_IGNORED
;
504 itemStates
|= ITEMIS_FOLDER
;
505 if (status
== git_wc_status_added
)
506 itemStates
|= ITEMIS_ADDED
;
507 if (status
== git_wc_status_deleted
)
508 itemStates
|= ITEMIS_DELETED
;
518 void CShellExt::InsertGitMenu(BOOL istop
, HMENU menu
, UINT pos
, UINT_PTR id
, UINT stringid
, UINT icon
, UINT idCmdFirst
, GitCommands com
, UINT
/*uFlags*/)
520 TCHAR menutextbuffer
[512] = {0};
521 TCHAR verbsbuffer
[255] = {0};
522 MAKESTRING(stringid
);
526 //menu entry for the top context menu, so append an "Git " before
527 //the menu text to indicate where the entry comes from
528 wcscpy_s(menutextbuffer
, 255, L
"Git ");
529 if (!g_ShellCache
.HasShellMenuAccelerators())
531 // remove the accelerators
532 tstring temp
= stringtablebuffer
;
533 temp
.erase(std::remove(temp
.begin(), temp
.end(), '&'), temp
.end());
534 wcscpy_s(stringtablebuffer
, 255, temp
.c_str());
537 wcscat_s(menutextbuffer
, 255, stringtablebuffer
);
539 // insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
540 // so we have an easy and fast way to check the current branch
541 // (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
542 if (com
== ShellMenuCommit
)
545 CTGitPath
path(folder_
.empty() ? files_
.front().c_str() : folder_
.c_str());
546 CString sProjectRoot
;
549 if (path
.GetAdminDirMask() & ITEMIS_SUBMODULE
)
552 wcscpy_s(menutextbuffer
, 255, L
"Git ");
554 menutextbuffer
[0] = L
'\0';
555 MAKESTRING(IDS_MENUCOMMITSUBMODULE
);
556 wcscat_s(menutextbuffer
, 255, stringtablebuffer
);
559 if (path
.HasAdminDir(&sProjectRoot
) && !CGit::GetCurrentBranchFromFile(sProjectRoot
, sBranchName
))
561 if (sBranchName
.GetLength() == 2 * GIT_HASH_SIZE
)
563 // if SHA1 only show 4 first bytes
565 for (int i
= 0; i
< 2 * GIT_HASH_SIZE
; ++i
)
566 if ( !iswxdigit(sBranchName
[i
]) )
572 sBranchName
= sBranchName
.Left(8) + L
"...";
576 if (sBranchName
.GetLength() > 64)
577 sBranchName
= sBranchName
.Left(64) + L
"...";
579 // scan to before "..."
580 LPTSTR s
= menutextbuffer
+ wcslen(menutextbuffer
)-1;
581 if (s
> menutextbuffer
)
583 while (s
> menutextbuffer
)
598 // append branch name and end with ...
599 wcscpy_s(s
, 255 - wcslen(menutextbuffer
) - 1, L
" -> \"" + sBranchName
+ L
"\"...");
603 if (com
== ShellMenuDiffLater
)
605 std::wstring sPath
= regDiffLater
;
608 // add the path of the saved file
609 wchar_t compact
[2 * GIT_HASH_SIZE
] = { 0 };
610 PathCompactPathEx(compact
, sPath
.c_str(), _countof(compact
) - 1, 0);
611 MAKESTRING(IDS_MENUDIFFNOW
);
613 sMenu
.Format(CString(stringtablebuffer
), compact
);
614 wcscpy_s(menutextbuffer
, sMenu
);
618 MENUITEMINFO menuiteminfo
= { 0 };
619 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
620 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_STRING
;
621 menuiteminfo
.fType
= MFT_STRING
;
622 menuiteminfo
.dwTypeData
= menutextbuffer
;
625 menuiteminfo
.fMask
|= MIIM_BITMAP
;
626 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, icon
);
628 menuiteminfo
.wID
= (UINT
)id
;
629 InsertMenuItem(menu
, pos
, TRUE
, &menuiteminfo
);
633 //menu entry for the top context menu, so append an "Git " before
634 //the menu text to indicate where the entry comes from
635 wcscpy_s(menutextbuffer
, 255, L
"Git ");
637 LoadString(g_hResInst
, stringid
, verbsbuffer
, _countof(verbsbuffer
));
638 wcscat_s(menutextbuffer
, 255, verbsbuffer
);
639 auto verb
= std::wstring(menutextbuffer
);
640 if (verb
.find('&') != -1)
642 verb
.erase(verb
.find('&'),1);
644 myVerbsMap
[verb
] = id
- idCmdFirst
;
645 myVerbsMap
[verb
] = id
;
646 myVerbsIDMap
[id
- idCmdFirst
] = verb
;
647 myVerbsIDMap
[id
] = verb
;
648 // We store the relative and absolute diameter
649 // (drawitem callback uses absolute, others relative)
650 myIDMap
[id
- idCmdFirst
] = com
;
653 mySubMenuMap
[pos
] = com
;
656 bool CShellExt::WriteClipboardPathsToTempFile(std::wstring
& tempfile
)
658 tempfile
= std::wstring();
659 //write all selected files and paths to a temporary file
660 //for TortoiseGitProc.exe to read out again.
662 DWORD pathlength
= GetTortoiseGitTempPath(0, nullptr);
663 auto path
= std::make_unique
<TCHAR
[]>(pathlength
+ 1);
664 auto tempFile
= std::make_unique
<TCHAR
[]>(pathlength
+ 100);
665 GetTortoiseGitTempPath(pathlength
+1, path
.get());
666 GetTempFileName(path
.get(), L
"git", 0, tempFile
.get());
667 tempfile
= std::wstring(tempFile
.get());
669 CAutoFile file
= ::CreateFile(tempFile
.get(),
674 FILE_ATTRIBUTE_TEMPORARY
,
680 if (!IsClipboardFormatAvailable(CF_HDROP
))
682 if (!OpenClipboard(nullptr))
685 HGLOBAL hglb
= GetClipboardData(CF_HDROP
);
691 HDROP hDrop
= (HDROP
)GlobalLock(hglb
);
694 SCOPE_EXIT
{ GlobalUnlock(hDrop
); };
696 TCHAR szFileName
[MAX_PATH
] = {0};
697 UINT cFiles
= DragQueryFile(hDrop
, 0xFFFFFFFF, nullptr, 0);
698 for(UINT i
= 0; i
< cFiles
; ++i
)
700 DragQueryFile(hDrop
, i
, szFileName
, _countof(szFileName
));
701 std::wstring filename
= szFileName
;
702 ::WriteFile (file
, filename
.c_str(), (DWORD
)filename
.size()*sizeof(TCHAR
), &written
, 0);
703 ::WriteFile(file
, L
"\n", 2, &written
, 0);
709 std::wstring
CShellExt::WriteFileListToTempFile()
711 //write all selected files and paths to a temporary file
712 //for TortoiseGitProc.exe to read out again.
713 DWORD pathlength
= GetTortoiseGitTempPath(0, nullptr);
714 auto path
= std::make_unique
<TCHAR
[]>(pathlength
+ 1);
715 auto tempFile
= std::make_unique
<TCHAR
[]>(pathlength
+ 100);
716 GetTortoiseGitTempPath(pathlength
+ 1, path
.get());
717 GetTempFileName(path
.get(), L
"git", 0, tempFile
.get());
718 auto retFilePath
= std::wstring(tempFile
.get());
720 CAutoFile file
= ::CreateFile (tempFile
.get(),
725 FILE_ATTRIBUTE_TEMPORARY
,
730 MessageBox(nullptr, L
"Could not create temporary file. Please check (the permissions of) your temp-folder: " + CString(tempFile
.get()), L
"TortoiseGit", MB_ICONERROR
);
731 return std::wstring();
737 ::WriteFile (file
, folder_
.c_str(), (DWORD
)folder_
.size()*sizeof(TCHAR
), &written
, 0);
738 ::WriteFile(file
, L
"\n", 2, &written
, 0);
741 for (const auto& file_
: files_
)
743 ::WriteFile(file
, file_
.c_str(), (DWORD
)file_
.size() * sizeof(TCHAR
), &written
, 0);
744 ::WriteFile(file
, L
"\n", 2, &written
, 0);
749 STDMETHODIMP
CShellExt::QueryDropContext(UINT uFlags
, UINT idCmdFirst
, HMENU hMenu
, UINT
&indexMenu
)
751 if (!CRegStdDWORD(L
"Software\\TortoiseGit\\EnableDragContextMenu", TRUE
, false, HKEY_CURRENT_USER
, KEY_WOW64_64KEY
))
754 PreserveChdir preserveChdir
;
757 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
758 return S_OK
; //we don't change the default action
760 if (files_
.empty() || folder_
.empty())
763 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
766 bool bSourceAndTargetFromSameRepository
= ((uuidSource
.size() == uuidTarget
.size() && _wcsnicmp(uuidSource
.c_str(), uuidTarget
.c_str(), uuidSource
.size()) == 0)) || uuidSource
.empty() || uuidTarget
.empty();
768 //the drop handler only has eight commands, but not all are visible at the same time:
769 //if the source file(s) are under version control then those files can be moved
770 //to the new location or they can be moved with a rename,
771 //if they are unversioned then they can be added to the working copy
772 //if they are versioned, they also can be exported to an unversioned location
773 UINT idCmd
= idCmdFirst
;
775 bool moveAvailable
= false;
777 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
778 if ((bSourceAndTargetFromSameRepository
|| (itemStatesFolder
& ITEMIS_ADDED
)) && (itemStatesFolder
& ITEMIS_FOLDERINGIT
) && ((itemStates
& (ITEMIS_NORMAL
| ITEMIS_INGIT
| ITEMIS_FOLDERINGIT
))))
780 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVEMENU
, 0, idCmdFirst
, ShellMenuDropMove
, uFlags
);
781 moveAvailable
= true;
784 // Git move and rename here
785 // 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
786 if ((bSourceAndTargetFromSameRepository
|| (itemStatesFolder
& ITEMIS_ADDED
)) && (itemStatesFolder
& ITEMIS_FOLDERINGIT
) && (itemStates
& (ITEMIS_NORMAL
| ITEMIS_INGIT
| ITEMIS_FOLDERINGIT
)) && (itemStates
& ITEMIS_ONLYONE
))
788 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVERENAMEMENU
, 0, idCmdFirst
, ShellMenuDropMoveRename
, uFlags
);
789 moveAvailable
= true;
793 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
794 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&((~itemStates) & ITEMIS_ADDED))
795 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYMENU, 0, idCmdFirst, ShellMenuDropCopy, uFlags);
797 // Git copy and rename here, source and target from same repository
798 // available if source is a single, versioned but not added item, target is versioned or target folder is added
799 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
800 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYRENAMEMENU, 0, idCmdFirst, ShellMenuDropCopyRename, uFlags);
803 // available if target is versioned and source is either unversioned or from another repository
804 if ((itemStatesFolder
& ITEMIS_FOLDERINGIT
) && (((~itemStates
) & ITEMIS_INGIT
) || !bSourceAndTargetFromSameRepository
) && !moveAvailable
)
805 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYADDMENU
, 0, idCmdFirst
, ShellMenuDropCopyAdd
, uFlags
);
808 // available if source is versioned and a folder
809 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
810 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
812 // Git export all here
813 // available if source is versioned and a folder
814 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
815 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
818 // available if source is a patchfile
819 if (itemStates
& ITEMIS_PATCHFILE
)
821 if (itemStates
& ITEMIS_ONLYONE
)
822 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_MENUAPPLYPATCH
, 0, idCmdFirst
, ShellMenuApplyPatch
, uFlags
);
823 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_MENUIMPORTPATCH
, 0, idCmdFirst
, ShellMenuImportPatchDrop
, uFlags
);
827 if (idCmd
!= idCmdFirst
)
828 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr);
832 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
835 STDMETHODIMP
CShellExt::QueryContextMenu(HMENU hMenu
,
841 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: QueryContextMenu itemStates=%ld\n", itemStates
);
842 PreserveChdir preserveChdir
;
844 //first check if our drop handler is called
845 //and then (if true) provide the context menu for the
847 if (m_State
== FileStateDropHandler
)
849 return QueryDropContext(uFlags
, idCmdFirst
, hMenu
, indexMenu
);
852 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
853 return S_OK
; //we don't change the default action
855 if (files_
.empty() && folder_
.empty())
858 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
865 CSIDL_COMMON_FAVORITES
,
866 CSIDL_COMMON_STARTMENU
,
867 CSIDL_COMPUTERSNEARME
,
875 CSIDL_INTERNET_CACHE
,
885 if (IsIllegalFolder(folder_
, csidlarray
))
890 // folder is empty, but maybe files are selected
892 return S_OK
; // nothing selected - we don't have a menu to show
893 // check whether a selected entry is an UID - those are namespace extensions
894 // which we can't handle
895 for (const auto& file
: files_
)
897 if (CStringUtils::StartsWith(file
.c_str(), L
"::{"))
903 if (CStringUtils::StartsWith(folder_
.c_str(), L
"::{"))
907 if (((uFlags
& CMF_EXTENDEDVERBS
) == 0) && g_ShellCache
.HideMenusForUnversionedItems())
909 if ((itemStates
& (ITEMIS_INGIT
| ITEMIS_INVERSIONEDFOLDER
| ITEMIS_FOLDERINGIT
| ITEMIS_BAREREPO
)) == 0)
913 //check if our menu is requested for a git admin directory
914 if (GitAdminDir::IsAdminDirPath(folder_
.c_str()))
917 if (uFlags
& CMF_EXTENDEDVERBS
)
918 itemStates
|= ITEMIS_EXTENDED
;
921 if (!std::wstring(regDiffLater
).empty())
922 itemStates
|= ITEMIS_HASDIFFLATER
;
924 const BOOL bShortcut
= !!(uFlags
& CMF_VERBSONLY
);
925 if ( bShortcut
&& (files_
.size()==1))
927 // Don't show the context menu for a link if the
928 // destination is not part of a working copy.
929 // It would only show the standard menu items
930 // which are already shown for the lnk-file.
931 CString path
= files_
.front().c_str();
932 if (!GitAdminDir::HasAdminDir(path
))
938 //check if we already added our menu entry for a folder.
939 //we check that by iterating through all menu entries and check if
940 //the dwItemData member points to our global ID string. That string is set
941 //by our shell extension when the folder menu is inserted.
942 TCHAR menubuf
[MAX_PATH
] = {0};
943 int count
= GetMenuItemCount(hMenu
);
944 for (int i
=0; i
<count
; ++i
)
946 MENUITEMINFO miif
= { 0 };
947 miif
.cbSize
= sizeof(MENUITEMINFO
);
948 miif
.fMask
= MIIM_DATA
;
949 miif
.dwTypeData
= menubuf
;
950 miif
.cch
= _countof(menubuf
);
951 GetMenuItemInfo(hMenu
, i
, TRUE
, &miif
);
952 if (miif
.dwItemData
== (ULONG_PTR
)g_MenuIDString
)
957 UINT idCmd
= idCmdFirst
;
959 //create the sub menu
960 HMENU subMenu
= CreateMenu();
961 int indexSubMenu
= 0;
963 unsigned __int64 topmenu
= g_ShellCache
.GetMenuLayout();
964 unsigned __int64 menumask
= g_ShellCache
.GetMenuMask();
965 unsigned __int64 menuex
= g_ShellCache
.GetMenuExt();
968 bool bAddSeparator
= false;
969 bool bMenuEntryAdded
= false;
970 bool bMenuEmpty
= true;
971 // insert separator at start
972 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
973 bool bShowIcons
= !!DWORD(CRegStdDWORD(L
"Software\\TortoiseGit\\ShowContextMenuIcons", TRUE
, false, HKEY_CURRENT_USER
, KEY_WOW64_64KEY
));
975 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
977 if (menuInfo
[menuIndex
].command
== ShellSeparator
)
979 // we don't add a separator immediately. Because there might not be
980 // another 'normal' menu entry after we insert a separator.
981 // we simply set a flag here, indicating that before the next
982 // 'normal' menu entry, a separator should be added.
984 bAddSeparator
= true;
986 bAddSeparator
= true;
990 // check the conditions whether to show the menu entry or not
991 bool bInsertMenu
= ShouldInsertItem(menuInfo
[menuIndex
]);
992 if (menuInfo
[menuIndex
].menuID
& menuex
)
994 if( !(itemStates
& ITEMIS_EXTENDED
) )
998 if (menuInfo
[menuIndex
].menuID
& (~menumask
))
1002 bool bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1003 // insert a separator
1004 if ((bMenuEntryAdded
)&&(bAddSeparator
)&&(!bIsTop
))
1006 bAddSeparator
= false;
1007 bMenuEntryAdded
= false;
1008 InsertMenu(subMenu
, indexSubMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, nullptr);
1012 // handle special cases (sub menus)
1013 if ((menuInfo
[menuIndex
].command
== ShellMenuIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuUnIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuDeleteIgnoreSub
))
1015 if(InsertIgnoreSubmenus(idCmd
, idCmdFirst
, hMenu
, subMenu
, indexMenu
, indexSubMenu
, topmenu
, bShowIcons
, uFlags
))
1016 bMenuEntryAdded
= true;
1020 bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1022 // insert the menu entry
1023 InsertGitMenu( bIsTop
,
1024 bIsTop
? hMenu
: subMenu
,
1025 bIsTop
? indexMenu
++ : indexSubMenu
++,
1027 menuInfo
[menuIndex
].menuTextID
,
1028 bShowIcons
? menuInfo
[menuIndex
].iconID
: 0,
1030 menuInfo
[menuIndex
].command
,
1034 bMenuEntryAdded
= true;
1036 bAddSeparator
= false;
1045 // do not show TortoiseGit menu if it's empty
1048 if (idCmd
- idCmdFirst
> 0)
1051 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
1055 //return number of menu items added
1056 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
1059 //add sub menu to main context menu
1060 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1061 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1062 MAKESTRING(IDS_MENUSUBMENU
);
1063 if (!g_ShellCache
.HasShellMenuAccelerators())
1065 // remove the accelerators
1066 tstring temp
= stringtablebuffer
;
1067 temp
.erase(std::remove(temp
.begin(), temp
.end(), '&'), temp
.end());
1068 wcscpy_s(stringtablebuffer
, temp
.c_str());
1070 MENUITEMINFO menuiteminfo
= { 0 };
1071 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
1072 menuiteminfo
.fType
= MFT_STRING
;
1073 menuiteminfo
.dwTypeData
= stringtablebuffer
;
1075 UINT uIcon
= bShowIcons
? IDI_APP
: 0;
1076 if (!folder_
.empty())
1078 uIcon
= bShowIcons
? IDI_MENUFOLDER
: 0;
1079 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFolder
;
1080 myIDMap
[idCmd
] = ShellSubMenuFolder
;
1081 menuiteminfo
.dwItemData
= (ULONG_PTR
)g_MenuIDString
;
1083 else if (!bShortcut
&& (files_
.size()==1))
1085 uIcon
= bShowIcons
? IDI_MENUFILE
: 0;
1086 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFile
;
1087 myIDMap
[idCmd
] = ShellSubMenuFile
;
1089 else if (bShortcut
&& (files_
.size()==1))
1091 uIcon
= bShowIcons
? IDI_MENULINK
: 0;
1092 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuLink
;
1093 myIDMap
[idCmd
] = ShellSubMenuLink
;
1095 else if (!files_
.empty())
1097 uIcon
= bShowIcons
? IDI_MENUMULTIPLE
: 0;
1098 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuMultiple
;
1099 myIDMap
[idCmd
] = ShellSubMenuMultiple
;
1103 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenu
;
1104 myIDMap
[idCmd
] = ShellSubMenu
;
1106 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
1109 menuiteminfo
.fMask
|= MIIM_BITMAP
;
1110 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, uIcon
);
1112 menuiteminfo
.hSubMenu
= subMenu
;
1113 menuiteminfo
.wID
= idCmd
++;
1114 InsertMenuItem(hMenu
, indexMenu
++, TRUE
, &menuiteminfo
);
1117 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
1121 //return number of menu items added
1122 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
1125 void CShellExt::TweakMenu(HMENU hMenu
)
1127 MENUINFO MenuInfo
= {};
1128 MenuInfo
.cbSize
= sizeof(MenuInfo
);
1129 MenuInfo
.fMask
= MIM_STYLE
| MIM_APPLYTOSUBMENUS
;
1130 MenuInfo
.dwStyle
= MNS_CHECKORBMP
;
1131 SetMenuInfo(hMenu
, &MenuInfo
);
1134 void CShellExt::AddPathCommand(tstring
& gitCmd
, LPCTSTR command
, bool bFilesAllowed
)
1137 gitCmd
+= L
" /path:\"";
1138 if ((bFilesAllowed
) && !files_
.empty())
1139 gitCmd
+= files_
.front();
1145 void CShellExt::AddPathFileCommand(tstring
& gitCmd
, LPCTSTR command
)
1147 tstring tempfile
= WriteFileListToTempFile();
1149 gitCmd
+= L
" /pathfile:\"";
1152 gitCmd
+= L
" /deletepathfile";
1155 void CShellExt::AddPathFileDropCommand(tstring
& gitCmd
, LPCTSTR command
)
1157 tstring tempfile
= WriteFileListToTempFile();
1159 gitCmd
+= L
" /pathfile:\"";
1162 gitCmd
+= L
" /deletepathfile";
1163 gitCmd
+= L
" /droptarget:\"";
1168 // This is called when you invoke a command on the menu:
1169 STDMETHODIMP
CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi
)
1171 PreserveChdir preserveChdir
;
1172 HRESULT hr
= E_INVALIDARG
;
1176 if (!files_
.empty() || !folder_
.empty())
1178 UINT_PTR idCmd
= LOWORD(lpcmi
->lpVerb
);
1180 if (HIWORD(lpcmi
->lpVerb
))
1182 auto verb
= std::wstring(MultibyteToWide(lpcmi
->lpVerb
));
1183 const auto verb_it
= myVerbsMap
.lower_bound(verb
);
1184 if (verb_it
!= myVerbsMap
.end() && verb_it
->first
== verb
)
1185 idCmd
= verb_it
->second
;
1190 // See if we have a handler interface for this id
1191 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1192 if (id_it
!= myIDMap
.end() && id_it
->first
== idCmd
)
1194 tstring
tortoiseProcPath(CPathUtils::GetAppDirectory(g_hmodThisDll
) + L
"TortoiseGitProc.exe");
1195 tstring
tortoiseMergePath(CPathUtils::GetAppDirectory(g_hmodThisDll
) + L
"TortoiseGitMerge.exe");
1197 //TortoiseGitProc expects a command line of the form:
1198 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1200 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1202 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1203 //* pathfile is a path to a temporary file which contains a list of file paths
1204 std::wstring gitCmd
= L
" /command:";
1205 std::wstring tempfile
;
1206 switch (id_it
->second
)
1211 TCHAR syncSeq
[12] = { 0 };
1212 swprintf_s(syncSeq
, L
"%d", g_syncSeq
++);
1213 AddPathCommand(gitCmd
, L
"sync", false);
1214 gitCmd
+= L
" /seq:";
1218 case ShellMenuSubSync
:
1219 AddPathFileCommand(gitCmd
, L
"subsync");
1220 if (itemStatesFolder
& ITEMIS_SUBMODULECONTAINER
|| (itemStates
& ITEMIS_SUBMODULECONTAINER
&& itemStates
& ITEMIS_WCROOT
&& itemStates
& ITEMIS_ONLYONE
))
1222 gitCmd
+= L
" /bkpath:\"";
1227 case ShellMenuUpdateExt
:
1228 AddPathFileCommand(gitCmd
, L
"subupdate");
1229 if (itemStatesFolder
& ITEMIS_SUBMODULECONTAINER
|| (itemStates
& ITEMIS_SUBMODULECONTAINER
&& itemStates
& ITEMIS_WCROOT
&& itemStates
& ITEMIS_ONLYONE
))
1231 gitCmd
+= L
" /bkpath:\"";
1236 case ShellMenuCommit
:
1237 AddPathFileCommand(gitCmd
, L
"commit");
1240 AddPathFileCommand(gitCmd
, L
"add");
1242 case ShellMenuIgnore
:
1243 AddPathFileCommand(gitCmd
, L
"ignore");
1245 case ShellMenuIgnoreCaseSensitive
:
1246 AddPathFileCommand(gitCmd
, L
"ignore");
1247 gitCmd
+= L
" /onlymask";
1249 case ShellMenuDeleteIgnore
:
1250 AddPathFileCommand(gitCmd
, L
"ignore");
1251 gitCmd
+= L
" /delete";
1253 case ShellMenuDeleteIgnoreCaseSensitive
:
1254 AddPathFileCommand(gitCmd
, L
"ignore");
1255 gitCmd
+= L
" /delete /onlymask";
1257 case ShellMenuUnIgnore
:
1258 AddPathFileCommand(gitCmd
, L
"unignore");
1260 case ShellMenuUnIgnoreCaseSensitive
:
1261 AddPathFileCommand(gitCmd
, L
"unignore");
1262 gitCmd
+= L
" /onlymask";
1264 case ShellMenuMergeAbort
:
1265 AddPathCommand(gitCmd
, L
"merge", false);
1266 gitCmd
+= L
" /abort";
1268 case ShellMenuRevert
:
1269 AddPathFileCommand(gitCmd
, L
"revert");
1271 case ShellMenuCleanup
:
1272 AddPathFileCommand(gitCmd
, L
"cleanup");
1274 case ShellMenuSendMail
:
1275 AddPathFileCommand(gitCmd
, L
"sendmail");
1277 case ShellMenuResolve
:
1278 AddPathFileCommand(gitCmd
, L
"resolve");
1280 case ShellMenuSwitch
:
1281 AddPathCommand(gitCmd
, L
"switch", false);
1283 case ShellMenuExport
:
1284 AddPathCommand(gitCmd
, L
"export", false);
1286 case ShellMenuAbout
:
1289 case ShellMenuCreateRepos
:
1290 AddPathCommand(gitCmd
, L
"repocreate", false);
1292 case ShellMenuMerge
:
1293 AddPathCommand(gitCmd
, L
"merge", false);
1296 AddPathCommand(gitCmd
, L
"copy", true);
1298 case ShellMenuSettings
:
1299 AddPathCommand(gitCmd
, L
"settings", true);
1304 case ShellMenuRename
:
1305 AddPathCommand(gitCmd
, L
"rename", true);
1307 case ShellMenuRemove
:
1308 AddPathFileCommand(gitCmd
, L
"remove");
1309 if (itemStates
& ITEMIS_SUBMODULE
)
1310 gitCmd
+= L
" /submodule";
1312 case ShellMenuRemoveKeep
:
1313 AddPathFileCommand(gitCmd
, L
"remove");
1314 gitCmd
+= L
" /keep";
1317 gitCmd
+= L
"diff /path:\"";
1318 if (files_
.size() == 1)
1319 gitCmd
+= files_
.front();
1320 else if (files_
.size() == 2)
1322 auto I
= files_
.cbegin();
1325 gitCmd
+= L
"\" /path2:\"";
1331 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1332 gitCmd
+= L
" /alternative";
1334 case ShellMenuDiffLater
:
1335 if (lpcmi
->fMask
& CMIC_MASK_CONTROL_DOWN
)
1338 regDiffLater
.removeValue();
1340 else if (files_
.size() == 1)
1342 if (std::wstring(regDiffLater
).empty())
1345 regDiffLater
= files_
[0];
1349 AddPathCommand(gitCmd
, L
"diff", true);
1350 gitCmd
+= L
" /path2:\"";
1351 gitCmd
+= std::wstring(regDiffLater
);
1353 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1354 gitCmd
+= L
" /alternative";
1355 regDiffLater
.removeValue();
1361 case ShellMenuPrevDiff
:
1362 AddPathCommand(gitCmd
, L
"prevdiff", true);
1363 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1364 gitCmd
+= L
" /alternative";
1366 case ShellMenuDiffTwo
:
1367 AddPathCommand(gitCmd
, L
"diffcommits", true);
1369 case ShellMenuDropCopyAdd
:
1370 AddPathFileDropCommand(gitCmd
, L
"dropcopyadd");
1372 case ShellMenuDropCopy
:
1373 AddPathFileDropCommand(gitCmd
, L
"dropcopy");
1375 case ShellMenuDropCopyRename
:
1376 AddPathFileDropCommand(gitCmd
, L
"dropcopy");
1377 gitCmd
+= L
" /rename";
1379 case ShellMenuDropMove
:
1380 AddPathFileDropCommand(gitCmd
, L
"dropmove");
1382 case ShellMenuDropMoveRename
:
1383 AddPathFileDropCommand(gitCmd
, L
"dropmove");
1384 gitCmd
+= L
" /rename";
1386 case ShellMenuDropExport
:
1387 AddPathFileDropCommand(gitCmd
, L
"dropexport");
1389 case ShellMenuDropExportExtended
:
1390 AddPathFileDropCommand(gitCmd
, L
"dropexport");
1391 gitCmd
+= L
" /extended";
1394 case ShellMenuLogSubmoduleFolder
:
1395 AddPathCommand(gitCmd
, L
"log", true);
1396 if (id_it
->second
== ShellMenuLogSubmoduleFolder
)
1397 gitCmd
+= L
" /submodule";
1399 case ShellMenuDaemon
:
1400 AddPathCommand(gitCmd
, L
"daemon", true);
1402 case ShellMenuRevisionGraph
:
1403 AddPathCommand(gitCmd
, L
"revisiongraph", true);
1405 case ShellMenuConflictEditor
:
1406 AddPathCommand(gitCmd
, L
"conflicteditor", true);
1407 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1408 gitCmd
+= L
" /alternative";
1410 case ShellMenuGitSVNRebase
:
1411 AddPathCommand(gitCmd
, L
"svnrebase", false);
1413 case ShellMenuGitSVNDCommit
:
1414 AddPathCommand(gitCmd
, L
"svndcommit", true);
1416 case ShellMenuGitSVNDFetch
:
1417 AddPathCommand(gitCmd
, L
"svnfetch", false);
1419 case ShellMenuGitSVNIgnore
:
1420 AddPathCommand(gitCmd
, L
"svnignore", false);
1422 case ShellMenuRebase
:
1423 AddPathCommand(gitCmd
, L
"rebase", false);
1425 case ShellMenuShowChanged
:
1426 if (files_
.size() > 1)
1427 AddPathFileCommand(gitCmd
, L
"repostatus");
1429 AddPathCommand(gitCmd
, L
"repostatus", true);
1431 case ShellMenuRepoBrowse
:
1432 AddPathCommand(gitCmd
, L
"repobrowser", false);
1434 case ShellMenuRefBrowse
:
1435 AddPathCommand(gitCmd
, L
"refbrowse", false);
1437 case ShellMenuRefLog
:
1438 AddPathCommand(gitCmd
, L
"reflog", false);
1440 case ShellMenuStashSave
:
1441 AddPathCommand(gitCmd
, L
"stashsave", true);
1443 case ShellMenuStashApply
:
1444 AddPathCommand(gitCmd
, L
"stashapply", false);
1446 case ShellMenuStashPop
:
1447 AddPathCommand(gitCmd
, L
"stashpop", false);
1449 case ShellMenuStashList
:
1450 AddPathCommand(gitCmd
, L
"reflog", false);
1451 gitCmd
+= L
" /ref:refs/stash";
1453 case ShellMenuBisectStart
:
1454 AddPathCommand(gitCmd
, L
"bisect", false);
1455 gitCmd
+= L
" /start";
1457 case ShellMenuBisectGood
:
1458 AddPathCommand(gitCmd
, L
"bisect", false);
1459 gitCmd
+= L
" /good";
1461 case ShellMenuBisectBad
:
1462 AddPathCommand(gitCmd
, L
"bisect", false);
1465 case ShellMenuBisectSkip
:
1466 AddPathCommand(gitCmd
, L
"bisect", false);
1467 gitCmd
+= L
" /skip";
1469 case ShellMenuBisectReset
:
1470 AddPathCommand(gitCmd
, L
"bisect", false);
1471 gitCmd
+= L
" /reset";
1473 case ShellMenuSubAdd
:
1474 AddPathCommand(gitCmd
, L
"subadd", false);
1476 case ShellMenuBlame
:
1477 AddPathCommand(gitCmd
, L
"blame", true);
1479 case ShellMenuApplyPatch
:
1480 if ((itemStates
& ITEMIS_PATCHINCLIPBOARD
) && ((~itemStates
) & ITEMIS_PATCHFILE
))
1482 // if there's a patch file in the clipboard, we save it
1483 // to a temporary file and tell TortoiseGitMerge to use that one
1484 UINT cFormat
= RegisterClipboardFormat(L
"TGIT_UNIFIEDDIFF");
1485 if (cFormat
&& OpenClipboard(nullptr))
1487 HGLOBAL hglb
= GetClipboardData(cFormat
);
1488 LPCSTR lpstr
= (LPCSTR
)GlobalLock(hglb
);
1490 DWORD len
= GetTortoiseGitTempPath(0, nullptr);
1491 auto path
= std::make_unique
<TCHAR
[]>(len
+ 1);
1492 auto tempF
= std::make_unique
<TCHAR
[]>(len
+ 100);
1493 GetTortoiseGitTempPath(len
+ 1, path
.get());
1494 GetTempFileName(path
.get(), TEXT("git"), 0, tempF
.get());
1495 std::wstring sTempFile
= std::wstring(tempF
.get());
1498 size_t patchlen
= strlen(lpstr
);
1499 _wfopen_s(&outFile
, sTempFile
.c_str(), L
"wb");
1502 size_t size
= fwrite(lpstr
, sizeof(char), patchlen
, outFile
);
1503 if (size
== patchlen
)
1505 itemStates
|= ITEMIS_PATCHFILE
;
1507 files_
.push_back(sTempFile
);
1515 if (itemStates
& ITEMIS_PATCHFILE
)
1517 gitCmd
= L
" /diff:\"";
1518 if (!files_
.empty())
1520 gitCmd
+= files_
.front();
1521 if (itemStatesFolder
& ITEMIS_FOLDERINGIT
)
1523 gitCmd
+= L
"\" /patchpath:\"";
1529 if (itemStates
& ITEMIS_INVERSIONEDFOLDER
)
1530 gitCmd
+= L
"\" /wc";
1536 gitCmd
= L
" /patchpath:\"";
1537 if (!files_
.empty())
1538 gitCmd
+= files_
.front();
1544 myVerbsIDMap
.clear();
1546 RunCommand(tortoiseMergePath
, gitCmd
, L
"TortoiseGitMerge launch failed");
1549 case ShellMenuClipPaste
:
1550 if (WriteClipboardPathsToTempFile(tempfile
))
1553 UINT cPrefDropFormat
= RegisterClipboardFormat(L
"Preferred DropEffect");
1554 if (cPrefDropFormat
)
1556 if (OpenClipboard(lpcmi
->hwnd
))
1558 HGLOBAL hglb
= GetClipboardData(cPrefDropFormat
);
1561 DWORD
* effect
= (DWORD
*) GlobalLock(hglb
);
1562 if (*effect
== DROPEFFECT_MOVE
)
1571 gitCmd
+= L
"pastecopy /pathfile:\"";
1573 gitCmd
+= L
"pastemove /pathfile:\"";
1576 gitCmd
+= L
" /deletepathfile";
1577 gitCmd
+= L
" /droptarget:\"";
1583 case ShellMenuClone
:
1584 AddPathCommand(gitCmd
, L
"clone", false);
1587 AddPathCommand(gitCmd
, L
"pull", false);
1590 AddPathCommand(gitCmd
, L
"push", false);
1592 case ShellMenuBranch
:
1593 AddPathCommand(gitCmd
, L
"branch", false);
1596 AddPathCommand(gitCmd
, L
"tag", false);
1598 case ShellMenuFormatPatch
:
1599 AddPathCommand(gitCmd
, L
"formatpatch", false);
1601 case ShellMenuImportPatch
:
1602 AddPathFileCommand(gitCmd
, L
"importpatch");
1604 case ShellMenuImportPatchDrop
:
1605 AddPathFileDropCommand(gitCmd
, L
"importpatch");
1607 case ShellMenuFetch
:
1608 AddPathCommand(gitCmd
, L
"fetch", false);
1614 } // switch (id_it->second)
1615 if (!gitCmd
.empty())
1617 gitCmd
+= L
" /hwnd:";
1618 TCHAR buf
[30] = { 0 };
1619 swprintf_s(buf
, L
"%p", (void*)lpcmi
->hwnd
);
1622 myVerbsIDMap
.clear();
1624 RunCommand(tortoiseProcPath
, gitCmd
, L
"TortoiseProc launch failed");
1627 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1628 } // if (files_.empty() || folder_.empty())
1632 // This is for the status bar and things like that:
1633 STDMETHODIMP
CShellExt::GetCommandString(UINT_PTR idCmd
,
1635 UINT FAR
* /*reserved*/,
1639 PreserveChdir preserveChdir
;
1640 //do we know the id?
1641 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1642 if (id_it
== myIDMap
.end() || id_it
->first
!= idCmd
)
1644 return E_INVALIDARG
; //no, we don't
1648 HRESULT hr
= E_INVALIDARG
;
1650 MAKESTRING(IDS_MENUDESCDEFAULT
);
1652 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1654 if (menuInfo
[menuIndex
].command
== (GitCommands
)id_it
->second
)
1656 MAKESTRING(menuInfo
[menuIndex
].menuDescID
);
1662 const TCHAR
* desc
= stringtablebuffer
;
1667 std::string help
= WideToMultibyte(desc
);
1668 lstrcpynA(pszName
, help
.c_str(), cchMax
- 1);
1674 std::wstring help
= desc
;
1675 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
- 1);
1681 const auto verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
1682 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
1684 std::string help
= WideToMultibyte(verb_id_it
->second
);
1685 lstrcpynA(pszName
, help
.c_str(), cchMax
- 1);
1692 const auto verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
1693 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
1695 std::wstring help
= verb_id_it
->second
;
1696 CTraceToOutputDebugString::Instance()(__FUNCTION__
": verb : %ws\n", help
.c_str());
1697 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
- 1);
1706 STDMETHODIMP
CShellExt::HandleMenuMsg(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
1709 return HandleMenuMsg2(uMsg
, wParam
, lParam
, &res
);
1712 STDMETHODIMP
CShellExt::HandleMenuMsg2(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*pResult
)
1714 PreserveChdir preserveChdir
;
1724 case WM_MEASUREITEM
:
1726 MEASUREITEMSTRUCT
* lpmis
= (MEASUREITEMSTRUCT
*)lParam
;
1729 lpmis
->itemWidth
= 16;
1730 lpmis
->itemHeight
= 16;
1737 DRAWITEMSTRUCT
* lpdis
= (DRAWITEMSTRUCT
*)lParam
;
1738 if (!lpdis
|| lpdis
->CtlType
!= ODT_MENU
)
1739 return S_OK
; //not for a menu
1740 resource
= GetMenuTextFromResource((int)myIDMap
[lpdis
->itemID
]);
1743 int iconWidth
= GetSystemMetrics(SM_CXSMICON
);
1744 int iconHeight
= GetSystemMetrics(SM_CYSMICON
);
1745 auto hIcon
= LoadIconEx(g_hResInst
, resource
, iconWidth
, iconHeight
);
1748 DrawIconEx(lpdis
->hDC
,
1750 lpdis
->rcItem
.top
+ (lpdis
->rcItem
.bottom
- lpdis
->rcItem
.top
- iconHeight
) / 2,
1751 hIcon
, iconWidth
, iconHeight
,
1752 0, nullptr, DI_NORMAL
);
1760 if (HIWORD(wParam
) != MF_POPUP
)
1762 int nChar
= LOWORD(wParam
);
1763 if (_istascii((wint_t)nChar
) && _istupper((wint_t)nChar
))
1764 nChar
= tolower(nChar
);
1765 // we have the char the user pressed, now search that char in all our
1767 std::vector
<UINT_PTR
> accmenus
;
1768 for (auto It
= mySubMenuMap
.cbegin(); It
!= mySubMenuMap
.cend(); ++It
)
1770 LPCTSTR resource
= GetMenuTextFromResource((int)mySubMenuMap
[It
->first
]);
1773 szItem
= stringtablebuffer
;
1774 TCHAR
* amp
= wcschr(szItem
, L
'&');
1778 int ampChar
= LOWORD(*amp
);
1779 if (_istascii((wint_t)ampChar
) && _istupper((wint_t)ampChar
))
1780 ampChar
= tolower(ampChar
);
1781 if (ampChar
== nChar
)
1783 // yep, we found a menu which has the pressed key
1784 // as an accelerator. Add that menu to the list to
1786 accmenus
.push_back(It
->first
);
1789 if (accmenus
.empty())
1791 // no menu with that accelerator key.
1792 *pResult
= MAKELONG(0, MNC_IGNORE
);
1795 if (accmenus
.size() == 1)
1797 // Only one menu with that accelerator key. We're lucky!
1798 // So just execute that menu entry.
1799 *pResult
= MAKELONG(accmenus
[0], MNC_EXECUTE
);
1802 if (accmenus
.size() > 1)
1804 // we have more than one menu item with this accelerator key!
1806 mif
.cbSize
= sizeof(MENUITEMINFO
);
1807 mif
.fMask
= MIIM_STATE
;
1808 for (auto it
= accmenus
.cbegin(); it
!= accmenus
.cend(); ++it
)
1810 GetMenuItemInfo((HMENU
)lParam
, (UINT
)*it
, TRUE
, &mif
);
1811 if (mif
.fState
== MFS_HILITE
)
1813 // this is the selected item, so select the next one
1815 if (it
== accmenus
.end())
1816 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
1818 *pResult
= MAKELONG(*it
, MNC_SELECT
);
1822 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
1833 LPCTSTR
CShellExt::GetMenuTextFromResource(int id
)
1835 TCHAR textbuf
[255] = { 0 };
1836 LPCTSTR resource
= nullptr;
1837 unsigned __int64 layout
= g_ShellCache
.GetMenuLayout();
1841 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1843 if (menuInfo
[menuIndex
].command
== id
)
1845 MAKESTRING(menuInfo
[menuIndex
].menuTextID
);
1846 resource
= MAKEINTRESOURCE(menuInfo
[menuIndex
].iconID
);
1849 case ShellSubMenuMultiple
:
1850 case ShellSubMenuLink
:
1851 case ShellSubMenuFolder
:
1852 case ShellSubMenuFile
:
1857 space
= (layout
& menuInfo
[menuIndex
].menuID
) ? 0 : 6;
1858 if (layout
& menuInfo
[menuIndex
].menuID
)
1860 wcscpy_s(textbuf
, 255, L
"Git ");
1861 wcscat_s(textbuf
, 255, stringtablebuffer
);
1862 wcscpy_s(stringtablebuffer
, 255, textbuf
);
1873 bool CShellExt::IsIllegalFolder(const std::wstring
& folder
, int* cslidarray
)
1876 TCHAR buf
[MAX_PATH
] = {0}; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
1877 LPITEMIDLIST pidl
= nullptr;
1878 while (cslidarray
[i
])
1882 if (SHGetFolderLocation(nullptr, cslidarray
[i
- 1], nullptr, 0, &pidl
) != S_OK
)
1884 if (!SHGetPathFromIDList(pidl
, buf
))
1886 // not a file system path, definitely illegal for our use
1887 CoTaskMemFree(pidl
);
1890 CoTaskMemFree(pidl
);
1893 if (wcscmp(buf
, folder
.c_str()) == 0)
1899 bool CShellExt::InsertIgnoreSubmenus(UINT
&idCmd
, UINT idCmdFirst
, HMENU hMenu
, HMENU subMenu
, UINT
&indexMenu
, int &indexSubMenu
, unsigned __int64 topmenu
, bool bShowIcons
, UINT
/*uFlags*/)
1901 HMENU ignoresubmenu
= nullptr;
1902 int indexignoresub
= 0;
1903 bool bShowIgnoreMenu
= false;
1904 TCHAR maskbuf
[MAX_PATH
] = {0}; // MAX_PATH is ok, since this only holds a filename
1905 TCHAR ignorepath
[MAX_PATH
] = {0}; // MAX_PATH is ok, since this only holds a filename
1908 UINT icon
= bShowIcons
? IDI_IGNORE
: 0;
1910 auto I
= files_
.cbegin();
1911 if (wcsrchr(I
->c_str(), L
'\\'))
1912 wcscpy_s(ignorepath
, wcsrchr(I
->c_str(), L
'\\') + 1);
1914 wcscpy_s(ignorepath
, I
->c_str());
1915 if ((itemStates
& ITEMIS_IGNORED
) && (!ignoredprops
.empty()))
1917 // check if the item name is ignored or the mask
1919 const size_t pathLength
= wcslen(ignorepath
);
1920 while ( (p
=ignoredprops
.find( ignorepath
,p
)) != -1 )
1922 if ( (p
==0 || ignoredprops
[p
-1]==TCHAR('\n'))
1923 && (p
+ pathLength
== ignoredprops
.length() || ignoredprops
[p
+ pathLength
+ 1] == TCHAR('\n')))
1931 ignoresubmenu
= CreateMenu();
1932 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
1933 auto verb
= std::wstring(ignorepath
);
1934 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
1935 myVerbsMap
[verb
] = idCmd
;
1936 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
1937 myVerbsIDMap
[idCmd
] = verb
;
1938 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnore
;
1939 myIDMap
[idCmd
++] = ShellMenuUnIgnore
;
1940 bShowIgnoreMenu
= true;
1942 wcscpy_s(maskbuf
, L
"*");
1943 if (wcsrchr(ignorepath
, L
'.'))
1945 wcscat_s(maskbuf
, wcsrchr(ignorepath
, L
'.'));
1946 p
= ignoredprops
.find(maskbuf
);
1948 ((ignoredprops
.compare(maskbuf
) == 0) || (ignoredprops
.find(L
'\n', p
) == p
+ wcslen(maskbuf
) + 1) || (ignoredprops
.rfind(L
'\n', p
) == p
- 1)))
1951 ignoresubmenu
= CreateMenu();
1953 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
1954 auto verb
= std::wstring(maskbuf
);
1955 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
1956 myVerbsMap
[verb
] = idCmd
;
1957 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
1958 myVerbsIDMap
[idCmd
] = verb
;
1959 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreCaseSensitive
;
1960 myIDMap
[idCmd
++] = ShellMenuUnIgnoreCaseSensitive
;
1961 bShowIgnoreMenu
= true;
1965 else if ((itemStates
& ITEMIS_IGNORED
) == 0)
1967 bShowIgnoreMenu
= true;
1968 ignoresubmenu
= CreateMenu();
1969 if (itemStates
& ITEMIS_ONLYONE
)
1971 if (itemStates
& ITEMIS_INGIT
)
1973 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
1974 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnore
;
1975 myIDMap
[idCmd
++] = ShellMenuDeleteIgnore
;
1977 wcscpy_s(maskbuf
, L
"*");
1978 if (!(itemStates
& ITEMIS_FOLDER
) && wcsrchr(ignorepath
, L
'.'))
1980 wcscat_s(maskbuf
, wcsrchr(ignorepath
, L
'.'));
1981 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
1982 auto verb
= std::wstring(maskbuf
);
1983 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
1984 myVerbsMap
[verb
] = idCmd
;
1985 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
1986 myVerbsIDMap
[idCmd
] = verb
;
1987 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnoreCaseSensitive
;
1988 myIDMap
[idCmd
++] = ShellMenuDeleteIgnoreCaseSensitive
;
1993 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
1994 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
1995 myIDMap
[idCmd
++] = ShellMenuIgnore
;
1997 wcscpy_s(maskbuf
, L
"*");
1998 if (!(itemStates
& ITEMIS_FOLDER
) && wcsrchr(ignorepath
, L
'.'))
2000 wcscat_s(maskbuf
, wcsrchr(ignorepath
, L
'.'));
2001 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2002 auto verb
= std::wstring(maskbuf
);
2003 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2004 myVerbsMap
[verb
] = idCmd
;
2005 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2006 myVerbsIDMap
[idCmd
] = verb
;
2007 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2008 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2014 // note: as of Windows 7, the shell does not pass more than 16 items from a multiselection
2015 // in the Initialize() call before the QueryContextMenu() call. Which means even if the user
2016 // has selected more than 16 files, we won't know about that here.
2017 // Note: after QueryContextMenu() exits, Initialize() is called again with all selected files.
2018 if (itemStates
& ITEMIS_INGIT
)
2020 if (files_
.size() >= 16)
2022 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE2
);
2023 wcscpy_s(ignorepath
, stringtablebuffer
);
2027 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE
);
2028 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2030 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2031 auto verb
= std::wstring(ignorepath
);
2032 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2033 myVerbsMap
[verb
] = idCmd
;
2034 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2035 myVerbsIDMap
[idCmd
] = verb
;
2036 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnore
;
2037 myIDMap
[idCmd
++] = ShellMenuDeleteIgnore
;
2039 if (files_
.size() >= 16)
2041 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK2
);
2042 wcscpy_s(ignorepath
, stringtablebuffer
);
2046 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK
);
2047 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2049 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2050 verb
= std::wstring(ignorepath
);
2051 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2052 myVerbsMap
[verb
] = idCmd
;
2053 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2054 myVerbsIDMap
[idCmd
] = verb
;
2055 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnoreCaseSensitive
;
2056 myIDMap
[idCmd
++] = ShellMenuDeleteIgnoreCaseSensitive
;
2060 if (files_
.size() >= 16)
2062 MAKESTRING(IDS_MENUIGNOREMULTIPLE2
);
2063 wcscpy_s(ignorepath
, stringtablebuffer
);
2067 MAKESTRING(IDS_MENUIGNOREMULTIPLE
);
2068 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2070 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2071 auto verb
= std::wstring(ignorepath
);
2072 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2073 myVerbsMap
[verb
] = idCmd
;
2074 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2075 myVerbsIDMap
[idCmd
] = verb
;
2076 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
2077 myIDMap
[idCmd
++] = ShellMenuIgnore
;
2079 if (files_
.size() >= 16)
2081 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK2
);
2082 wcscpy_s(ignorepath
, stringtablebuffer
);
2086 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK
);
2087 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2089 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2090 verb
= std::wstring(ignorepath
);
2091 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2092 myVerbsMap
[verb
] = idCmd
;
2093 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2094 myVerbsIDMap
[idCmd
] = verb
;
2095 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2096 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2101 if (bShowIgnoreMenu
)
2103 MENUITEMINFO menuiteminfo
= { 0 };
2104 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
2105 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
2108 menuiteminfo
.fMask
|= MIIM_BITMAP
;
2109 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, icon
);
2111 menuiteminfo
.fType
= MFT_STRING
;
2112 menuiteminfo
.hSubMenu
= ignoresubmenu
;
2113 menuiteminfo
.wID
= idCmd
;
2114 SecureZeroMemory(stringtablebuffer
, sizeof(stringtablebuffer
));
2115 if (itemStates
& ITEMIS_IGNORED
)
2116 GetMenuTextFromResource(ShellMenuUnIgnoreSub
);
2117 else if (itemStates
& ITEMIS_INGIT
)
2118 GetMenuTextFromResource(ShellMenuDeleteIgnoreSub
);
2120 GetMenuTextFromResource(ShellMenuIgnoreSub
);
2121 menuiteminfo
.dwTypeData
= stringtablebuffer
;
2122 menuiteminfo
.cch
= (UINT
)min(wcslen(menuiteminfo
.dwTypeData
), UINT_MAX
);
2124 InsertMenuItem((topmenu
& MENUIGNORE
) ? hMenu
: subMenu
, (topmenu
& MENUIGNORE
) ? indexMenu
++ : indexSubMenu
++, TRUE
, &menuiteminfo
);
2125 if (itemStates
& ITEMIS_IGNORED
)
2127 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreSub
;
2128 myIDMap
[idCmd
++] = ShellMenuUnIgnoreSub
;
2132 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreSub
;
2133 myIDMap
[idCmd
++] = ShellMenuIgnoreSub
;
2136 return bShowIgnoreMenu
;
2139 void CShellExt::RunCommand(const tstring
& path
, const tstring
& command
, LPCTSTR errorMessage
)
2141 if (CCreateProcessHelper::CreateProcessDetached(path
.c_str(), command
.c_str()))
2143 // process started - exit
2147 MessageBox(nullptr, CFormatMessageWrapper(), errorMessage
, MB_OK
| MB_ICONERROR
);
2150 bool CShellExt::ShouldInsertItem(const MenuInfo
& item
) const
2152 return ShouldEnableMenu(item
.first
) || ShouldEnableMenu(item
.second
) ||
2153 ShouldEnableMenu(item
.third
) || ShouldEnableMenu(item
.fourth
);
2156 bool CShellExt::ShouldEnableMenu(const YesNoPair
& pair
) const
2158 if (pair
.yes
&& pair
.no
)
2160 if (((pair
.yes
& itemStates
) == pair
.yes
) && ((pair
.no
& (~itemStates
)) == pair
.no
))
2163 else if ((pair
.yes
) && ((pair
.yes
& itemStates
) == pair
.yes
))
2165 else if ((pair
.no
) && ((pair
.no
& (~itemStates
)) == pair
.no
))