1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012, 2014-2016 - TortoiseSVN
4 // Copyright (C) 2008-2017 - TortoiseGit
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "ItemIDList.h"
23 #include "PreserveChdir.h"
24 #include "UnicodeUtils.h"
26 #include "GitStatus.h"
28 #include "PathUtils.h"
29 #include "CreateProcessHelper.h"
30 #include "FormatMessageWrapper.h"
31 #include "../TGitCache/CacheInterface.h"
34 #define GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
35 #define GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
37 int g_shellidlist
=RegisterClipboardFormat(CFSTR_SHELLIDLIST
);
39 extern MenuInfo menuInfo
[];
40 static int g_syncSeq
= 0;
42 STDMETHODIMP
CShellExt::Initialize(LPCITEMIDLIST pIDFolder
,
43 LPDATAOBJECT pDataObj
,
46 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: Initialize\n");
47 PreserveChdir preserveChdir
;
54 std::wstring statuspath
;
55 git_wc_status_kind fetchedstatus
= git_wc_status_none
;
56 // get selected files/folders
60 FORMATETC fmte
= {(CLIPFORMAT
)g_shellidlist
,
65 HRESULT hres
= pDataObj
->GetData(&fmte
, &medium
);
67 if (SUCCEEDED(hres
) && medium
.hGlobal
)
69 if (m_State
== FileStateDropHandler
)
71 if (!CRegStdDWORD(L
"Software\\TortoiseGit\\EnableDragContextMenu", TRUE
, false, HKEY_CURRENT_USER
, KEY_WOW64_64KEY
))
73 ReleaseStgMedium(&medium
);
77 FORMATETC etc
= { CF_HDROP
, nullptr, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
};
78 STGMEDIUM stg
= { TYMED_HGLOBAL
};
79 if ( FAILED( pDataObj
->GetData ( &etc
, &stg
)))
81 ReleaseStgMedium ( &medium
);
86 HDROP drop
= (HDROP
)GlobalLock(stg
.hGlobal
);
89 ReleaseStgMedium ( &stg
);
90 ReleaseStgMedium ( &medium
);
94 int count
= DragQueryFile(drop
, (UINT
)-1, nullptr, 0);
96 itemStates
|= ITEMIS_ONLYONE
;
97 for (int i
= 0; i
< count
; i
++)
99 // find the path length in chars
100 UINT len
= DragQueryFile(drop
, i
, nullptr, 0);
103 auto szFileName
= std::make_unique
<TCHAR
[]>(len
+ 1);
104 if (0 == DragQueryFile(drop
, i
, szFileName
.get(), len
+ 1))
106 auto str
= std::wstring(szFileName
.get());
107 if ((!str
.empty()) && (g_ShellCache
.IsContextPathAllowed(szFileName
.get())))
111 strpath
.SetFromWin(str
.c_str());
112 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".diff") == 0) ? ITEMIS_PATCHFILE
: 0;
113 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".patch") == 0) ? ITEMIS_PATCHFILE
: 0;
115 files_
.push_back(str
);
118 //get the git status of the item
119 git_wc_status_kind status
= git_wc_status_none
;
121 askedpath
.SetFromWin(str
.c_str());
122 CString workTreePath
;
123 askedpath
.HasAdminDir(&workTreePath
);
124 uuidSource
= workTreePath
;
127 if (g_ShellCache
.GetCacheType() == ShellCache::exe
&& g_ShellCache
.IsPathAllowed(str
.c_str()))
129 CTGitPath
tpath(str
.c_str());
130 if (!tpath
.HasAdminDir())
132 status
= git_wc_status_none
;
135 if (tpath
.IsAdminDir())
137 status
= git_wc_status_none
;
140 TGITCacheResponse itemStatus
;
141 SecureZeroMemory(&itemStatus
, sizeof(itemStatus
));
142 if (m_remoteCacheLink
.GetStatusFromRemoteCache(tpath
, &itemStatus
, true))
144 fetchedstatus
= status
= (git_wc_status_kind
)itemStatus
.m_status
;
145 if (askedpath
.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
147 itemStates
|= ITEMIS_FOLDER
;
148 if ((status
!= git_wc_status_unversioned
) && (status
!= git_wc_status_ignored
) && (status
!= git_wc_status_none
))
149 itemStates
|= ITEMIS_FOLDERINGIT
;
156 stat
.GetStatus(CTGitPath(str
.c_str()), false, false, true);
160 status
= stat
.status
->status
;
161 fetchedstatus
= status
;
162 if ( askedpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
164 itemStates
|= ITEMIS_FOLDER
;
165 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
166 itemStates
|= ITEMIS_FOLDERINGIT
;
168 //if ((stat.status->entry)&&(stat.status->entry->uuid))
169 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
173 // sometimes, git_client_status() returns with an error.
174 // in that case, we have to check if the working copy is versioned
175 // anyway to show the 'correct' context menu
176 if (askedpath
.HasAdminDir())
177 status
= git_wc_status_normal
;
183 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
186 // TODO: should we really assume any sub-directory to be versioned
187 // or only if it contains versioned files
188 itemStates
|= askedpath
.GetAdminDirMask();
190 if ((status
== git_wc_status_unversioned
) || (status
== git_wc_status_ignored
) || (status
== git_wc_status_none
))
191 itemStates
&= ~ITEMIS_INGIT
;
193 if (status
== git_wc_status_ignored
)
194 itemStates
|= ITEMIS_IGNORED
;
195 if (status
== git_wc_status_normal
)
196 itemStates
|= ITEMIS_NORMAL
;
197 if (status
== git_wc_status_conflicted
)
198 itemStates
|= ITEMIS_CONFLICTED
;
199 if (status
== git_wc_status_added
)
200 itemStates
|= ITEMIS_ADDED
;
201 if (status
== git_wc_status_deleted
)
202 itemStates
|= ITEMIS_DELETED
;
205 } // for (int i = 0; i < count; i++)
206 GlobalUnlock ( drop
);
207 ReleaseStgMedium ( &stg
);
209 } // if (m_State == FileStateDropHandler)
212 //Enumerate PIDLs which the user has selected
213 CIDA
* cida
= (CIDA
*)GlobalLock(medium
.hGlobal
);
214 ItemIDList
parent( GetPIDLFolder (cida
));
216 int count
= cida
->cidl
;
217 BOOL statfetched
= FALSE
;
218 for (int i
= 0; i
< count
; ++i
)
220 ItemIDList
child (GetPIDLItem (cida
, i
), &parent
);
221 std::wstring str
= child
.toString();
222 if ((str
.empty() == false)&&(g_ShellCache
.IsContextPathAllowed(str
.c_str())))
224 //check if our menu is requested for a git admin directory
225 if (GitAdminDir::IsAdminDirPath(str
.c_str()))
228 files_
.push_back(str
);
230 strpath
.SetFromWin(str
.c_str());
231 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".diff") == 0) ? ITEMIS_PATCHFILE
: 0;
232 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".patch") == 0) ? ITEMIS_PATCHFILE
: 0;
235 //get the git status of the item
236 git_wc_status_kind status
= git_wc_status_none
;
237 if ((g_ShellCache
.IsSimpleContext())&&(strpath
.IsDirectory()))
239 if (strpath
.HasAdminDir())
240 status
= git_wc_status_normal
;
246 if (g_ShellCache
.GetCacheType() == ShellCache::exe
&& g_ShellCache
.IsPathAllowed(str
.c_str()))
248 CTGitPath
tpath(str
.c_str());
249 if(!tpath
.HasAdminDir())
251 status
= git_wc_status_none
;
254 if(tpath
.IsAdminDir())
256 status
= git_wc_status_none
;
259 TGITCacheResponse itemStatus
;
260 SecureZeroMemory(&itemStatus
, sizeof(itemStatus
));
261 if (m_remoteCacheLink
.GetStatusFromRemoteCache(tpath
, &itemStatus
, true))
263 fetchedstatus
= status
= (git_wc_status_kind
)itemStatus
.m_status
;
264 if (strpath
.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
266 itemStates
|= ITEMIS_FOLDER
;
267 if ((status
!= git_wc_status_unversioned
) && (status
!= git_wc_status_ignored
) && (status
!= git_wc_status_none
))
268 itemStates
|= ITEMIS_FOLDERINGIT
;
270 if (status
== git_wc_status_conflicted
)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
271 itemStates
|= ITEMIS_CONFLICTED
;
277 if (strpath
.HasAdminDir())
278 stat
.GetStatus(strpath
, false, false, true);
282 status
= stat
.status
->status
;
283 fetchedstatus
= status
;
284 if ( strpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
286 itemStates
|= ITEMIS_FOLDER
;
287 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
288 itemStates
|= ITEMIS_FOLDERINGIT
;
290 // TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
291 if (status
== git_wc_status_conflicted
)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
292 itemStates
|= ITEMIS_CONFLICTED
;
293 //if ((stat.status->entry)&&(stat.status->entry->uuid))
294 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
298 // sometimes, git_client_status() returns with an error.
299 // in that case, we have to check if the working copy is versioned
300 // anyway to show the 'correct' context menu
301 if (strpath
.HasAdminDir())
303 status
= git_wc_status_normal
;
304 fetchedstatus
= status
;
312 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
316 itemStates
|= strpath
.GetAdminDirMask();
318 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
319 itemStates
&= ~ITEMIS_INGIT
;
320 if (status
== git_wc_status_ignored
)
322 itemStates
|= ITEMIS_IGNORED
;
325 if (status
== git_wc_status_normal
)
326 itemStates
|= ITEMIS_NORMAL
;
327 if (status
== git_wc_status_conflicted
)
328 itemStates
|= ITEMIS_CONFLICTED
;
329 if (status
== git_wc_status_added
)
330 itemStates
|= ITEMIS_ADDED
;
331 if (status
== git_wc_status_deleted
)
332 itemStates
|= ITEMIS_DELETED
;
335 } // for (int i = 0; i < count; ++i)
336 ItemIDList
child (GetPIDLItem (cida
, 0), &parent
);
337 if (g_ShellCache
.HasGITAdminDir(child
.toString().c_str(), FALSE
))
338 itemStates
|= ITEMIS_INVERSIONEDFOLDER
;
340 if (GitAdminDir::IsBareRepo(child
.toString().c_str()))
341 itemStates
= ITEMIS_BAREREPO
;
343 GlobalUnlock(medium
.hGlobal
);
345 // if the item is a versioned folder, check if there's a patch file
346 // in the clipboard to be used in "Apply Patch"
347 UINT cFormatDiff
= RegisterClipboardFormat(L
"TGIT_UNIFIEDDIFF");
350 if (IsClipboardFormatAvailable(cFormatDiff
))
351 itemStates
|= ITEMIS_PATCHINCLIPBOARD
;
353 if (IsClipboardFormatAvailable(CF_HDROP
))
354 itemStates
|= ITEMIS_PATHINCLIPBOARD
;
358 ReleaseStgMedium ( &medium
);
359 if (medium
.pUnkForRelease
)
361 IUnknown
* relInterface
= (IUnknown
*)medium
.pUnkForRelease
;
362 relInterface
->Release();
367 // get folder background
370 ItemIDList
list(pIDFolder
);
371 folder_
= list
.toString();
372 git_wc_status_kind status
= git_wc_status_none
;
373 if (IsClipboardFormatAvailable(CF_HDROP
))
374 itemStatesFolder
|= ITEMIS_PATHINCLIPBOARD
;
377 askedpath
.SetFromWin(folder_
.c_str());
379 if (g_ShellCache
.IsContextPathAllowed(folder_
.c_str()))
381 if (folder_
.compare(statuspath
)!=0)
383 CString worktreePath
;
384 askedpath
.HasAdminDir(&worktreePath
);
385 uuidTarget
= worktreePath
;
388 if (g_ShellCache
.GetCacheType() == ShellCache::exe
&& g_ShellCache
.IsPathAllowed(folder_
.c_str()))
390 CTGitPath
tpath(folder_
.c_str());
391 if(!tpath
.HasAdminDir())
392 status
= git_wc_status_none
;
393 else if(tpath
.IsAdminDir())
394 status
= git_wc_status_none
;
397 TGITCacheResponse itemStatus
;
398 SecureZeroMemory(&itemStatus
, sizeof(itemStatus
));
399 if (m_remoteCacheLink
.GetStatusFromRemoteCache(tpath
, &itemStatus
, true))
400 status
= (git_wc_status_kind
)itemStatus
.m_status
;
406 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
408 status
= stat
.status
->status
;
411 // sometimes, git_client_status() returns with an error.
412 // in that case, we have to check if the working copy is versioned
413 // anyway to show the 'correct' context menu
414 if (askedpath
.HasAdminDir())
415 status
= git_wc_status_normal
;
419 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
420 itemStatesFolder
|= askedpath
.GetAdminDirMask();
422 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
423 itemStates
&= ~ITEMIS_INGIT
;
425 if (status
== git_wc_status_normal
)
426 itemStatesFolder
|= ITEMIS_NORMAL
;
427 if (status
== git_wc_status_conflicted
)
428 itemStatesFolder
|= ITEMIS_CONFLICTED
;
429 if (status
== git_wc_status_added
)
430 itemStatesFolder
|= ITEMIS_ADDED
;
431 if (status
== git_wc_status_deleted
)
432 itemStatesFolder
|= ITEMIS_DELETED
;
437 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
442 status
= fetchedstatus
;
444 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
445 itemStatesFolder
|= askedpath
.GetAdminDirMask();
447 if (status
== git_wc_status_ignored
)
448 itemStatesFolder
|= ITEMIS_IGNORED
;
449 itemStatesFolder
|= ITEMIS_FOLDER
;
451 itemStates
|= ITEMIS_ONLYONE
;
452 if (m_State
!= FileStateDropHandler
)
453 itemStates
|= itemStatesFolder
;
458 status
= fetchedstatus
;
461 if (files_
.size() == 2)
462 itemStates
|= ITEMIS_TWO
;
463 if ((files_
.size() == 1)&&(g_ShellCache
.IsContextPathAllowed(files_
.front().c_str())))
465 itemStates
|= ITEMIS_ONLYONE
;
466 if (m_State
!= FileStateDropHandler
)
468 if (PathIsDirectory(files_
.front().c_str()))
470 folder_
= files_
.front();
471 git_wc_status_kind status
= git_wc_status_none
;
473 askedpath
.SetFromWin(folder_
.c_str());
475 if (folder_
.compare(statuspath
)!=0)
480 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
482 status
= stat
.status
->status
;
486 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
491 status
= fetchedstatus
;
493 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
494 itemStates
|= askedpath
.GetAdminDirMask();
496 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
497 itemStates
&= ~ITEMIS_INGIT
;
499 if (status
== git_wc_status_ignored
)
500 itemStates
|= ITEMIS_IGNORED
;
501 itemStates
|= ITEMIS_FOLDER
;
502 if (status
== git_wc_status_added
)
503 itemStates
|= ITEMIS_ADDED
;
504 if (status
== git_wc_status_deleted
)
505 itemStates
|= ITEMIS_DELETED
;
515 void CShellExt::InsertGitMenu(BOOL istop
, HMENU menu
, UINT pos
, UINT_PTR id
, UINT stringid
, UINT icon
, UINT idCmdFirst
, GitCommands com
, UINT
/*uFlags*/)
517 TCHAR menutextbuffer
[512] = {0};
518 TCHAR verbsbuffer
[255] = {0};
519 MAKESTRING(stringid
);
523 //menu entry for the top context menu, so append an "Git " before
524 //the menu text to indicate where the entry comes from
525 wcscpy_s(menutextbuffer
, 255, L
"Git ");
526 if (!g_ShellCache
.HasShellMenuAccelerators())
528 // remove the accelerators
529 tstring temp
= stringtablebuffer
;
530 temp
.erase(std::remove(temp
.begin(), temp
.end(), '&'), temp
.end());
531 wcscpy_s(stringtablebuffer
, 255, temp
.c_str());
534 wcscat_s(menutextbuffer
, 255, stringtablebuffer
);
536 // insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
537 // so we have an easy and fast way to check the current branch
538 // (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
539 if (com
== ShellMenuCommit
)
542 CTGitPath
path(folder_
.empty() ? files_
.front().c_str() : folder_
.c_str());
543 CString sProjectRoot
;
546 if (path
.GetAdminDirMask() & ITEMIS_SUBMODULE
)
549 wcscpy_s(menutextbuffer
, 255, L
"Git ");
551 menutextbuffer
[0] = L
'\0';
552 MAKESTRING(IDS_MENUCOMMITSUBMODULE
);
553 wcscat_s(menutextbuffer
, 255, stringtablebuffer
);
556 if (path
.HasAdminDir(&sProjectRoot
) && !CGit::GetCurrentBranchFromFile(sProjectRoot
, sBranchName
))
558 if (sBranchName
.GetLength() == 2 * GIT_HASH_SIZE
)
560 // if SHA1 only show 4 first bytes
562 for (int i
= 0; i
< 2 * GIT_HASH_SIZE
; ++i
)
563 if ( !iswxdigit(sBranchName
[i
]) )
569 sBranchName
= sBranchName
.Left(8) + L
"...";
573 if (sBranchName
.GetLength() > 64)
574 sBranchName
= sBranchName
.Left(64) + L
"...";
576 // scan to before "..."
577 LPTSTR s
= menutextbuffer
+ wcslen(menutextbuffer
)-1;
578 if (s
> menutextbuffer
)
580 while (s
> menutextbuffer
)
595 // append branch name and end with ...
596 wcscpy_s(s
, 255 - wcslen(menutextbuffer
) - 1, L
" -> \"" + sBranchName
+ L
"\"...");
600 if (com
== ShellMenuDiffLater
)
602 std::wstring sPath
= regDiffLater
;
605 // add the path of the saved file
606 wchar_t compact
[2 * GIT_HASH_SIZE
] = { 0 };
607 PathCompactPathEx(compact
, sPath
.c_str(), _countof(compact
) - 1, 0);
608 MAKESTRING(IDS_MENUDIFFNOW
);
610 sMenu
.Format(CString(stringtablebuffer
), compact
);
611 wcscpy_s(menutextbuffer
, sMenu
);
615 MENUITEMINFO menuiteminfo
= { 0 };
616 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
617 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_STRING
;
618 menuiteminfo
.fType
= MFT_STRING
;
619 menuiteminfo
.dwTypeData
= menutextbuffer
;
622 menuiteminfo
.fMask
|= MIIM_BITMAP
;
623 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, icon
);
625 menuiteminfo
.wID
= (UINT
)id
;
626 InsertMenuItem(menu
, pos
, TRUE
, &menuiteminfo
);
630 //menu entry for the top context menu, so append an "Git " before
631 //the menu text to indicate where the entry comes from
632 wcscpy_s(menutextbuffer
, 255, L
"Git ");
634 LoadString(g_hResInst
, stringid
, verbsbuffer
, _countof(verbsbuffer
));
635 wcscat_s(menutextbuffer
, 255, verbsbuffer
);
636 auto verb
= std::wstring(menutextbuffer
);
637 if (verb
.find('&') != -1)
639 verb
.erase(verb
.find('&'),1);
641 myVerbsMap
[verb
] = id
- idCmdFirst
;
642 myVerbsMap
[verb
] = id
;
643 myVerbsIDMap
[id
- idCmdFirst
] = verb
;
644 myVerbsIDMap
[id
] = verb
;
645 // We store the relative and absolute diameter
646 // (drawitem callback uses absolute, others relative)
647 myIDMap
[id
- idCmdFirst
] = com
;
650 mySubMenuMap
[pos
] = com
;
653 bool CShellExt::WriteClipboardPathsToTempFile(std::wstring
& tempfile
)
655 tempfile
= std::wstring();
656 //write all selected files and paths to a temporary file
657 //for TortoiseGitProc.exe to read out again.
659 DWORD pathlength
= GetTortoiseGitTempPath(0, nullptr);
660 auto path
= std::make_unique
<TCHAR
[]>(pathlength
+ 1);
661 auto tempFile
= std::make_unique
<TCHAR
[]>(pathlength
+ 100);
662 GetTortoiseGitTempPath(pathlength
+1, path
.get());
663 GetTempFileName(path
.get(), L
"git", 0, tempFile
.get());
664 tempfile
= std::wstring(tempFile
.get());
666 CAutoFile file
= ::CreateFile(tempFile
.get(),
671 FILE_ATTRIBUTE_TEMPORARY
,
677 if (!IsClipboardFormatAvailable(CF_HDROP
))
679 if (!OpenClipboard(nullptr))
682 HGLOBAL hglb
= GetClipboardData(CF_HDROP
);
688 HDROP hDrop
= (HDROP
)GlobalLock(hglb
);
691 SCOPE_EXIT
{ GlobalUnlock(hDrop
); };
693 TCHAR szFileName
[MAX_PATH
] = {0};
694 UINT cFiles
= DragQueryFile(hDrop
, 0xFFFFFFFF, nullptr, 0);
695 for(UINT i
= 0; i
< cFiles
; ++i
)
697 DragQueryFile(hDrop
, i
, szFileName
, _countof(szFileName
));
698 std::wstring filename
= szFileName
;
699 ::WriteFile (file
, filename
.c_str(), (DWORD
)filename
.size()*sizeof(TCHAR
), &written
, 0);
700 ::WriteFile(file
, L
"\n", 2, &written
, 0);
706 std::wstring
CShellExt::WriteFileListToTempFile()
708 //write all selected files and paths to a temporary file
709 //for TortoiseGitProc.exe to read out again.
710 DWORD pathlength
= GetTortoiseGitTempPath(0, nullptr);
711 auto path
= std::make_unique
<TCHAR
[]>(pathlength
+ 1);
712 auto tempFile
= std::make_unique
<TCHAR
[]>(pathlength
+ 100);
713 GetTortoiseGitTempPath(pathlength
+ 1, path
.get());
714 GetTempFileName(path
.get(), L
"git", 0, tempFile
.get());
715 auto retFilePath
= std::wstring(tempFile
.get());
717 CAutoFile file
= ::CreateFile (tempFile
.get(),
722 FILE_ATTRIBUTE_TEMPORARY
,
727 MessageBox(nullptr, L
"Could not create temporary file. Please check (the permissions of) your temp-folder: " + CString(tempFile
.get()), L
"TortoiseGit", MB_ICONERROR
);
728 return std::wstring();
734 ::WriteFile (file
, folder_
.c_str(), (DWORD
)folder_
.size()*sizeof(TCHAR
), &written
, 0);
735 ::WriteFile(file
, L
"\n", 2, &written
, 0);
738 for (const auto& file_
: files_
)
740 ::WriteFile(file
, file_
.c_str(), (DWORD
)file_
.size() * sizeof(TCHAR
), &written
, 0);
741 ::WriteFile(file
, L
"\n", 2, &written
, 0);
746 STDMETHODIMP
CShellExt::QueryDropContext(UINT uFlags
, UINT idCmdFirst
, HMENU hMenu
, UINT
&indexMenu
)
748 if (!CRegStdDWORD(L
"Software\\TortoiseGit\\EnableDragContextMenu", TRUE
, false, HKEY_CURRENT_USER
, KEY_WOW64_64KEY
))
751 PreserveChdir preserveChdir
;
754 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
755 return S_OK
; //we don't change the default action
757 if (files_
.empty() || folder_
.empty())
760 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
763 bool bSourceAndTargetFromSameRepository
= ((uuidSource
.size() == uuidTarget
.size() && _wcsnicmp(uuidSource
.c_str(), uuidTarget
.c_str(), uuidSource
.size()) == 0)) || uuidSource
.empty() || uuidTarget
.empty();
765 //the drop handler only has eight commands, but not all are visible at the same time:
766 //if the source file(s) are under version control then those files can be moved
767 //to the new location or they can be moved with a rename,
768 //if they are unversioned then they can be added to the working copy
769 //if they are versioned, they also can be exported to an unversioned location
770 UINT idCmd
= idCmdFirst
;
772 bool moveAvailable
= false;
774 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
775 if ((bSourceAndTargetFromSameRepository
|| (itemStatesFolder
& ITEMIS_ADDED
)) && (itemStatesFolder
& ITEMIS_FOLDERINGIT
) && ((itemStates
& (ITEMIS_NORMAL
| ITEMIS_INGIT
| ITEMIS_FOLDERINGIT
))))
777 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVEMENU
, 0, idCmdFirst
, ShellMenuDropMove
, uFlags
);
778 moveAvailable
= true;
781 // Git move and rename here
782 // 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
783 if ((bSourceAndTargetFromSameRepository
|| (itemStatesFolder
& ITEMIS_ADDED
)) && (itemStatesFolder
& ITEMIS_FOLDERINGIT
) && (itemStates
& (ITEMIS_NORMAL
| ITEMIS_INGIT
| ITEMIS_FOLDERINGIT
)) && (itemStates
& ITEMIS_ONLYONE
))
785 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVERENAMEMENU
, 0, idCmdFirst
, ShellMenuDropMoveRename
, uFlags
);
786 moveAvailable
= true;
790 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
791 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&((~itemStates) & ITEMIS_ADDED))
792 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYMENU, 0, idCmdFirst, ShellMenuDropCopy, uFlags);
794 // Git copy and rename here, source and target from same repository
795 // available if source is a single, versioned but not added item, target is versioned or target folder is added
796 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
797 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYRENAMEMENU, 0, idCmdFirst, ShellMenuDropCopyRename, uFlags);
800 // available if target is versioned and source is either unversioned or from another repository
801 if ((itemStatesFolder
& ITEMIS_FOLDERINGIT
) && (((~itemStates
) & ITEMIS_INGIT
) || !bSourceAndTargetFromSameRepository
) && !moveAvailable
)
802 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYADDMENU
, 0, idCmdFirst
, ShellMenuDropCopyAdd
, uFlags
);
805 // available if source is versioned and a folder
806 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
807 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
809 // Git export all here
810 // available if source is versioned and a folder
811 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
812 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
815 // available if source is a patchfile
816 if (itemStates
& ITEMIS_PATCHFILE
)
818 if (itemStates
& ITEMIS_ONLYONE
)
819 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_MENUAPPLYPATCH
, 0, idCmdFirst
, ShellMenuApplyPatch
, uFlags
);
820 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_MENUIMPORTPATCH
, 0, idCmdFirst
, ShellMenuImportPatchDrop
, uFlags
);
824 if (idCmd
!= idCmdFirst
)
825 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr);
829 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
832 STDMETHODIMP
CShellExt::QueryContextMenu(HMENU hMenu
,
838 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: QueryContextMenu itemStates=%ld\n", itemStates
);
839 PreserveChdir preserveChdir
;
841 //first check if our drop handler is called
842 //and then (if true) provide the context menu for the
844 if (m_State
== FileStateDropHandler
)
846 return QueryDropContext(uFlags
, idCmdFirst
, hMenu
, indexMenu
);
849 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
850 return S_OK
; //we don't change the default action
852 if (files_
.empty() && folder_
.empty())
855 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
862 CSIDL_COMMON_FAVORITES
,
863 CSIDL_COMMON_STARTMENU
,
864 CSIDL_COMPUTERSNEARME
,
872 CSIDL_INTERNET_CACHE
,
882 if (IsIllegalFolder(folder_
, csidlarray
))
887 // folder is empty, but maybe files are selected
889 return S_OK
; // nothing selected - we don't have a menu to show
890 // check whether a selected entry is an UID - those are namespace extensions
891 // which we can't handle
892 for (const auto& file
: files_
)
894 if (CStringUtils::StartsWith(file
.c_str(), L
"::{"))
900 if (CStringUtils::StartsWith(folder_
.c_str(), L
"::{"))
904 if (((uFlags
& CMF_EXTENDEDVERBS
) == 0) && g_ShellCache
.HideMenusForUnversionedItems())
906 if ((itemStates
& (ITEMIS_INGIT
| ITEMIS_INVERSIONEDFOLDER
| ITEMIS_FOLDERINGIT
| ITEMIS_BAREREPO
)) == 0)
910 //check if our menu is requested for a git admin directory
911 if (GitAdminDir::IsAdminDirPath(folder_
.c_str()))
914 if (uFlags
& CMF_EXTENDEDVERBS
)
915 itemStates
|= ITEMIS_EXTENDED
;
918 if (!std::wstring(regDiffLater
).empty())
919 itemStates
|= ITEMIS_HASDIFFLATER
;
921 const BOOL bShortcut
= !!(uFlags
& CMF_VERBSONLY
);
922 if ( bShortcut
&& (files_
.size()==1))
924 // Don't show the context menu for a link if the
925 // destination is not part of a working copy.
926 // It would only show the standard menu items
927 // which are already shown for the lnk-file.
928 CString path
= files_
.front().c_str();
929 if (!GitAdminDir::HasAdminDir(path
))
935 //check if we already added our menu entry for a folder.
936 //we check that by iterating through all menu entries and check if
937 //the dwItemData member points to our global ID string. That string is set
938 //by our shell extension when the folder menu is inserted.
939 TCHAR menubuf
[MAX_PATH
] = {0};
940 int count
= GetMenuItemCount(hMenu
);
941 for (int i
=0; i
<count
; ++i
)
943 MENUITEMINFO miif
= { 0 };
944 miif
.cbSize
= sizeof(MENUITEMINFO
);
945 miif
.fMask
= MIIM_DATA
;
946 miif
.dwTypeData
= menubuf
;
947 miif
.cch
= _countof(menubuf
);
948 GetMenuItemInfo(hMenu
, i
, TRUE
, &miif
);
949 if (miif
.dwItemData
== (ULONG_PTR
)g_MenuIDString
)
954 UINT idCmd
= idCmdFirst
;
956 //create the sub menu
957 HMENU subMenu
= CreateMenu();
958 int indexSubMenu
= 0;
960 unsigned __int64 topmenu
= g_ShellCache
.GetMenuLayout();
961 unsigned __int64 menumask
= g_ShellCache
.GetMenuMask();
962 unsigned __int64 menuex
= g_ShellCache
.GetMenuExt();
965 bool bAddSeparator
= false;
966 bool bMenuEntryAdded
= false;
967 bool bMenuEmpty
= true;
968 // insert separator at start
969 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
970 bool bShowIcons
= !!DWORD(CRegStdDWORD(L
"Software\\TortoiseGit\\ShowContextMenuIcons", TRUE
, false, HKEY_CURRENT_USER
, KEY_WOW64_64KEY
));
972 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
974 if (menuInfo
[menuIndex
].command
== ShellSeparator
)
976 // we don't add a separator immediately. Because there might not be
977 // another 'normal' menu entry after we insert a separator.
978 // we simply set a flag here, indicating that before the next
979 // 'normal' menu entry, a separator should be added.
981 bAddSeparator
= true;
983 bAddSeparator
= true;
987 // check the conditions whether to show the menu entry or not
988 bool bInsertMenu
= ShouldInsertItem(menuInfo
[menuIndex
]);
989 if (menuInfo
[menuIndex
].menuID
& menuex
)
991 if( !(itemStates
& ITEMIS_EXTENDED
) )
995 if (menuInfo
[menuIndex
].menuID
& (~menumask
))
999 bool bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1000 // insert a separator
1001 if ((bMenuEntryAdded
)&&(bAddSeparator
)&&(!bIsTop
))
1003 bAddSeparator
= false;
1004 bMenuEntryAdded
= false;
1005 InsertMenu(subMenu
, indexSubMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, nullptr);
1009 // handle special cases (sub menus)
1010 if ((menuInfo
[menuIndex
].command
== ShellMenuIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuUnIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuDeleteIgnoreSub
))
1012 if(InsertIgnoreSubmenus(idCmd
, idCmdFirst
, hMenu
, subMenu
, indexMenu
, indexSubMenu
, topmenu
, bShowIcons
, uFlags
))
1013 bMenuEntryAdded
= true;
1017 bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1019 // insert the menu entry
1020 InsertGitMenu( bIsTop
,
1021 bIsTop
? hMenu
: subMenu
,
1022 bIsTop
? indexMenu
++ : indexSubMenu
++,
1024 menuInfo
[menuIndex
].menuTextID
,
1025 bShowIcons
? menuInfo
[menuIndex
].iconID
: 0,
1027 menuInfo
[menuIndex
].command
,
1031 bMenuEntryAdded
= true;
1033 bAddSeparator
= false;
1042 // do not show TortoiseGit menu if it's empty
1045 if (idCmd
- idCmdFirst
> 0)
1048 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
1052 //return number of menu items added
1053 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
1056 //add sub menu to main context menu
1057 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1058 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1059 MAKESTRING(IDS_MENUSUBMENU
);
1060 if (!g_ShellCache
.HasShellMenuAccelerators())
1062 // remove the accelerators
1063 tstring temp
= stringtablebuffer
;
1064 temp
.erase(std::remove(temp
.begin(), temp
.end(), '&'), temp
.end());
1065 wcscpy_s(stringtablebuffer
, temp
.c_str());
1067 MENUITEMINFO menuiteminfo
= { 0 };
1068 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
1069 menuiteminfo
.fType
= MFT_STRING
;
1070 menuiteminfo
.dwTypeData
= stringtablebuffer
;
1072 UINT uIcon
= bShowIcons
? IDI_APP
: 0;
1073 if (!folder_
.empty())
1075 uIcon
= bShowIcons
? IDI_MENUFOLDER
: 0;
1076 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFolder
;
1077 myIDMap
[idCmd
] = ShellSubMenuFolder
;
1078 menuiteminfo
.dwItemData
= (ULONG_PTR
)g_MenuIDString
;
1080 else if (!bShortcut
&& (files_
.size()==1))
1082 uIcon
= bShowIcons
? IDI_MENUFILE
: 0;
1083 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFile
;
1084 myIDMap
[idCmd
] = ShellSubMenuFile
;
1086 else if (bShortcut
&& (files_
.size()==1))
1088 uIcon
= bShowIcons
? IDI_MENULINK
: 0;
1089 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuLink
;
1090 myIDMap
[idCmd
] = ShellSubMenuLink
;
1092 else if (!files_
.empty())
1094 uIcon
= bShowIcons
? IDI_MENUMULTIPLE
: 0;
1095 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuMultiple
;
1096 myIDMap
[idCmd
] = ShellSubMenuMultiple
;
1100 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenu
;
1101 myIDMap
[idCmd
] = ShellSubMenu
;
1103 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
1106 menuiteminfo
.fMask
|= MIIM_BITMAP
;
1107 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, uIcon
);
1109 menuiteminfo
.hSubMenu
= subMenu
;
1110 menuiteminfo
.wID
= idCmd
++;
1111 InsertMenuItem(hMenu
, indexMenu
++, TRUE
, &menuiteminfo
);
1114 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
1118 //return number of menu items added
1119 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
1122 void CShellExt::TweakMenu(HMENU hMenu
)
1124 MENUINFO MenuInfo
= {};
1125 MenuInfo
.cbSize
= sizeof(MenuInfo
);
1126 MenuInfo
.fMask
= MIM_STYLE
| MIM_APPLYTOSUBMENUS
;
1127 MenuInfo
.dwStyle
= MNS_CHECKORBMP
;
1128 SetMenuInfo(hMenu
, &MenuInfo
);
1131 void CShellExt::AddPathCommand(tstring
& gitCmd
, LPCTSTR command
, bool bFilesAllowed
)
1134 gitCmd
+= L
" /path:\"";
1135 if ((bFilesAllowed
) && !files_
.empty())
1136 gitCmd
+= files_
.front();
1142 void CShellExt::AddPathFileCommand(tstring
& gitCmd
, LPCTSTR command
)
1144 tstring tempfile
= WriteFileListToTempFile();
1146 gitCmd
+= L
" /pathfile:\"";
1149 gitCmd
+= L
" /deletepathfile";
1152 void CShellExt::AddPathFileDropCommand(tstring
& gitCmd
, LPCTSTR command
)
1154 tstring tempfile
= WriteFileListToTempFile();
1156 gitCmd
+= L
" /pathfile:\"";
1159 gitCmd
+= L
" /deletepathfile";
1160 gitCmd
+= L
" /droptarget:\"";
1165 // This is called when you invoke a command on the menu:
1166 STDMETHODIMP
CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi
)
1168 PreserveChdir preserveChdir
;
1169 HRESULT hr
= E_INVALIDARG
;
1173 if (!files_
.empty() || !folder_
.empty())
1175 UINT_PTR idCmd
= LOWORD(lpcmi
->lpVerb
);
1177 if (HIWORD(lpcmi
->lpVerb
))
1179 auto verb
= std::wstring(MultibyteToWide(lpcmi
->lpVerb
));
1180 const auto verb_it
= myVerbsMap
.lower_bound(verb
);
1181 if (verb_it
!= myVerbsMap
.end() && verb_it
->first
== verb
)
1182 idCmd
= verb_it
->second
;
1187 // See if we have a handler interface for this id
1188 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1189 if (id_it
!= myIDMap
.end() && id_it
->first
== idCmd
)
1191 tstring
tortoiseProcPath(CPathUtils::GetAppDirectory(g_hmodThisDll
) + L
"TortoiseGitProc.exe");
1192 tstring
tortoiseMergePath(CPathUtils::GetAppDirectory(g_hmodThisDll
) + L
"TortoiseGitMerge.exe");
1194 //TortoiseGitProc expects a command line of the form:
1195 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1197 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1199 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1200 //* pathfile is a path to a temporary file which contains a list of file paths
1201 std::wstring gitCmd
= L
" /command:";
1202 std::wstring tempfile
;
1203 switch (id_it
->second
)
1208 TCHAR syncSeq
[12] = { 0 };
1209 swprintf_s(syncSeq
, L
"%d", g_syncSeq
++);
1210 AddPathCommand(gitCmd
, L
"sync", false);
1211 gitCmd
+= L
" /seq:";
1215 case ShellMenuSubSync
:
1216 AddPathFileCommand(gitCmd
, L
"subsync");
1217 if (itemStatesFolder
& ITEMIS_SUBMODULECONTAINER
|| (itemStates
& ITEMIS_SUBMODULECONTAINER
&& itemStates
& ITEMIS_WCROOT
&& itemStates
& ITEMIS_ONLYONE
))
1219 gitCmd
+= L
" /bkpath:\"";
1224 case ShellMenuUpdateExt
:
1225 AddPathFileCommand(gitCmd
, L
"subupdate");
1226 if (itemStatesFolder
& ITEMIS_SUBMODULECONTAINER
|| (itemStates
& ITEMIS_SUBMODULECONTAINER
&& itemStates
& ITEMIS_WCROOT
&& itemStates
& ITEMIS_ONLYONE
))
1228 gitCmd
+= L
" /bkpath:\"";
1233 case ShellMenuCommit
:
1234 AddPathFileCommand(gitCmd
, L
"commit");
1237 AddPathFileCommand(gitCmd
, L
"add");
1239 case ShellMenuIgnore
:
1240 AddPathFileCommand(gitCmd
, L
"ignore");
1242 case ShellMenuIgnoreCaseSensitive
:
1243 AddPathFileCommand(gitCmd
, L
"ignore");
1244 gitCmd
+= L
" /onlymask";
1246 case ShellMenuDeleteIgnore
:
1247 AddPathFileCommand(gitCmd
, L
"ignore");
1248 gitCmd
+= L
" /delete";
1250 case ShellMenuDeleteIgnoreCaseSensitive
:
1251 AddPathFileCommand(gitCmd
, L
"ignore");
1252 gitCmd
+= L
" /delete /onlymask";
1254 case ShellMenuUnIgnore
:
1255 AddPathFileCommand(gitCmd
, L
"unignore");
1257 case ShellMenuUnIgnoreCaseSensitive
:
1258 AddPathFileCommand(gitCmd
, L
"unignore");
1259 gitCmd
+= L
" /onlymask";
1261 case ShellMenuMergeAbort
:
1262 AddPathCommand(gitCmd
, L
"merge", false);
1263 gitCmd
+= L
" /abort";
1265 case ShellMenuRevert
:
1266 AddPathFileCommand(gitCmd
, L
"revert");
1268 case ShellMenuCleanup
:
1269 AddPathFileCommand(gitCmd
, L
"cleanup");
1271 case ShellMenuSendMail
:
1272 AddPathFileCommand(gitCmd
, L
"sendmail");
1274 case ShellMenuResolve
:
1275 AddPathFileCommand(gitCmd
, L
"resolve");
1277 case ShellMenuSwitch
:
1278 AddPathCommand(gitCmd
, L
"switch", false);
1280 case ShellMenuExport
:
1281 AddPathCommand(gitCmd
, L
"export", false);
1283 case ShellMenuAbout
:
1286 case ShellMenuCreateRepos
:
1287 AddPathCommand(gitCmd
, L
"repocreate", false);
1289 case ShellMenuMerge
:
1290 AddPathCommand(gitCmd
, L
"merge", false);
1293 AddPathCommand(gitCmd
, L
"copy", true);
1295 case ShellMenuSettings
:
1296 AddPathCommand(gitCmd
, L
"settings", true);
1301 case ShellMenuRename
:
1302 AddPathCommand(gitCmd
, L
"rename", true);
1304 case ShellMenuRemove
:
1305 AddPathFileCommand(gitCmd
, L
"remove");
1306 if (itemStates
& ITEMIS_SUBMODULE
)
1307 gitCmd
+= L
" /submodule";
1309 case ShellMenuRemoveKeep
:
1310 AddPathFileCommand(gitCmd
, L
"remove");
1311 gitCmd
+= L
" /keep";
1314 gitCmd
+= L
"diff /path:\"";
1315 if (files_
.size() == 1)
1316 gitCmd
+= files_
.front();
1317 else if (files_
.size() == 2)
1319 auto I
= files_
.cbegin();
1322 gitCmd
+= L
"\" /path2:\"";
1328 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1329 gitCmd
+= L
" /alternative";
1331 case ShellMenuDiffLater
:
1332 if (lpcmi
->fMask
& CMIC_MASK_CONTROL_DOWN
)
1335 regDiffLater
.removeValue();
1337 else if (files_
.size() == 1)
1339 if (std::wstring(regDiffLater
).empty())
1342 regDiffLater
= files_
[0];
1346 AddPathCommand(gitCmd
, L
"diff", true);
1347 gitCmd
+= L
" /path2:\"";
1348 gitCmd
+= std::wstring(regDiffLater
);
1350 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1351 gitCmd
+= L
" /alternative";
1352 regDiffLater
.removeValue();
1358 case ShellMenuPrevDiff
:
1359 AddPathCommand(gitCmd
, L
"prevdiff", true);
1360 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1361 gitCmd
+= L
" /alternative";
1363 case ShellMenuDiffTwo
:
1364 AddPathCommand(gitCmd
, L
"diffcommits", true);
1366 case ShellMenuDropCopyAdd
:
1367 AddPathFileDropCommand(gitCmd
, L
"dropcopyadd");
1369 case ShellMenuDropCopy
:
1370 AddPathFileDropCommand(gitCmd
, L
"dropcopy");
1372 case ShellMenuDropCopyRename
:
1373 AddPathFileDropCommand(gitCmd
, L
"dropcopy");
1374 gitCmd
+= L
" /rename";
1376 case ShellMenuDropMove
:
1377 AddPathFileDropCommand(gitCmd
, L
"dropmove");
1379 case ShellMenuDropMoveRename
:
1380 AddPathFileDropCommand(gitCmd
, L
"dropmove");
1381 gitCmd
+= L
" /rename";
1383 case ShellMenuDropExport
:
1384 AddPathFileDropCommand(gitCmd
, L
"dropexport");
1386 case ShellMenuDropExportExtended
:
1387 AddPathFileDropCommand(gitCmd
, L
"dropexport");
1388 gitCmd
+= L
" /extended";
1391 case ShellMenuLogSubmoduleFolder
:
1392 AddPathCommand(gitCmd
, L
"log", true);
1393 if (id_it
->second
== ShellMenuLogSubmoduleFolder
)
1394 gitCmd
+= L
" /submodule";
1396 case ShellMenuDaemon
:
1397 AddPathCommand(gitCmd
, L
"daemon", true);
1399 case ShellMenuRevisionGraph
:
1400 AddPathCommand(gitCmd
, L
"revisiongraph", true);
1402 case ShellMenuConflictEditor
:
1403 AddPathCommand(gitCmd
, L
"conflicteditor", true);
1404 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1405 gitCmd
+= L
" /alternative";
1407 case ShellMenuGitSVNRebase
:
1408 AddPathCommand(gitCmd
, L
"svnrebase", false);
1410 case ShellMenuGitSVNDCommit
:
1411 AddPathCommand(gitCmd
, L
"svndcommit", true);
1413 case ShellMenuGitSVNDFetch
:
1414 AddPathCommand(gitCmd
, L
"svnfetch", false);
1416 case ShellMenuGitSVNIgnore
:
1417 AddPathCommand(gitCmd
, L
"svnignore", false);
1419 case ShellMenuRebase
:
1420 AddPathCommand(gitCmd
, L
"rebase", false);
1422 case ShellMenuShowChanged
:
1423 if (files_
.size() > 1)
1424 AddPathFileCommand(gitCmd
, L
"repostatus");
1426 AddPathCommand(gitCmd
, L
"repostatus", true);
1428 case ShellMenuRepoBrowse
:
1429 AddPathCommand(gitCmd
, L
"repobrowser", false);
1431 case ShellMenuRefBrowse
:
1432 AddPathCommand(gitCmd
, L
"refbrowse", false);
1434 case ShellMenuRefLog
:
1435 AddPathCommand(gitCmd
, L
"reflog", false);
1437 case ShellMenuStashSave
:
1438 AddPathCommand(gitCmd
, L
"stashsave", true);
1440 case ShellMenuStashApply
:
1441 AddPathCommand(gitCmd
, L
"stashapply", false);
1443 case ShellMenuStashPop
:
1444 AddPathCommand(gitCmd
, L
"stashpop", false);
1446 case ShellMenuStashList
:
1447 AddPathCommand(gitCmd
, L
"reflog", false);
1448 gitCmd
+= L
" /ref:refs/stash";
1450 case ShellMenuBisectStart
:
1451 AddPathCommand(gitCmd
, L
"bisect", false);
1452 gitCmd
+= L
" /start";
1454 case ShellMenuBisectGood
:
1455 AddPathCommand(gitCmd
, L
"bisect", false);
1456 gitCmd
+= L
" /good";
1458 case ShellMenuBisectBad
:
1459 AddPathCommand(gitCmd
, L
"bisect", false);
1462 case ShellMenuBisectSkip
:
1463 AddPathCommand(gitCmd
, L
"bisect", false);
1464 gitCmd
+= L
" /skip";
1466 case ShellMenuBisectReset
:
1467 AddPathCommand(gitCmd
, L
"bisect", false);
1468 gitCmd
+= L
" /reset";
1470 case ShellMenuSubAdd
:
1471 AddPathCommand(gitCmd
, L
"subadd", false);
1473 case ShellMenuBlame
:
1474 AddPathCommand(gitCmd
, L
"blame", true);
1476 case ShellMenuApplyPatch
:
1477 if ((itemStates
& ITEMIS_PATCHINCLIPBOARD
) && ((~itemStates
) & ITEMIS_PATCHFILE
))
1479 // if there's a patch file in the clipboard, we save it
1480 // to a temporary file and tell TortoiseGitMerge to use that one
1481 UINT cFormat
= RegisterClipboardFormat(L
"TGIT_UNIFIEDDIFF");
1482 if (cFormat
&& OpenClipboard(nullptr))
1484 HGLOBAL hglb
= GetClipboardData(cFormat
);
1485 LPCSTR lpstr
= (LPCSTR
)GlobalLock(hglb
);
1487 DWORD len
= GetTortoiseGitTempPath(0, nullptr);
1488 auto path
= std::make_unique
<TCHAR
[]>(len
+ 1);
1489 auto tempF
= std::make_unique
<TCHAR
[]>(len
+ 100);
1490 GetTortoiseGitTempPath(len
+ 1, path
.get());
1491 GetTempFileName(path
.get(), TEXT("git"), 0, tempF
.get());
1492 std::wstring sTempFile
= std::wstring(tempF
.get());
1495 size_t patchlen
= strlen(lpstr
);
1496 _wfopen_s(&outFile
, sTempFile
.c_str(), L
"wb");
1499 size_t size
= fwrite(lpstr
, sizeof(char), patchlen
, outFile
);
1500 if (size
== patchlen
)
1502 itemStates
|= ITEMIS_PATCHFILE
;
1504 files_
.push_back(sTempFile
);
1512 if (itemStates
& ITEMIS_PATCHFILE
)
1514 gitCmd
= L
" /diff:\"";
1515 if (!files_
.empty())
1517 gitCmd
+= files_
.front();
1518 if (itemStatesFolder
& ITEMIS_FOLDERINGIT
)
1520 gitCmd
+= L
"\" /patchpath:\"";
1526 if (itemStates
& ITEMIS_INVERSIONEDFOLDER
)
1527 gitCmd
+= L
"\" /wc";
1533 gitCmd
= L
" /patchpath:\"";
1534 if (!files_
.empty())
1535 gitCmd
+= files_
.front();
1541 myVerbsIDMap
.clear();
1543 RunCommand(tortoiseMergePath
, gitCmd
, L
"TortoiseGitMerge launch failed");
1546 case ShellMenuClipPaste
:
1547 if (WriteClipboardPathsToTempFile(tempfile
))
1550 UINT cPrefDropFormat
= RegisterClipboardFormat(L
"Preferred DropEffect");
1551 if (cPrefDropFormat
)
1553 if (OpenClipboard(lpcmi
->hwnd
))
1555 HGLOBAL hglb
= GetClipboardData(cPrefDropFormat
);
1558 DWORD
* effect
= (DWORD
*) GlobalLock(hglb
);
1559 if (*effect
== DROPEFFECT_MOVE
)
1568 gitCmd
+= L
"pastecopy /pathfile:\"";
1570 gitCmd
+= L
"pastemove /pathfile:\"";
1573 gitCmd
+= L
" /deletepathfile";
1574 gitCmd
+= L
" /droptarget:\"";
1580 case ShellMenuClone
:
1581 AddPathCommand(gitCmd
, L
"clone", false);
1584 AddPathCommand(gitCmd
, L
"pull", false);
1587 AddPathCommand(gitCmd
, L
"push", false);
1589 case ShellMenuBranch
:
1590 AddPathCommand(gitCmd
, L
"branch", false);
1593 AddPathCommand(gitCmd
, L
"tag", false);
1595 case ShellMenuFormatPatch
:
1596 AddPathCommand(gitCmd
, L
"formatpatch", false);
1598 case ShellMenuImportPatch
:
1599 AddPathFileCommand(gitCmd
, L
"importpatch");
1601 case ShellMenuImportPatchDrop
:
1602 AddPathFileDropCommand(gitCmd
, L
"importpatch");
1604 case ShellMenuFetch
:
1605 AddPathCommand(gitCmd
, L
"fetch", false);
1611 } // switch (id_it->second)
1612 if (!gitCmd
.empty())
1614 gitCmd
+= L
" /hwnd:";
1615 TCHAR buf
[30] = { 0 };
1616 swprintf_s(buf
, L
"%p", (void*)lpcmi
->hwnd
);
1619 myVerbsIDMap
.clear();
1621 RunCommand(tortoiseProcPath
, gitCmd
, L
"TortoiseProc launch failed");
1624 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1625 } // if (files_.empty() || folder_.empty())
1629 // This is for the status bar and things like that:
1630 STDMETHODIMP
CShellExt::GetCommandString(UINT_PTR idCmd
,
1632 UINT FAR
* /*reserved*/,
1636 PreserveChdir preserveChdir
;
1637 //do we know the id?
1638 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1639 if (id_it
== myIDMap
.end() || id_it
->first
!= idCmd
)
1641 return E_INVALIDARG
; //no, we don't
1645 HRESULT hr
= E_INVALIDARG
;
1647 MAKESTRING(IDS_MENUDESCDEFAULT
);
1649 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1651 if (menuInfo
[menuIndex
].command
== (GitCommands
)id_it
->second
)
1653 MAKESTRING(menuInfo
[menuIndex
].menuDescID
);
1659 const TCHAR
* desc
= stringtablebuffer
;
1664 std::string help
= WideToMultibyte(desc
);
1665 lstrcpynA(pszName
, help
.c_str(), cchMax
- 1);
1671 std::wstring help
= desc
;
1672 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
- 1);
1678 const auto verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
1679 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
1681 std::string help
= WideToMultibyte(verb_id_it
->second
);
1682 lstrcpynA(pszName
, help
.c_str(), cchMax
- 1);
1689 const auto verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
1690 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
1692 std::wstring help
= verb_id_it
->second
;
1693 CTraceToOutputDebugString::Instance()(__FUNCTION__
": verb : %ws\n", help
.c_str());
1694 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
- 1);
1703 STDMETHODIMP
CShellExt::HandleMenuMsg(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
1706 return HandleMenuMsg2(uMsg
, wParam
, lParam
, &res
);
1709 STDMETHODIMP
CShellExt::HandleMenuMsg2(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*pResult
)
1711 PreserveChdir preserveChdir
;
1721 case WM_MEASUREITEM
:
1723 MEASUREITEMSTRUCT
* lpmis
= (MEASUREITEMSTRUCT
*)lParam
;
1726 lpmis
->itemWidth
= 16;
1727 lpmis
->itemHeight
= 16;
1734 DRAWITEMSTRUCT
* lpdis
= (DRAWITEMSTRUCT
*)lParam
;
1735 if (!lpdis
|| lpdis
->CtlType
!= ODT_MENU
)
1736 return S_OK
; //not for a menu
1737 resource
= GetMenuTextFromResource((int)myIDMap
[lpdis
->itemID
]);
1740 int iconWidth
= GetSystemMetrics(SM_CXSMICON
);
1741 int iconHeight
= GetSystemMetrics(SM_CYSMICON
);
1742 HICON hIcon
= (HICON
)LoadImage(g_hResInst
, resource
, IMAGE_ICON
, iconWidth
, iconHeight
, LR_DEFAULTCOLOR
);
1745 DrawIconEx(lpdis
->hDC
,
1747 lpdis
->rcItem
.top
+ (lpdis
->rcItem
.bottom
- lpdis
->rcItem
.top
- iconHeight
) / 2,
1748 hIcon
, iconWidth
, iconHeight
,
1749 0, nullptr, DI_NORMAL
);
1757 if (HIWORD(wParam
) != MF_POPUP
)
1759 int nChar
= LOWORD(wParam
);
1760 if (_istascii((wint_t)nChar
) && _istupper((wint_t)nChar
))
1761 nChar
= tolower(nChar
);
1762 // we have the char the user pressed, now search that char in all our
1764 std::vector
<UINT_PTR
> accmenus
;
1765 for (auto It
= mySubMenuMap
.cbegin(); It
!= mySubMenuMap
.cend(); ++It
)
1767 LPCTSTR resource
= GetMenuTextFromResource((int)mySubMenuMap
[It
->first
]);
1770 szItem
= stringtablebuffer
;
1771 TCHAR
* amp
= wcschr(szItem
, L
'&');
1775 int ampChar
= LOWORD(*amp
);
1776 if (_istascii((wint_t)ampChar
) && _istupper((wint_t)ampChar
))
1777 ampChar
= tolower(ampChar
);
1778 if (ampChar
== nChar
)
1780 // yep, we found a menu which has the pressed key
1781 // as an accelerator. Add that menu to the list to
1783 accmenus
.push_back(It
->first
);
1786 if (accmenus
.empty())
1788 // no menu with that accelerator key.
1789 *pResult
= MAKELONG(0, MNC_IGNORE
);
1792 if (accmenus
.size() == 1)
1794 // Only one menu with that accelerator key. We're lucky!
1795 // So just execute that menu entry.
1796 *pResult
= MAKELONG(accmenus
[0], MNC_EXECUTE
);
1799 if (accmenus
.size() > 1)
1801 // we have more than one menu item with this accelerator key!
1803 mif
.cbSize
= sizeof(MENUITEMINFO
);
1804 mif
.fMask
= MIIM_STATE
;
1805 for (auto it
= accmenus
.cbegin(); it
!= accmenus
.cend(); ++it
)
1807 GetMenuItemInfo((HMENU
)lParam
, (UINT
)*it
, TRUE
, &mif
);
1808 if (mif
.fState
== MFS_HILITE
)
1810 // this is the selected item, so select the next one
1812 if (it
== accmenus
.end())
1813 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
1815 *pResult
= MAKELONG(*it
, MNC_SELECT
);
1819 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
1830 LPCTSTR
CShellExt::GetMenuTextFromResource(int id
)
1832 TCHAR textbuf
[255] = { 0 };
1833 LPCTSTR resource
= nullptr;
1834 unsigned __int64 layout
= g_ShellCache
.GetMenuLayout();
1838 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1840 if (menuInfo
[menuIndex
].command
== id
)
1842 MAKESTRING(menuInfo
[menuIndex
].menuTextID
);
1843 resource
= MAKEINTRESOURCE(menuInfo
[menuIndex
].iconID
);
1846 case ShellSubMenuMultiple
:
1847 case ShellSubMenuLink
:
1848 case ShellSubMenuFolder
:
1849 case ShellSubMenuFile
:
1854 space
= (layout
& menuInfo
[menuIndex
].menuID
) ? 0 : 6;
1855 if (layout
& menuInfo
[menuIndex
].menuID
)
1857 wcscpy_s(textbuf
, 255, L
"Git ");
1858 wcscat_s(textbuf
, 255, stringtablebuffer
);
1859 wcscpy_s(stringtablebuffer
, 255, textbuf
);
1870 bool CShellExt::IsIllegalFolder(const std::wstring
& folder
, int* cslidarray
)
1873 TCHAR buf
[MAX_PATH
] = {0}; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
1874 LPITEMIDLIST pidl
= nullptr;
1875 while (cslidarray
[i
])
1879 if (SHGetFolderLocation(nullptr, cslidarray
[i
- 1], nullptr, 0, &pidl
) != S_OK
)
1881 if (!SHGetPathFromIDList(pidl
, buf
))
1883 // not a file system path, definitely illegal for our use
1884 CoTaskMemFree(pidl
);
1887 CoTaskMemFree(pidl
);
1890 if (wcscmp(buf
, folder
.c_str()) == 0)
1896 bool CShellExt::InsertIgnoreSubmenus(UINT
&idCmd
, UINT idCmdFirst
, HMENU hMenu
, HMENU subMenu
, UINT
&indexMenu
, int &indexSubMenu
, unsigned __int64 topmenu
, bool bShowIcons
, UINT
/*uFlags*/)
1898 HMENU ignoresubmenu
= nullptr;
1899 int indexignoresub
= 0;
1900 bool bShowIgnoreMenu
= false;
1901 TCHAR maskbuf
[MAX_PATH
] = {0}; // MAX_PATH is ok, since this only holds a filename
1902 TCHAR ignorepath
[MAX_PATH
] = {0}; // MAX_PATH is ok, since this only holds a filename
1905 UINT icon
= bShowIcons
? IDI_IGNORE
: 0;
1907 auto I
= files_
.cbegin();
1908 if (wcsrchr(I
->c_str(), L
'\\'))
1909 wcscpy_s(ignorepath
, wcsrchr(I
->c_str(), L
'\\') + 1);
1911 wcscpy_s(ignorepath
, I
->c_str());
1912 if ((itemStates
& ITEMIS_IGNORED
) && (!ignoredprops
.empty()))
1914 // check if the item name is ignored or the mask
1916 const size_t pathLength
= wcslen(ignorepath
);
1917 while ( (p
=ignoredprops
.find( ignorepath
,p
)) != -1 )
1919 if ( (p
==0 || ignoredprops
[p
-1]==TCHAR('\n'))
1920 && (p
+ pathLength
== ignoredprops
.length() || ignoredprops
[p
+ pathLength
+ 1] == TCHAR('\n')))
1928 ignoresubmenu
= CreateMenu();
1929 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
1930 auto verb
= std::wstring(ignorepath
);
1931 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
1932 myVerbsMap
[verb
] = idCmd
;
1933 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
1934 myVerbsIDMap
[idCmd
] = verb
;
1935 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnore
;
1936 myIDMap
[idCmd
++] = ShellMenuUnIgnore
;
1937 bShowIgnoreMenu
= true;
1939 wcscpy_s(maskbuf
, L
"*");
1940 if (wcsrchr(ignorepath
, L
'.'))
1942 wcscat_s(maskbuf
, wcsrchr(ignorepath
, L
'.'));
1943 p
= ignoredprops
.find(maskbuf
);
1945 ((ignoredprops
.compare(maskbuf
) == 0) || (ignoredprops
.find(L
'\n', p
) == p
+ wcslen(maskbuf
) + 1) || (ignoredprops
.rfind(L
'\n', p
) == p
- 1)))
1948 ignoresubmenu
= CreateMenu();
1950 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
1951 auto verb
= std::wstring(maskbuf
);
1952 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
1953 myVerbsMap
[verb
] = idCmd
;
1954 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
1955 myVerbsIDMap
[idCmd
] = verb
;
1956 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreCaseSensitive
;
1957 myIDMap
[idCmd
++] = ShellMenuUnIgnoreCaseSensitive
;
1958 bShowIgnoreMenu
= true;
1962 else if ((itemStates
& ITEMIS_IGNORED
) == 0)
1964 bShowIgnoreMenu
= true;
1965 ignoresubmenu
= CreateMenu();
1966 if (itemStates
& ITEMIS_ONLYONE
)
1968 if (itemStates
& ITEMIS_INGIT
)
1970 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
1971 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnore
;
1972 myIDMap
[idCmd
++] = ShellMenuDeleteIgnore
;
1974 wcscpy_s(maskbuf
, L
"*");
1975 if (!(itemStates
& ITEMIS_FOLDER
) && wcsrchr(ignorepath
, L
'.'))
1977 wcscat_s(maskbuf
, wcsrchr(ignorepath
, L
'.'));
1978 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
1979 auto verb
= std::wstring(maskbuf
);
1980 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
1981 myVerbsMap
[verb
] = idCmd
;
1982 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
1983 myVerbsIDMap
[idCmd
] = verb
;
1984 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnoreCaseSensitive
;
1985 myIDMap
[idCmd
++] = ShellMenuDeleteIgnoreCaseSensitive
;
1990 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
1991 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
1992 myIDMap
[idCmd
++] = ShellMenuIgnore
;
1994 wcscpy_s(maskbuf
, L
"*");
1995 if (!(itemStates
& ITEMIS_FOLDER
) && wcsrchr(ignorepath
, L
'.'))
1997 wcscat_s(maskbuf
, wcsrchr(ignorepath
, L
'.'));
1998 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
1999 auto verb
= std::wstring(maskbuf
);
2000 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2001 myVerbsMap
[verb
] = idCmd
;
2002 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2003 myVerbsIDMap
[idCmd
] = verb
;
2004 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2005 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2011 // note: as of Windows 7, the shell does not pass more than 16 items from a multiselection
2012 // in the Initialize() call before the QueryContextMenu() call. Which means even if the user
2013 // has selected more than 16 files, we won't know about that here.
2014 // Note: after QueryContextMenu() exits, Initialize() is called again with all selected files.
2015 if (itemStates
& ITEMIS_INGIT
)
2017 if (files_
.size() >= 16)
2019 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE2
);
2020 wcscpy_s(ignorepath
, stringtablebuffer
);
2024 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE
);
2025 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2027 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2028 auto verb
= std::wstring(ignorepath
);
2029 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2030 myVerbsMap
[verb
] = idCmd
;
2031 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2032 myVerbsIDMap
[idCmd
] = verb
;
2033 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnore
;
2034 myIDMap
[idCmd
++] = ShellMenuDeleteIgnore
;
2036 if (files_
.size() >= 16)
2038 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK2
);
2039 wcscpy_s(ignorepath
, stringtablebuffer
);
2043 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK
);
2044 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2046 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2047 verb
= std::wstring(ignorepath
);
2048 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2049 myVerbsMap
[verb
] = idCmd
;
2050 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2051 myVerbsIDMap
[idCmd
] = verb
;
2052 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnoreCaseSensitive
;
2053 myIDMap
[idCmd
++] = ShellMenuDeleteIgnoreCaseSensitive
;
2057 if (files_
.size() >= 16)
2059 MAKESTRING(IDS_MENUIGNOREMULTIPLE2
);
2060 wcscpy_s(ignorepath
, stringtablebuffer
);
2064 MAKESTRING(IDS_MENUIGNOREMULTIPLE
);
2065 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2067 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2068 auto verb
= std::wstring(ignorepath
);
2069 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2070 myVerbsMap
[verb
] = idCmd
;
2071 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2072 myVerbsIDMap
[idCmd
] = verb
;
2073 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
2074 myIDMap
[idCmd
++] = ShellMenuIgnore
;
2076 if (files_
.size() >= 16)
2078 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK2
);
2079 wcscpy_s(ignorepath
, stringtablebuffer
);
2083 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK
);
2084 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2086 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2087 verb
= std::wstring(ignorepath
);
2088 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2089 myVerbsMap
[verb
] = idCmd
;
2090 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2091 myVerbsIDMap
[idCmd
] = verb
;
2092 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2093 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2098 if (bShowIgnoreMenu
)
2100 MENUITEMINFO menuiteminfo
= { 0 };
2101 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
2102 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
2105 menuiteminfo
.fMask
|= MIIM_BITMAP
;
2106 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, icon
);
2108 menuiteminfo
.fType
= MFT_STRING
;
2109 menuiteminfo
.hSubMenu
= ignoresubmenu
;
2110 menuiteminfo
.wID
= idCmd
;
2111 SecureZeroMemory(stringtablebuffer
, sizeof(stringtablebuffer
));
2112 if (itemStates
& ITEMIS_IGNORED
)
2113 GetMenuTextFromResource(ShellMenuUnIgnoreSub
);
2114 else if (itemStates
& ITEMIS_INGIT
)
2115 GetMenuTextFromResource(ShellMenuDeleteIgnoreSub
);
2117 GetMenuTextFromResource(ShellMenuIgnoreSub
);
2118 menuiteminfo
.dwTypeData
= stringtablebuffer
;
2119 menuiteminfo
.cch
= (UINT
)min(wcslen(menuiteminfo
.dwTypeData
), UINT_MAX
);
2121 InsertMenuItem((topmenu
& MENUIGNORE
) ? hMenu
: subMenu
, (topmenu
& MENUIGNORE
) ? indexMenu
++ : indexSubMenu
++, TRUE
, &menuiteminfo
);
2122 if (itemStates
& ITEMIS_IGNORED
)
2124 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreSub
;
2125 myIDMap
[idCmd
++] = ShellMenuUnIgnoreSub
;
2129 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreSub
;
2130 myIDMap
[idCmd
++] = ShellMenuIgnoreSub
;
2133 return bShowIgnoreMenu
;
2136 void CShellExt::RunCommand(const tstring
& path
, const tstring
& command
, LPCTSTR errorMessage
)
2138 if (CCreateProcessHelper::CreateProcessDetached(path
.c_str(), command
.c_str()))
2140 // process started - exit
2144 MessageBox(nullptr, CFormatMessageWrapper(), errorMessage
, MB_OK
| MB_ICONERROR
);
2147 bool CShellExt::ShouldInsertItem(const MenuInfo
& item
) const
2149 return ShouldEnableMenu(item
.first
) || ShouldEnableMenu(item
.second
) ||
2150 ShouldEnableMenu(item
.third
) || ShouldEnableMenu(item
.fourth
);
2153 bool CShellExt::ShouldEnableMenu(const YesNoPair
& pair
) const
2155 if (pair
.yes
&& pair
.no
)
2157 if (((pair
.yes
& itemStates
) == pair
.yes
) && ((pair
.no
& (~itemStates
)) == pair
.no
))
2160 else if ((pair
.yes
) && ((pair
.yes
& itemStates
) == pair
.yes
))
2162 else if ((pair
.no
) && ((pair
.no
& (~itemStates
)) == pair
.no
))