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