1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012 - TortoiseSVN
4 // Copyright (C) 2008-2012 - 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"
25 #include "GitStatus.h"
27 #include "PathUtils.h"
28 #include "CreateProcessHelper.h"
29 #include "FormatMessageWrapper.h"
32 #define GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
33 #define GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
35 int g_shellidlist
=RegisterClipboardFormat(CFSTR_SHELLIDLIST
);
37 extern MenuInfo menuInfo
[];
40 STDMETHODIMP
CShellExt::Initialize(LPCITEMIDLIST pIDFolder
,
41 LPDATAOBJECT pDataObj
,
46 return Initialize_Wrap(pIDFolder
, pDataObj
, hRegKey
);
48 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
54 STDMETHODIMP
CShellExt::Initialize_Wrap(LPCITEMIDLIST pIDFolder
,
55 LPDATAOBJECT pDataObj
,
58 ATLTRACE("Shell :: Initialize\n");
59 PreserveChdir preserveChdir
;
67 git_wc_status_kind fetchedstatus
= git_wc_status_none
;
68 // get selected files/folders
72 FORMATETC fmte
= {(CLIPFORMAT
)g_shellidlist
,
73 (DVTARGETDEVICE FAR
*)NULL
,
77 HRESULT hres
= pDataObj
->GetData(&fmte
, &medium
);
79 if (SUCCEEDED(hres
) && medium
.hGlobal
)
81 if (m_State
== FileStateDropHandler
)
84 FORMATETC etc
= { CF_HDROP
, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
};
85 STGMEDIUM stg
= { TYMED_HGLOBAL
};
86 if ( FAILED( pDataObj
->GetData ( &etc
, &stg
)))
88 ReleaseStgMedium ( &medium
);
93 HDROP drop
= (HDROP
)GlobalLock(stg
.hGlobal
);
96 ReleaseStgMedium ( &stg
);
97 ReleaseStgMedium ( &medium
);
101 int count
= DragQueryFile(drop
, (UINT
)-1, NULL
, 0);
103 itemStates
|= ITEMIS_ONLYONE
;
104 for (int i
= 0; i
< count
; i
++)
106 // find the path length in chars
107 UINT len
= DragQueryFile(drop
, i
, NULL
, 0);
110 std::unique_ptr
<TCHAR
[]> szFileName(new TCHAR
[len
+ 1]);
111 if (0 == DragQueryFile(drop
, i
, szFileName
.get(), len
+ 1))
113 stdstring str
= stdstring(szFileName
.get());
114 if ((!str
.empty()) && (g_ShellCache
.IsContextPathAllowed(szFileName
.get())))
116 if (itemStates
& ITEMIS_ONLYONE
)
119 strpath
.SetFromWin(str
.c_str());
120 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE
: 0;
121 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE
: 0;
123 files_
.push_back(str
);
126 //get the Subversion status of the item
127 git_wc_status_kind status
= git_wc_status_none
;
129 askedpath
.SetFromWin(str
.c_str());
133 stat
.GetStatus(CTGitPath(str
.c_str()), false, false, true);
137 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
138 fetchedstatus
= status
;
139 //if ((stat.status->entry)&&(stat.status->entry->lock_token))
140 // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
141 if ( askedpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
143 itemStates
|= ITEMIS_FOLDER
;
144 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
145 itemStates
|= ITEMIS_FOLDERINGIT
;
147 //if ((stat.status->entry)&&(stat.status->entry->present_props))
149 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
150 // itemStates |= ITEMIS_NEEDSLOCK;
152 //if ((stat.status->entry)&&(stat.status->entry->uuid))
153 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
157 // sometimes, git_client_status() returns with an error.
158 // in that case, we have to check if the working copy is versioned
159 // anyway to show the 'correct' context menu
160 if (askedpath
.HasAdminDir())
161 status
= git_wc_status_normal
;
166 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
169 // TODO: should we really assume any sub-directory to be versioned
170 // or only if it contains versioned files
171 itemStates
|= askedpath
.GetAdminDirMask();
173 if ((status
== git_wc_status_unversioned
) || (status
== git_wc_status_ignored
) || (status
== git_wc_status_none
))
174 itemStates
&= ~ITEMIS_INGIT
;
176 if (status
== git_wc_status_ignored
)
177 itemStates
|= ITEMIS_IGNORED
;
178 if (status
== git_wc_status_normal
)
179 itemStates
|= ITEMIS_NORMAL
;
180 if (status
== git_wc_status_conflicted
)
181 itemStates
|= ITEMIS_CONFLICTED
;
182 if (status
== git_wc_status_added
)
183 itemStates
|= ITEMIS_ADDED
;
184 if (status
== git_wc_status_deleted
)
185 itemStates
|= ITEMIS_DELETED
;
188 } // for (int i = 0; i < count; i++)
189 GlobalUnlock ( drop
);
190 ReleaseStgMedium ( &stg
);
192 } // if (m_State == FileStateDropHandler)
196 //Enumerate PIDLs which the user has selected
197 CIDA
* cida
= (CIDA
*)GlobalLock(medium
.hGlobal
);
198 ItemIDList
parent( GetPIDLFolder (cida
));
200 int count
= cida
->cidl
;
201 BOOL statfetched
= FALSE
;
202 for (int i
= 0; i
< count
; ++i
)
204 ItemIDList
child (GetPIDLItem (cida
, i
), &parent
);
205 stdstring str
= child
.toString();
206 if ((str
.empty() == false)&&(g_ShellCache
.IsContextPathAllowed(str
.c_str())))
208 //check if our menu is requested for a subversion admin directory
209 if (g_GitAdminDir
.IsAdminDirPath(str
.c_str()))
212 files_
.push_back(str
);
214 strpath
.SetFromWin(str
.c_str());
215 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE
: 0;
216 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE
: 0;
219 //get the Subversion status of the item
220 git_wc_status_kind status
= git_wc_status_none
;
221 if ((g_ShellCache
.IsSimpleContext())&&(strpath
.IsDirectory()))
223 if (strpath
.HasAdminDir())
224 status
= git_wc_status_normal
;
231 if (strpath
.HasAdminDir())
232 stat
.GetStatus(strpath
, false, false, true);
236 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
237 fetchedstatus
= status
;
238 //if ((stat.status->entry)&&(stat.status->entry->lock_token))
239 // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
240 if ( strpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
242 itemStates
|= ITEMIS_FOLDER
;
243 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
244 itemStates
|= ITEMIS_FOLDERINGIT
;
246 // TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
247 if (status
== git_wc_status_conflicted
)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
248 itemStates
|= ITEMIS_CONFLICTED
;
249 //if ((stat.status->entry)&&(stat.status->entry->present_props))
251 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
252 // itemStates |= ITEMIS_NEEDSLOCK;
254 //if ((stat.status->entry)&&(stat.status->entry->uuid))
255 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
259 // sometimes, git_client_status() returns with an error.
260 // in that case, we have to check if the working copy is versioned
261 // anyway to show the 'correct' context menu
262 if (strpath
.HasAdminDir())
264 status
= git_wc_status_normal
;
265 fetchedstatus
= status
;
272 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
276 itemStates
|= strpath
.GetAdminDirMask();
278 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
279 itemStates
&= ~ITEMIS_INGIT
;
280 if (status
== git_wc_status_ignored
)
282 itemStates
|= ITEMIS_IGNORED
;
283 // the item is ignored. Get the svn:ignored properties so we can (maybe) later
284 // offer a 'remove from ignored list' entry
285 // GitProperties props(strpath.GetContainingDirectory(), false);
286 // ignoredprops.empty();
287 // for (int p=0; p<props.GetCount(); ++p)
289 // if (props.GetItemName(p).compare(stdstring(_T("svn:ignore")))==0)
291 // std::string st = props.GetItemValue(p);
292 // ignoredprops = MultibyteToWide(st.c_str());
293 // // remove all escape chars ('\\')
294 // std::remove(ignoredprops.begin(), ignoredprops.end(), '\\');
300 if (status
== git_wc_status_normal
)
301 itemStates
|= ITEMIS_NORMAL
;
302 if (status
== git_wc_status_conflicted
)
303 itemStates
|= ITEMIS_CONFLICTED
;
304 if (status
== git_wc_status_added
)
305 itemStates
|= ITEMIS_ADDED
;
306 if (status
== git_wc_status_deleted
)
307 itemStates
|= ITEMIS_DELETED
;
310 } // for (int i = 0; i < count; ++i)
311 ItemIDList
child (GetPIDLItem (cida
, 0), &parent
);
312 if (g_ShellCache
.HasGITAdminDir(child
.toString().c_str(), FALSE
))
313 itemStates
|= ITEMIS_INVERSIONEDFOLDER
;
315 if (itemStates
== 0 && g_GitAdminDir
.IsBareRepo(child
.toString().c_str()))
316 itemStates
= ITEMIS_BAREREPO
;
318 GlobalUnlock(medium
.hGlobal
);
320 // if the item is a versioned folder, check if there's a patch file
321 // in the clipboard to be used in "Apply Patch"
322 UINT cFormatDiff
= RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
325 if (IsClipboardFormatAvailable(cFormatDiff
))
326 itemStates
|= ITEMIS_PATCHINCLIPBOARD
;
328 if (IsClipboardFormatAvailable(CF_HDROP
))
329 itemStates
|= ITEMIS_PATHINCLIPBOARD
;
333 ReleaseStgMedium ( &medium
);
334 if (medium
.pUnkForRelease
)
336 IUnknown
* relInterface
= (IUnknown
*)medium
.pUnkForRelease
;
337 relInterface
->Release();
342 // get folder background
346 ItemIDList
list(pIDFolder
);
347 folder_
= list
.toString();
348 git_wc_status_kind status
= git_wc_status_none
;
349 if (IsClipboardFormatAvailable(CF_HDROP
))
350 itemStatesFolder
|= ITEMIS_PATHINCLIPBOARD
;
353 askedpath
.SetFromWin(folder_
.c_str());
355 if (g_ShellCache
.IsContextPathAllowed(folder_
.c_str()))
357 if (folder_
.compare(statuspath
)!=0)
363 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
366 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
367 // if ((stat.status->entry)&&(stat.status->entry->lock_token))
368 // itemStatesFolder |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
369 // if ((stat.status->entry)&&(stat.status->entry->present_props))
371 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
372 // itemStatesFolder |= ITEMIS_NEEDSLOCK;
374 // if ((stat.status->entry)&&(stat.status->entry->uuid))
375 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
380 // sometimes, git_client_status() returns with an error.
381 // in that case, we have to check if the working copy is versioned
382 // anyway to show the 'correct' context menu
383 if (askedpath
.HasAdminDir())
384 status
= git_wc_status_normal
;
387 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
388 itemStatesFolder
|= askedpath
.GetAdminDirMask();
390 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
391 itemStates
&= ~ITEMIS_INGIT
;
393 if (status
== git_wc_status_normal
)
394 itemStatesFolder
|= ITEMIS_NORMAL
;
395 if (status
== git_wc_status_conflicted
)
396 itemStatesFolder
|= ITEMIS_CONFLICTED
;
397 if (status
== git_wc_status_added
)
398 itemStatesFolder
|= ITEMIS_ADDED
;
399 if (status
== git_wc_status_deleted
)
400 itemStatesFolder
|= ITEMIS_DELETED
;
405 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
410 status
= fetchedstatus
;
412 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
413 itemStatesFolder
|= askedpath
.GetAdminDirMask();
415 if (status
== git_wc_status_ignored
)
416 itemStatesFolder
|= ITEMIS_IGNORED
;
417 itemStatesFolder
|= ITEMIS_FOLDER
;
419 itemStates
|= ITEMIS_ONLYONE
;
420 if (m_State
!= FileStateDropHandler
)
421 itemStates
|= itemStatesFolder
;
426 status
= fetchedstatus
;
429 if (files_
.size() == 2)
430 itemStates
|= ITEMIS_TWO
;
431 if ((files_
.size() == 1)&&(g_ShellCache
.IsContextPathAllowed(files_
.front().c_str())))
434 itemStates
|= ITEMIS_ONLYONE
;
435 if (m_State
!= FileStateDropHandler
)
437 if (PathIsDirectory(files_
.front().c_str()))
439 folder_
= files_
.front();
440 git_wc_status_kind status
= git_wc_status_none
;
442 askedpath
.SetFromWin(folder_
.c_str());
444 if (folder_
.compare(statuspath
)!=0)
449 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
452 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
453 // if ((stat.status->entry)&&(stat.status->entry->lock_token))
454 // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
455 // if ((stat.status->entry)&&(stat.status->entry->present_props))
457 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
458 // itemStates |= ITEMIS_NEEDSLOCK;
460 // if ((stat.status->entry)&&(stat.status->entry->uuid))
461 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
466 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
471 status
= fetchedstatus
;
473 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
474 itemStates
|= askedpath
.GetAdminDirMask();
476 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
477 itemStates
&= ~ITEMIS_INGIT
;
479 if (status
== git_wc_status_ignored
)
480 itemStates
|= ITEMIS_IGNORED
;
481 itemStates
|= ITEMIS_FOLDER
;
482 if (status
== git_wc_status_added
)
483 itemStates
|= ITEMIS_ADDED
;
484 if (status
== git_wc_status_deleted
)
485 itemStates
|= ITEMIS_DELETED
;
495 void CShellExt::InsertGitMenu(BOOL istop
, HMENU menu
, UINT pos
, UINT_PTR id
, UINT stringid
, UINT icon
, UINT idCmdFirst
, GitCommands com
, UINT
/*uFlags*/)
497 TCHAR menutextbuffer
[512] = {0};
498 TCHAR verbsbuffer
[255] = {0};
499 MAKESTRING(stringid
);
503 //menu entry for the top context menu, so append an "Git " before
504 //the menu text to indicate where the entry comes from
505 _tcscpy_s(menutextbuffer
, 255, _T("Git "));
506 if (!g_ShellCache
.HasShellMenuAccelerators())
508 // remove the accelerators
509 tstring temp
= stringtablebuffer
;
510 temp
.erase(std::remove(temp
.begin(), temp
.end(), '&'), temp
.end());
511 _tcscpy_s(stringtablebuffer
, 255, temp
.c_str());
514 _tcscat_s(menutextbuffer
, 255, stringtablebuffer
);
516 // insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
517 // so we have an easy and fast way to check the current branch
518 // (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
519 if (com
== ShellMenuCommit
)
522 CTGitPath
path(folder_
.empty() ? files_
.front().c_str() : folder_
.c_str());
523 CString sProjectRoot
;
526 if (path
.GetAdminDirMask() & ITEMIS_SUBMODULE
)
529 _tcscpy_s(menutextbuffer
, 255, _T("Git "));
530 _tcscat_s(menutextbuffer
, 255, CString(MAKEINTRESOURCE(IDS_MENUCOMMITSUBMODULE
)));
533 if (path
.HasAdminDir(&sProjectRoot
) && !g_Git
.GetCurrentBranchFromFile(sProjectRoot
, sBranchName
))
535 if (sBranchName
.GetLength() == 40)
537 // if SHA1 only show 4 first bytes
539 for (int i
=0; i
<40; i
++)
540 if ( !iswxdigit(sBranchName
[i
]) )
546 sBranchName
= sBranchName
.Left(8) + _T("....");
550 if (sBranchName
.GetLength() > 64)
551 sBranchName
= sBranchName
.Left(64) + _T("...");
553 // scan to before "..."
554 LPTSTR s
= menutextbuffer
+ _tcslen(menutextbuffer
)-1;
555 if (s
> menutextbuffer
)
557 while (s
> menutextbuffer
)
572 // append branch name and end with ...
573 _tcscpy(s
, _T(" -> \"") + sBranchName
+ _T("\"..."));
577 MENUITEMINFO menuiteminfo
;
578 SecureZeroMemory(&menuiteminfo
, sizeof(menuiteminfo
));
579 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
580 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_STRING
;
581 menuiteminfo
.fType
= MFT_STRING
;
582 menuiteminfo
.dwTypeData
= menutextbuffer
;
585 menuiteminfo
.fMask
|= MIIM_BITMAP
;
586 menuiteminfo
.hbmpItem
= (SysInfo::Instance().IsVistaOrLater()) ? IconToBitmapPARGB32(icon
) : HBMMENU_CALLBACK
;
588 menuiteminfo
.wID
= (UINT
)id
;
589 InsertMenuItem(menu
, pos
, TRUE
, &menuiteminfo
);
593 //menu entry for the top context menu, so append an "Git " before
594 //the menu text to indicate where the entry comes from
595 _tcscpy_s(menutextbuffer
, 255, _T("Git "));
597 LoadString(g_hResInst
, stringid
, verbsbuffer
, sizeof(verbsbuffer
));
598 _tcscat_s(menutextbuffer
, 255, verbsbuffer
);
599 stdstring verb
= stdstring(menutextbuffer
);
600 if (verb
.find('&') != -1)
602 verb
.erase(verb
.find('&'),1);
604 myVerbsMap
[verb
] = id
- idCmdFirst
;
605 myVerbsMap
[verb
] = id
;
606 myVerbsIDMap
[id
- idCmdFirst
] = verb
;
607 myVerbsIDMap
[id
] = verb
;
608 // We store the relative and absolute diameter
609 // (drawitem callback uses absolute, others relative)
610 myIDMap
[id
- idCmdFirst
] = com
;
613 mySubMenuMap
[pos
] = com
;
616 HBITMAP
CShellExt::IconToBitmap(UINT uIcon
)
618 std::map
<UINT
, HBITMAP
>::iterator bitmap_it
= bitmaps
.lower_bound(uIcon
);
619 if (bitmap_it
!= bitmaps
.end() && bitmap_it
->first
== uIcon
)
620 return bitmap_it
->second
;
622 HICON hIcon
= (HICON
)LoadImage(g_hResInst
, MAKEINTRESOURCE(uIcon
), IMAGE_ICON
, 12, 12, LR_DEFAULTCOLOR
);
628 rect
.right
= ::GetSystemMetrics(SM_CXMENUCHECK
);
629 rect
.bottom
= ::GetSystemMetrics(SM_CYMENUCHECK
);
631 rect
.left
= rect
.top
= 0;
633 HWND desktop
= ::GetDesktopWindow();
640 HDC screen_dev
= ::GetDC(desktop
);
641 if (screen_dev
== NULL
)
647 // Create a compatible DC
648 HDC dst_hdc
= ::CreateCompatibleDC(screen_dev
);
652 ::ReleaseDC(desktop
, screen_dev
);
656 // Create a new bitmap of icon size
657 HBITMAP bmp
= ::CreateCompatibleBitmap(screen_dev
, rect
.right
, rect
.bottom
);
662 ::ReleaseDC(desktop
, screen_dev
);
666 // Select it into the compatible DC
667 HBITMAP old_dst_bmp
= (HBITMAP
)::SelectObject(dst_hdc
, bmp
);
668 if (old_dst_bmp
== NULL
)
674 // Fill the background of the compatible DC with the white color
675 // that is taken by menu routines as transparent
676 ::SetBkColor(dst_hdc
, RGB(255, 255, 255));
677 ::ExtTextOut(dst_hdc
, 0, 0, ETO_OPAQUE
, &rect
, NULL
, 0, NULL
);
679 // Draw the icon into the compatible DC
680 ::DrawIconEx(dst_hdc
, 0, 0, hIcon
, rect
.right
, rect
.bottom
, 0, NULL
, DI_NORMAL
);
683 ::SelectObject(dst_hdc
, old_dst_bmp
);
685 ::ReleaseDC(desktop
, screen_dev
);
688 bitmaps
.insert(bitmap_it
, std::make_pair(uIcon
, bmp
));
692 bool CShellExt::WriteClipboardPathsToTempFile(stdstring
& tempfile
)
695 tempfile
= stdstring();
696 //write all selected files and paths to a temporary file
697 //for TortoiseGitProc.exe to read out again.
699 DWORD pathlength
= GetTortoiseGitTempPath(0, NULL
);
700 std::unique_ptr
<TCHAR
[]> path(new TCHAR
[pathlength
+ 1]);
701 std::unique_ptr
<TCHAR
[]> tempFile(new TCHAR
[pathlength
+ 100]);
702 GetTortoiseGitTempPath(pathlength
+1, path
.get());
703 GetTempFileName(path
.get(), _T("git"), 0, tempFile
.get());
704 tempfile
= stdstring(tempFile
.get());
706 CAutoFile file
= ::CreateFile(tempFile
.get(),
711 FILE_ATTRIBUTE_TEMPORARY
,
717 if (!IsClipboardFormatAvailable(CF_HDROP
))
719 if (!OpenClipboard(NULL
))
722 stdstring sClipboardText
;
723 HGLOBAL hglb
= GetClipboardData(CF_HDROP
);
724 HDROP hDrop
= (HDROP
)GlobalLock(hglb
);
727 TCHAR szFileName
[MAX_PATH
];
728 UINT cFiles
= DragQueryFile(hDrop
, 0xFFFFFFFF, NULL
, 0);
729 for(UINT i
= 0; i
< cFiles
; ++i
)
731 DragQueryFile(hDrop
, i
, szFileName
, sizeof(szFileName
));
732 stdstring filename
= szFileName
;
733 ::WriteFile (file
, filename
.c_str(), (DWORD
)filename
.size()*sizeof(TCHAR
), &written
, 0);
734 ::WriteFile (file
, _T("\n"), 2, &written
, 0);
746 stdstring
CShellExt::WriteFileListToTempFile()
748 //write all selected files and paths to a temporary file
749 //for TortoiseGitProc.exe to read out again.
750 DWORD pathlength
= GetTortoiseGitTempPath(0, NULL
);
751 std::unique_ptr
<TCHAR
[]> path(new TCHAR
[pathlength
+ 1]);
752 std::unique_ptr
<TCHAR
[]> tempFile(new TCHAR
[pathlength
+ 100]);
753 GetTortoiseGitTempPath(pathlength
+ 1, path
.get());
754 GetTempFileName(path
.get(), _T("git"), 0, tempFile
.get());
755 stdstring retFilePath
= stdstring(tempFile
.get());
757 CAutoFile file
= ::CreateFile (tempFile
.get(),
762 FILE_ATTRIBUTE_TEMPORARY
,
771 ::WriteFile (file
, folder_
.c_str(), (DWORD
)folder_
.size()*sizeof(TCHAR
), &written
, 0);
772 ::WriteFile (file
, _T("\n"), 2, &written
, 0);
775 for (std::vector
<stdstring
>::iterator I
= files_
.begin(); I
!= files_
.end(); ++I
)
777 ::WriteFile (file
, I
->c_str(), (DWORD
)I
->size()*sizeof(TCHAR
), &written
, 0);
778 ::WriteFile (file
, _T("\n"), 2, &written
, 0);
783 STDMETHODIMP
CShellExt::QueryDropContext(UINT uFlags
, UINT idCmdFirst
, HMENU hMenu
, UINT
&indexMenu
)
785 PreserveChdir preserveChdir
;
788 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
789 return S_OK
; //we don't change the default action
791 if (files_
.empty() || folder_
.empty())
794 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
797 if (itemStatesFolder
& ITEMIS_FOLDER
) // we do not support folders atm, see issue #963
800 bool bSourceAndTargetFromSameRepository
= (uuidSource
.compare(uuidTarget
) == 0) || uuidSource
.empty() || uuidTarget
.empty();
802 //the drop handler only has eight commands, but not all are visible at the same time:
803 //if the source file(s) are under version control then those files can be moved
804 //to the new location or they can be moved with a rename,
805 //if they are unversioned then they can be added to the working copy
806 //if they are versioned, they also can be exported to an unversioned location
807 UINT idCmd
= idCmdFirst
;
810 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
811 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINGIT
)&&((itemStates
& ITEMIS_INGIT
)&&((~itemStates
) & ITEMIS_ADDED
)))
812 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVEMENU
, 0, idCmdFirst
, ShellMenuDropMove
, uFlags
);
814 // Git move and rename here
815 // 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
816 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINGIT
)&&(itemStates
& ITEMIS_INGIT
)&&(itemStates
& ITEMIS_ONLYONE
)&&((~itemStates
) & ITEMIS_ADDED
))
817 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVERENAMEMENU
, 0, idCmdFirst
, ShellMenuDropMoveRename
, uFlags
);
820 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
821 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINGIT
)&&(itemStates
& ITEMIS_INGIT
)&&((~itemStates
) & ITEMIS_ADDED
))
822 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYMENU
, 0, idCmdFirst
, ShellMenuDropCopy
, uFlags
);
824 // Git copy and rename here, source and target from same repository
825 // available if source is a single, versioned but not added item, target is versioned or target folder is added
826 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINGIT
)&&(itemStates
& ITEMIS_INGIT
)&&(itemStates
& ITEMIS_ONLYONE
)&&((~itemStates
) & ITEMIS_ADDED
))
827 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYRENAMEMENU
, 0, idCmdFirst
, ShellMenuDropCopyRename
, uFlags
);
830 // available if target is versioned and source is either unversioned or from another repository
831 if ((itemStatesFolder
& ITEMIS_FOLDERINGIT
)&&(((~itemStates
) & ITEMIS_INGIT
)||!bSourceAndTargetFromSameRepository
))
832 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYADDMENU
, 0, idCmdFirst
, ShellMenuDropCopyAdd
, uFlags
);
835 // available if source is versioned and a folder
836 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
837 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
839 // Git export all here
840 // available if source is versioned and a folder
841 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
842 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
845 // available if source is a patchfile
846 if (itemStates
& ITEMIS_PATCHFILE
)
847 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_MENUAPPLYPATCH
, 0, idCmdFirst
, ShellMenuApplyPatch
, uFlags
);
850 if (idCmd
!= idCmdFirst
)
851 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, NULL
);
855 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
858 STDMETHODIMP
CShellExt::QueryContextMenu(HMENU hMenu
,
866 return QueryContextMenu_Wrap(hMenu
, indexMenu
, idCmdFirst
, idCmdLast
, uFlags
);
868 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
874 STDMETHODIMP
CShellExt::QueryContextMenu_Wrap(HMENU hMenu
,
880 ATLTRACE("Shell :: QueryContextMenu\n");
881 PreserveChdir preserveChdir
;
883 //first check if our drop handler is called
884 //and then (if true) provide the context menu for the
886 if (m_State
== FileStateDropHandler
)
888 return QueryDropContext(uFlags
, idCmdFirst
, hMenu
, indexMenu
);
891 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
892 return S_OK
; //we don't change the default action
894 if (files_
.empty() && folder_
.empty())
897 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
904 CSIDL_COMMON_FAVORITES
,
905 CSIDL_COMMON_STARTMENU
,
906 CSIDL_COMPUTERSNEARME
,
914 CSIDL_INTERNET_CACHE
,
924 if (IsIllegalFolder(folder_
, csidlarray
))
929 // folder is empty, but maybe files are selected
931 return S_OK
; // nothing selected - we don't have a menu to show
932 // check whether a selected entry is an UID - those are namespace extensions
933 // which we can't handle
934 for (std::vector
<stdstring
>::const_iterator it
= files_
.begin(); it
!= files_
.end(); ++it
)
936 if (_tcsncmp(it
->c_str(), _T("::{"), 3)==0)
942 if (_tcsncmp(folder_
.c_str(), _T("::{"), 3) == 0)
946 if (((uFlags
& CMF_EXTENDEDVERBS
) == 0) && g_ShellCache
.HideMenusForUnversionedItems())
948 if ((itemStates
& (ITEMIS_INGIT
|ITEMIS_INVERSIONEDFOLDER
|ITEMIS_FOLDERINGIT
|ITEMIS_BAREREPO
|ITEMIS_TWO
))==0)
952 //check if our menu is requested for a subversion admin directory
953 if (g_GitAdminDir
.IsAdminDirPath(folder_
.c_str()))
956 if (uFlags
& CMF_EXTENDEDVERBS
)
957 itemStates
|= ITEMIS_EXTENDED
;
959 const BOOL bShortcut
= !!(uFlags
& CMF_VERBSONLY
);
960 if ( bShortcut
&& (files_
.size()==1))
962 // Don't show the context menu for a link if the
963 // destination is not part of a working copy.
964 // It would only show the standard menu items
965 // which are already shown for the lnk-file.
966 CString path
= files_
.front().c_str();
967 if ( !g_GitAdminDir
.HasAdminDir(path
) )
973 //check if we already added our menu entry for a folder.
974 //we check that by iterating through all menu entries and check if
975 //the dwItemData member points to our global ID string. That string is set
976 //by our shell extension when the folder menu is inserted.
977 TCHAR menubuf
[MAX_PATH
];
978 int count
= GetMenuItemCount(hMenu
);
979 for (int i
=0; i
<count
; ++i
)
982 SecureZeroMemory(&miif
, sizeof(MENUITEMINFO
));
983 miif
.cbSize
= sizeof(MENUITEMINFO
);
984 miif
.fMask
= MIIM_DATA
;
985 miif
.dwTypeData
= menubuf
;
986 miif
.cch
= _countof(menubuf
);
987 GetMenuItemInfo(hMenu
, i
, TRUE
, &miif
);
988 if (miif
.dwItemData
== (ULONG_PTR
)g_MenuIDString
)
993 UINT idCmd
= idCmdFirst
;
995 //create the sub menu
996 HMENU subMenu
= CreateMenu();
997 int indexSubMenu
= 0;
999 unsigned __int64 topmenu
= g_ShellCache
.GetMenuLayout();
1000 unsigned __int64 menumask
= g_ShellCache
.GetMenuMask();
1001 unsigned __int64 menuex
= g_ShellCache
.GetMenuExt();
1004 bool bAddSeparator
= false;
1005 bool bMenuEntryAdded
= false;
1006 bool bMenuEmpty
= true;
1007 // insert separator at start
1008 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, NULL
); idCmd
++;
1009 bool bShowIcons
= !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\ShowContextMenuIcons"), TRUE
));
1011 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1013 if (menuInfo
[menuIndex
].command
== ShellSeparator
)
1015 // we don't add a separator immediately. Because there might not be
1016 // another 'normal' menu entry after we insert a separator.
1017 // we simply set a flag here, indicating that before the next
1018 // 'normal' menu entry, a separator should be added.
1020 bAddSeparator
= true;
1024 // check the conditions whether to show the menu entry or not
1025 bool bInsertMenu
= ShouldInsertItem(menuInfo
[menuIndex
]);
1026 if (menuInfo
[menuIndex
].menuID
& menuex
)
1028 if( !(itemStates
& ITEMIS_EXTENDED
) )
1030 bInsertMenu
= false;
1034 if (menuInfo
[menuIndex
].menuID
& (~menumask
))
1038 bool bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1039 // insert a separator
1040 if ((bMenuEntryAdded
)&&(bAddSeparator
)&&(!bIsTop
))
1042 bAddSeparator
= false;
1043 bMenuEntryAdded
= false;
1044 InsertMenu(subMenu
, indexSubMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, NULL
);
1048 // handle special cases (sub menus)
1049 if ((menuInfo
[menuIndex
].command
== ShellMenuIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuUnIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuDeleteIgnoreSub
))
1051 if(InsertIgnoreSubmenus(idCmd
, idCmdFirst
, hMenu
, subMenu
, indexMenu
, indexSubMenu
, topmenu
, bShowIcons
, uFlags
))
1053 bMenuEntryAdded
= true;
1059 bool bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1061 // insert the menu entry
1062 InsertGitMenu( bIsTop
,
1063 bIsTop
? hMenu
: subMenu
,
1064 bIsTop
? indexMenu
++ : indexSubMenu
++,
1066 menuInfo
[menuIndex
].menuTextID
,
1067 bShowIcons
? menuInfo
[menuIndex
].iconID
: 0,
1069 menuInfo
[menuIndex
].command
,
1073 bMenuEntryAdded
= true;
1075 bAddSeparator
= false;
1084 //add sub menu to main context menu
1085 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1086 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1087 MAKESTRING(IDS_MENUSUBMENU
);
1088 if (!g_ShellCache
.HasShellMenuAccelerators())
1090 // remove the accelerators
1091 tstring temp
= stringtablebuffer
;
1092 temp
.erase(std::remove(temp
.begin(), temp
.end(), '&'), temp
.end());
1093 _tcscpy_s(stringtablebuffer
, temp
.c_str());
1095 MENUITEMINFO menuiteminfo
;
1096 SecureZeroMemory(&menuiteminfo
, sizeof(menuiteminfo
));
1097 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
1098 menuiteminfo
.fType
= MFT_STRING
;
1099 menuiteminfo
.dwTypeData
= stringtablebuffer
;
1101 UINT uIcon
= bShowIcons
? IDI_APP
: 0;
1102 if (!folder_
.empty())
1104 uIcon
= bShowIcons
? IDI_MENUFOLDER
: 0;
1105 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFolder
;
1106 myIDMap
[idCmd
] = ShellSubMenuFolder
;
1107 menuiteminfo
.dwItemData
= (ULONG_PTR
)g_MenuIDString
;
1109 else if (!bShortcut
&& (files_
.size()==1))
1111 uIcon
= bShowIcons
? IDI_MENUFILE
: 0;
1112 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFile
;
1113 myIDMap
[idCmd
] = ShellSubMenuFile
;
1115 else if (bShortcut
&& (files_
.size()==1))
1117 uIcon
= bShowIcons
? IDI_MENULINK
: 0;
1118 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuLink
;
1119 myIDMap
[idCmd
] = ShellSubMenuLink
;
1121 else if (!files_
.empty())
1123 uIcon
= bShowIcons
? IDI_MENUMULTIPLE
: 0;
1124 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuMultiple
;
1125 myIDMap
[idCmd
] = ShellSubMenuMultiple
;
1129 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenu
;
1130 myIDMap
[idCmd
] = ShellSubMenu
;
1132 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
1135 menuiteminfo
.fMask
|= MIIM_BITMAP
;
1136 menuiteminfo
.hbmpItem
= (SysInfo::Instance().IsVistaOrLater()) ? IconToBitmapPARGB32(uIcon
) : HBMMENU_CALLBACK
;
1138 menuiteminfo
.hSubMenu
= subMenu
;
1139 menuiteminfo
.wID
= idCmd
++;
1140 InsertMenuItem(hMenu
, indexMenu
++, TRUE
, &menuiteminfo
);
1143 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, NULL
); idCmd
++;
1147 //return number of menu items added
1148 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
1151 void CShellExt::TweakMenu(HMENU hMenu
)
1153 MENUINFO MenuInfo
= {};
1154 MenuInfo
.cbSize
= sizeof(MenuInfo
);
1155 MenuInfo
.fMask
= MIM_STYLE
| MIM_APPLYTOSUBMENUS
;
1156 MenuInfo
.dwStyle
= MNS_CHECKORBMP
;
1157 SetMenuInfo(hMenu
, &MenuInfo
);
1160 void CShellExt::AddPathCommand(tstring
& gitCmd
, LPCTSTR command
, bool bFilesAllowed
)
1163 gitCmd
+= _T(" /path:\"");
1164 if ((bFilesAllowed
) && !files_
.empty())
1165 gitCmd
+= files_
.front();
1171 void CShellExt::AddPathFileCommand(tstring
& gitCmd
, LPCTSTR command
)
1173 tstring tempfile
= WriteFileListToTempFile();
1175 gitCmd
+= _T(" /pathfile:\"");
1178 gitCmd
+= _T(" /deletepathfile");
1181 void CShellExt::AddPathFileDropCommand(tstring
& gitCmd
, LPCTSTR command
)
1183 tstring tempfile
= WriteFileListToTempFile();
1185 gitCmd
+= _T(" /pathfile:\"");
1188 gitCmd
+= _T(" /deletepathfile");
1189 gitCmd
+= _T(" /droptarget:\"");
1194 STDMETHODIMP
CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi
)
1198 return InvokeCommand_Wrap(lpcmi
);
1200 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1206 // This is called when you invoke a command on the menu:
1207 STDMETHODIMP
CShellExt::InvokeCommand_Wrap(LPCMINVOKECOMMANDINFO lpcmi
)
1209 PreserveChdir preserveChdir
;
1210 HRESULT hr
= E_INVALIDARG
;
1214 if (!files_
.empty() || !folder_
.empty())
1216 UINT_PTR idCmd
= LOWORD(lpcmi
->lpVerb
);
1218 if (HIWORD(lpcmi
->lpVerb
))
1220 stdstring verb
= stdstring(MultibyteToWide(lpcmi
->lpVerb
));
1221 std::map
<stdstring
, UINT_PTR
>::const_iterator verb_it
= myVerbsMap
.lower_bound(verb
);
1222 if (verb_it
!= myVerbsMap
.end() && verb_it
->first
== verb
)
1223 idCmd
= verb_it
->second
;
1228 // See if we have a handler interface for this id
1229 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1230 if (id_it
!= myIDMap
.end() && id_it
->first
== idCmd
)
1232 tstring tortoiseProcPath
= CPathUtils::GetAppDirectory(g_hmodThisDll
) + _T("TortoiseGitProc.exe");
1233 tstring tortoiseMergePath
= CPathUtils::GetAppDirectory(g_hmodThisDll
) + _T("TortoiseGitMerge.exe");
1235 //TortoiseProc expects a command line of the form:
1236 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1238 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1240 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1241 //* pathfile is a path to a temporary file which contains a list of file paths
1242 stdstring gitCmd
= _T(" /command:");
1244 switch (id_it
->second
)
1248 AddPathCommand(gitCmd
, L
"sync", false);
1250 case ShellMenuSubSync
:
1251 AddPathFileCommand(gitCmd
, L
"subsync");
1252 if (itemStatesFolder
& ITEMIS_SUBMODULECONTAINER
|| (itemStates
& ITEMIS_SUBMODULECONTAINER
&& itemStates
& ITEMIS_WCROOT
&& itemStates
& ITEMIS_ONLYONE
))
1254 gitCmd
+= _T(" /bkpath:\"");
1259 case ShellMenuUpdateExt
:
1260 AddPathFileCommand(gitCmd
, L
"subupdate");
1261 if (itemStatesFolder
& ITEMIS_SUBMODULECONTAINER
|| (itemStates
& ITEMIS_SUBMODULECONTAINER
&& itemStates
& ITEMIS_WCROOT
&& itemStates
& ITEMIS_ONLYONE
))
1263 gitCmd
+= _T(" /bkpath:\"");
1268 case ShellMenuCommit
:
1269 AddPathFileCommand(gitCmd
, L
"commit");
1272 AddPathFileCommand(gitCmd
, L
"add");
1274 case ShellMenuIgnore
:
1275 AddPathFileCommand(gitCmd
, L
"ignore");
1277 case ShellMenuIgnoreCaseSensitive
:
1278 AddPathFileCommand(gitCmd
, L
"ignore");
1279 gitCmd
+= _T(" /onlymask");
1281 case ShellMenuDeleteIgnore
:
1282 AddPathFileCommand(gitCmd
, L
"ignore");
1283 gitCmd
+= _T(" /delete");
1285 case ShellMenuDeleteIgnoreCaseSensitive
:
1286 AddPathFileCommand(gitCmd
, L
"ignore");
1287 gitCmd
+= _T(" /delete /onlymask");
1289 case ShellMenuUnIgnore
:
1290 AddPathFileCommand(gitCmd
, L
"unignore");
1292 case ShellMenuUnIgnoreCaseSensitive
:
1293 AddPathFileCommand(gitCmd
, L
"unignore");
1294 gitCmd
+= _T(" /onlymask");
1296 case ShellMenuRevert
:
1297 AddPathFileCommand(gitCmd
, L
"revert");
1299 case ShellMenuCleanup
:
1300 AddPathFileCommand(gitCmd
, L
"cleanup");
1302 case ShellMenuSendMail
:
1303 AddPathFileCommand(gitCmd
, L
"sendmail");
1305 case ShellMenuResolve
:
1306 AddPathFileCommand(gitCmd
, L
"resolve");
1308 case ShellMenuSwitch
:
1309 AddPathCommand(gitCmd
, L
"switch", false);
1311 case ShellMenuExport
:
1312 AddPathCommand(gitCmd
, L
"export", false);
1314 case ShellMenuAbout
:
1315 gitCmd
+= _T("about");
1317 case ShellMenuCreateRepos
:
1318 AddPathCommand(gitCmd
, L
"repocreate", false);
1320 case ShellMenuMerge
:
1321 AddPathCommand(gitCmd
, L
"merge", false);
1324 AddPathCommand(gitCmd
, L
"copy", true);
1326 case ShellMenuSettings
:
1327 AddPathCommand(gitCmd
, L
"settings", true);
1330 gitCmd
+= _T("help");
1332 case ShellMenuRename
:
1333 AddPathCommand(gitCmd
, L
"rename", true);
1335 case ShellMenuRemove
:
1336 AddPathFileCommand(gitCmd
, L
"remove");
1338 case ShellMenuRemoveKeep
:
1339 AddPathFileCommand(gitCmd
, L
"remove");
1340 gitCmd
+= _T(" /keep");
1343 gitCmd
+= _T("diff /path:\"");
1344 if (files_
.size() == 1)
1345 gitCmd
+= files_
.front();
1346 else if (files_
.size() == 2)
1348 std::vector
<stdstring
>::iterator I
= files_
.begin();
1351 gitCmd
+= _T("\" /path2:\"");
1357 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1358 gitCmd
+= _T(" /alternative");
1360 case ShellMenuPrevDiff
:
1361 AddPathCommand(gitCmd
, L
"prevdiff", true);
1362 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1363 gitCmd
+= _T(" /alternative");
1365 case ShellMenuDiffTwo
:
1366 AddPathCommand(gitCmd
, L
"diffcommits", true);
1368 case ShellMenuDropCopyAdd
:
1369 AddPathFileDropCommand(gitCmd
, L
"dropcopyadd");
1371 case ShellMenuDropCopy
:
1372 AddPathFileDropCommand(gitCmd
, L
"dropcopy");
1374 case ShellMenuDropCopyRename
:
1375 AddPathFileDropCommand(gitCmd
, L
"dropcopy");
1376 gitCmd
+= _T("\" /rename";)
1378 case ShellMenuDropMove
:
1379 AddPathFileDropCommand(gitCmd
, L
"dropmove");
1381 case ShellMenuDropMoveRename
:
1382 AddPathFileDropCommand(gitCmd
, L
"dropmove");
1383 gitCmd
+= _T("\" /rename";)
1385 case ShellMenuDropExport
:
1386 AddPathFileDropCommand(gitCmd
, L
"dropexport");
1388 case ShellMenuDropExportExtended
:
1389 AddPathFileDropCommand(gitCmd
, L
"dropexport");
1390 gitCmd
+= _T(" /extended");
1393 case ShellMenuLogSubmoduleFolder
:
1394 AddPathCommand(gitCmd
, L
"log", true);
1395 if (id_it
->second
== ShellMenuLogSubmoduleFolder
)
1396 gitCmd
+= _T(" /submodule");
1398 case ShellMenuRevisionGraph
:
1399 AddPathCommand(gitCmd
, L
"revisiongraph", true);
1401 case ShellMenuConflictEditor
:
1402 AddPathCommand(gitCmd
, L
"conflicteditor", true);
1404 case ShellMenuGitSVNRebase
:
1405 AddPathCommand(gitCmd
, L
"svnrebase", false);
1407 case ShellMenuGitSVNDCommit
:
1408 AddPathCommand(gitCmd
, L
"svndcommit", true);
1410 case ShellMenuGitSVNDFetch
:
1411 AddPathCommand(gitCmd
, L
"svnfetch", false);
1413 case ShellMenuGitSVNIgnore
:
1414 AddPathCommand(gitCmd
, L
"svnignore", false);
1416 case ShellMenuRebase
:
1417 AddPathCommand(gitCmd
, L
"rebase", false);
1419 case ShellMenuShowChanged
:
1420 if (files_
.size() > 1)
1422 AddPathFileCommand(gitCmd
, L
"repostatus");
1426 AddPathCommand(gitCmd
, L
"repostatus", true);
1429 case ShellMenuRepoBrowse
:
1430 AddPathCommand(gitCmd
, L
"repobrowser", false);
1432 case ShellMenuRefBrowse
:
1433 AddPathCommand(gitCmd
, L
"refbrowse", false);
1435 case ShellMenuRefLog
:
1436 AddPathCommand(gitCmd
, L
"reflog", false);
1438 case ShellMenuStashSave
:
1439 AddPathCommand(gitCmd
, L
"stashsave", true);
1441 case ShellMenuStashApply
:
1442 AddPathCommand(gitCmd
, L
"stashapply", false);
1444 case ShellMenuStashPop
:
1445 AddPathCommand(gitCmd
, L
"stashpop", false);
1447 case ShellMenuStashList
:
1448 AddPathCommand(gitCmd
, L
"reflog", false);
1449 gitCmd
+= _T(" /ref:refs/stash");
1451 case ShellMenuBisectStart
:
1452 AddPathCommand(gitCmd
, L
"bisect", false);
1453 gitCmd
+= _T("\" /start");
1455 case ShellMenuBisectGood
:
1456 AddPathCommand(gitCmd
, L
"bisect", false);
1457 gitCmd
+= _T("\" /good");
1459 case ShellMenuBisectBad
:
1460 AddPathCommand(gitCmd
, L
"bisect", false);
1461 gitCmd
+= _T("\" /bad");
1463 case ShellMenuBisectReset
:
1464 AddPathCommand(gitCmd
, L
"bisect", false);
1465 gitCmd
+= _T("\" /reset");
1467 case ShellMenuSubAdd
:
1468 AddPathCommand(gitCmd
, L
"subadd", false);
1470 case ShellMenuBlame
:
1471 AddPathCommand(gitCmd
, L
"blame", true);
1473 case ShellMenuApplyPatch
:
1474 if ((itemStates
& ITEMIS_PATCHINCLIPBOARD
) && ((~itemStates
) & ITEMIS_PATCHFILE
))
1476 // if there's a patch file in the clipboard, we save it
1477 // to a temporary file and tell TortoiseGitMerge to use that one
1478 UINT cFormat
= RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
1479 if ((cFormat
)&&(OpenClipboard(NULL
)))
1481 HGLOBAL hglb
= GetClipboardData(cFormat
);
1482 LPCSTR lpstr
= (LPCSTR
)GlobalLock(hglb
);
1484 DWORD len
= GetTortoiseGitTempPath(0, NULL
);
1485 std::unique_ptr
<TCHAR
[]> path(new TCHAR
[len
+ 1]);
1486 std::unique_ptr
<TCHAR
[]> tempF(new TCHAR
[len
+ 100]);
1487 GetTortoiseGitTempPath(len
+ 1, path
.get());
1488 GetTempFileName(path
.get(), TEXT("git"), 0, tempF
.get());
1489 std::wstring sTempFile
= std::wstring(tempF
.get());
1492 size_t patchlen
= strlen(lpstr
);
1493 _tfopen_s(&outFile
, sTempFile
.c_str(), _T("wb"));
1496 size_t size
= fwrite(lpstr
, sizeof(char), patchlen
, outFile
);
1497 if (size
== patchlen
)
1499 itemStates
|= ITEMIS_PATCHFILE
;
1501 files_
.push_back(sTempFile
);
1509 if (itemStates
& ITEMIS_PATCHFILE
)
1511 gitCmd
= _T(" /diff:\"");
1512 if (!files_
.empty())
1514 gitCmd
+= files_
.front();
1515 if (itemStatesFolder
& ITEMIS_FOLDERINGIT
)
1517 gitCmd
+= _T("\" /patchpath:\"");
1523 if (itemStates
& ITEMIS_INVERSIONEDFOLDER
)
1524 gitCmd
+= _T("\" /wc");
1530 gitCmd
= _T(" /patchpath:\"");
1531 if (!files_
.empty())
1532 gitCmd
+= files_
.front();
1538 myVerbsIDMap
.clear();
1540 RunCommand(tortoiseMergePath
, gitCmd
, _T("TortoiseGitMerge launch failed"));
1543 case ShellMenuClipPaste
:
1544 if (WriteClipboardPathsToTempFile(tempfile
))
1547 UINT cPrefDropFormat
= RegisterClipboardFormat(_T("Preferred DropEffect"));
1548 if (cPrefDropFormat
)
1550 if (OpenClipboard(lpcmi
->hwnd
))
1552 HGLOBAL hglb
= GetClipboardData(cPrefDropFormat
);
1555 DWORD
* effect
= (DWORD
*) GlobalLock(hglb
);
1556 if (*effect
== DROPEFFECT_MOVE
)
1565 gitCmd
+= _T("pastecopy /pathfile:\"");
1567 gitCmd
+= _T("pastemove /pathfile:\"");
1570 gitCmd
+= _T(" /deletepathfile");
1571 gitCmd
+= _T(" /droptarget:\"");
1577 case ShellMenuClone
:
1578 AddPathCommand(gitCmd
, L
"clone", false);
1581 AddPathCommand(gitCmd
, L
"pull", false);
1584 AddPathCommand(gitCmd
, L
"push", false);
1586 case ShellMenuBranch
:
1587 AddPathCommand(gitCmd
, L
"branch", false);
1590 AddPathCommand(gitCmd
, L
"tag", false);
1592 case ShellMenuFormatPatch
:
1593 AddPathCommand(gitCmd
, L
"formatpatch", false);
1595 case ShellMenuImportPatch
:
1596 AddPathFileCommand(gitCmd
, L
"importpatch");
1598 case ShellMenuFetch
:
1599 AddPathCommand(gitCmd
, L
"fetch", false);
1605 } // switch (id_it->second)
1606 gitCmd
+= _T(" /hwnd:");
1608 _stprintf_s(buf
, _T("%ld"), (LONG_PTR
)lpcmi
->hwnd
);
1611 myVerbsIDMap
.clear();
1613 RunCommand(tortoiseProcPath
, gitCmd
, _T("TortoiseProc launch failed"));
1615 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1616 } // if (files_.empty() || folder_.empty())
1621 // This is for the status bar and things like that:
1622 STDMETHODIMP
CShellExt::GetCommandString(UINT_PTR idCmd
,
1624 UINT FAR
* reserved
,
1630 return GetCommandString_Wrap(idCmd
, uFlags
, reserved
, pszName
, cchMax
);
1632 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1638 // This is for the status bar and things like that:
1639 STDMETHODIMP
CShellExt::GetCommandString_Wrap(UINT_PTR idCmd
,
1641 UINT FAR
* /*reserved*/,
1645 PreserveChdir preserveChdir
;
1646 //do we know the id?
1647 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1648 if (id_it
== myIDMap
.end() || id_it
->first
!= idCmd
)
1650 return E_INVALIDARG
; //no, we don't
1654 HRESULT hr
= E_INVALIDARG
;
1656 MAKESTRING(IDS_MENUDESCDEFAULT
);
1658 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1660 if (menuInfo
[menuIndex
].command
== (GitCommands
)id_it
->second
)
1662 MAKESTRING(menuInfo
[menuIndex
].menuDescID
);
1668 const TCHAR
* desc
= stringtablebuffer
;
1673 std::string help
= WideToMultibyte(desc
);
1674 lstrcpynA(pszName
, help
.c_str(), cchMax
);
1680 wide_string help
= desc
;
1681 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
);
1687 std::map
<UINT_PTR
, stdstring
>::const_iterator verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
1688 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
1690 std::string help
= WideToMultibyte(verb_id_it
->second
);
1691 lstrcpynA(pszName
, help
.c_str(), cchMax
);
1698 std::map
<UINT_PTR
, stdstring
>::const_iterator verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
1699 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
1701 wide_string help
= verb_id_it
->second
;
1702 ATLTRACE("verb : %ws\n", help
.c_str());
1703 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
);
1712 STDMETHODIMP
CShellExt::HandleMenuMsg(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
1716 return HandleMenuMsg_Wrap(uMsg
, wParam
, lParam
);
1718 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1724 STDMETHODIMP
CShellExt::HandleMenuMsg_Wrap(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
1727 return HandleMenuMsg2(uMsg
, wParam
, lParam
, &res
);
1730 STDMETHODIMP
CShellExt::HandleMenuMsg2(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*pResult
)
1734 return HandleMenuMsg2_Wrap(uMsg
, wParam
, lParam
, pResult
);
1736 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1742 STDMETHODIMP
CShellExt::HandleMenuMsg2_Wrap(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*pResult
)
1744 PreserveChdir preserveChdir
;
1747 if (pResult
== NULL
)
1754 case WM_MEASUREITEM
:
1756 MEASUREITEMSTRUCT
* lpmis
= (MEASUREITEMSTRUCT
*)lParam
;
1759 lpmis
->itemWidth
= 16;
1760 lpmis
->itemHeight
= 16;
1767 DRAWITEMSTRUCT
* lpdis
= (DRAWITEMSTRUCT
*)lParam
;
1768 if ((lpdis
==NULL
)||(lpdis
->CtlType
!= ODT_MENU
))
1769 return S_OK
; //not for a menu
1770 resource
= GetMenuTextFromResource((int)myIDMap
[lpdis
->itemID
]);
1771 if (resource
== NULL
)
1773 HICON hIcon
= (HICON
)LoadImage(g_hResInst
, resource
, IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
1776 DrawIconEx(lpdis
->hDC
,
1778 lpdis
->rcItem
.top
+ (lpdis
->rcItem
.bottom
- lpdis
->rcItem
.top
- 16) / 2,
1780 0, NULL
, DI_NORMAL
);
1789 if (HIWORD(wParam
) != MF_POPUP
)
1791 int nChar
= LOWORD(wParam
);
1792 if (_istascii((wint_t)nChar
) && _istupper((wint_t)nChar
))
1793 nChar
= tolower(nChar
);
1794 // we have the char the user pressed, now search that char in all our
1796 std::vector
<UINT_PTR
> accmenus
;
1797 for (std::map
<UINT_PTR
, UINT_PTR
>::iterator It
= mySubMenuMap
.begin(); It
!= mySubMenuMap
.end(); ++It
)
1799 resource
= GetMenuTextFromResource((int)mySubMenuMap
[It
->first
]);
1800 if (resource
== NULL
)
1802 szItem
= stringtablebuffer
;
1803 TCHAR
* amp
= _tcschr(szItem
, '&');
1807 int ampChar
= LOWORD(*amp
);
1808 if (_istascii((wint_t)ampChar
) && _istupper((wint_t)ampChar
))
1809 ampChar
= tolower(ampChar
);
1810 if (ampChar
== nChar
)
1812 // yep, we found a menu which has the pressed key
1813 // as an accelerator. Add that menu to the list to
1815 accmenus
.push_back(It
->first
);
1818 if (accmenus
.empty())
1820 // no menu with that accelerator key.
1821 *pResult
= MAKELONG(0, MNC_IGNORE
);
1824 if (accmenus
.size() == 1)
1826 // Only one menu with that accelerator key. We're lucky!
1827 // So just execute that menu entry.
1828 *pResult
= MAKELONG(accmenus
[0], MNC_EXECUTE
);
1831 if (accmenus
.size() > 1)
1833 // we have more than one menu item with this accelerator key!
1835 mif
.cbSize
= sizeof(MENUITEMINFO
);
1836 mif
.fMask
= MIIM_STATE
;
1837 for (std::vector
<UINT_PTR
>::iterator it
= accmenus
.begin(); it
!= accmenus
.end(); ++it
)
1839 GetMenuItemInfo((HMENU
)lParam
, (UINT
)*it
, TRUE
, &mif
);
1840 if (mif
.fState
== MFS_HILITE
)
1842 // this is the selected item, so select the next one
1844 if (it
== accmenus
.end())
1845 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
1847 *pResult
= MAKELONG(*it
, MNC_SELECT
);
1851 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
1862 LPCTSTR
CShellExt::GetMenuTextFromResource(int id
)
1865 LPCTSTR resource
= NULL
;
1866 unsigned __int64 layout
= g_ShellCache
.GetMenuLayout();
1870 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1872 if (menuInfo
[menuIndex
].command
== id
)
1874 MAKESTRING(menuInfo
[menuIndex
].menuTextID
);
1875 resource
= MAKEINTRESOURCE(menuInfo
[menuIndex
].iconID
);
1878 case ShellSubMenuMultiple
:
1879 case ShellSubMenuLink
:
1880 case ShellSubMenuFolder
:
1881 case ShellSubMenuFile
:
1886 space
= layout
& menuInfo
[menuIndex
].menuID
? 0 : 6;
1887 if (layout
& (menuInfo
[menuIndex
].menuID
))
1889 _tcscpy_s(textbuf
, 255, _T("Git "));
1890 _tcscat_s(textbuf
, 255, stringtablebuffer
);
1891 _tcscpy_s(stringtablebuffer
, 255, textbuf
);
1902 bool CShellExt::IsIllegalFolder(std::wstring folder
, int * cslidarray
)
1905 TCHAR buf
[MAX_PATH
]; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
1906 LPITEMIDLIST pidl
= NULL
;
1907 while (cslidarray
[i
])
1911 if (SHGetFolderLocation(NULL
, cslidarray
[i
-1], NULL
, 0, &pidl
)!=S_OK
)
1913 if (!SHGetPathFromIDList(pidl
, buf
))
1915 // not a file system path, definitely illegal for our use
1916 CoTaskMemFree(pidl
);
1919 CoTaskMemFree(pidl
);
1920 if (_tcslen(buf
)==0)
1922 if (_tcscmp(buf
, folder
.c_str())==0)
1928 bool CShellExt::InsertIgnoreSubmenus(UINT
&idCmd
, UINT idCmdFirst
, HMENU hMenu
, HMENU subMenu
, UINT
&indexMenu
, int &indexSubMenu
, unsigned __int64 topmenu
, bool bShowIcons
, UINT
/*uFlags*/)
1930 HMENU ignoresubmenu
= NULL
;
1931 int indexignoresub
= 0;
1932 bool bShowIgnoreMenu
= false;
1933 TCHAR maskbuf
[MAX_PATH
]; // MAX_PATH is ok, since this only holds a filename
1934 TCHAR ignorepath
[MAX_PATH
]; // MAX_PATH is ok, since this only holds a filename
1937 UINT icon
= bShowIcons
? IDI_IGNORE
: 0;
1939 std::vector
<stdstring
>::iterator I
= files_
.begin();
1940 if (_tcsrchr(I
->c_str(), '\\'))
1941 _tcscpy_s(ignorepath
, MAX_PATH
, _tcsrchr(I
->c_str(), '\\')+1);
1943 _tcscpy_s(ignorepath
, MAX_PATH
, I
->c_str());
1944 if ((itemStates
& ITEMIS_IGNORED
) && (!ignoredprops
.empty()))
1946 // check if the item name is ignored or the mask
1948 while ( (p
=ignoredprops
.find( ignorepath
,p
)) != -1 )
1950 if ( (p
==0 || ignoredprops
[p
-1]==TCHAR('\n'))
1951 && (p
+_tcslen(ignorepath
)==ignoredprops
.length() || ignoredprops
[p
+_tcslen(ignorepath
)+1]==TCHAR('\n')) )
1959 ignoresubmenu
= CreateMenu();
1960 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
1961 stdstring verb
= stdstring(ignorepath
);
1962 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
1963 myVerbsMap
[verb
] = idCmd
;
1964 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
1965 myVerbsIDMap
[idCmd
] = verb
;
1966 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnore
;
1967 myIDMap
[idCmd
++] = ShellMenuUnIgnore
;
1968 bShowIgnoreMenu
= true;
1970 _tcscpy_s(maskbuf
, MAX_PATH
, _T("*"));
1971 if (_tcsrchr(ignorepath
, '.'))
1973 _tcscat_s(maskbuf
, MAX_PATH
, _tcsrchr(ignorepath
, '.'));
1974 p
= ignoredprops
.find(maskbuf
);
1976 ((ignoredprops
.compare(maskbuf
)==0) || (ignoredprops
.find('\n', p
)==p
+_tcslen(maskbuf
)+1) || (ignoredprops
.rfind('\n', p
)==p
-1)))
1978 if (ignoresubmenu
==NULL
)
1979 ignoresubmenu
= CreateMenu();
1981 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
1982 stdstring verb
= stdstring(maskbuf
);
1983 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
1984 myVerbsMap
[verb
] = idCmd
;
1985 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
1986 myVerbsIDMap
[idCmd
] = verb
;
1987 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreCaseSensitive
;
1988 myIDMap
[idCmd
++] = ShellMenuUnIgnoreCaseSensitive
;
1989 bShowIgnoreMenu
= true;
1993 else if ((itemStates
& ITEMIS_IGNORED
) == 0)
1995 bShowIgnoreMenu
= true;
1996 ignoresubmenu
= CreateMenu();
1997 if (itemStates
& ITEMIS_ONLYONE
)
1999 if (itemStates
& ITEMIS_INGIT
)
2001 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2002 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnore
;
2003 myIDMap
[idCmd
++] = ShellMenuDeleteIgnore
;
2005 _tcscpy_s(maskbuf
, MAX_PATH
, _T("*"));
2006 if (!(itemStates
& ITEMIS_FOLDER
) && _tcsrchr(ignorepath
, '.'))
2008 _tcscat_s(maskbuf
, MAX_PATH
, _tcsrchr(ignorepath
, '.'));
2009 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2010 stdstring verb
= stdstring(maskbuf
);
2011 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2012 myVerbsMap
[verb
] = idCmd
;
2013 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2014 myVerbsIDMap
[idCmd
] = verb
;
2015 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnoreCaseSensitive
;
2016 myIDMap
[idCmd
++] = ShellMenuDeleteIgnoreCaseSensitive
;
2021 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2022 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
2023 myIDMap
[idCmd
++] = ShellMenuIgnore
;
2025 _tcscpy_s(maskbuf
, MAX_PATH
, _T("*"));
2026 if (!(itemStates
& ITEMIS_FOLDER
) && _tcsrchr(ignorepath
, '.'))
2028 _tcscat_s(maskbuf
, MAX_PATH
, _tcsrchr(ignorepath
, '.'));
2029 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2030 stdstring verb
= stdstring(maskbuf
);
2031 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2032 myVerbsMap
[verb
] = idCmd
;
2033 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2034 myVerbsIDMap
[idCmd
] = verb
;
2035 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2036 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2042 if (itemStates
& ITEMIS_INGIT
)
2044 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE
);
2045 _stprintf_s(ignorepath
, MAX_PATH
, stringtablebuffer
, files_
.size());
2046 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2047 stdstring verb
= stdstring(ignorepath
);
2048 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2049 myVerbsMap
[verb
] = idCmd
;
2050 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2051 myVerbsIDMap
[idCmd
] = verb
;
2052 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnore
;
2053 myIDMap
[idCmd
++] = ShellMenuDeleteIgnore
;
2055 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK
);
2056 _stprintf_s(ignorepath
, MAX_PATH
, stringtablebuffer
, files_
.size());
2057 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2058 verb
= stdstring(ignorepath
);
2059 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2060 myVerbsMap
[verb
] = idCmd
;
2061 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2062 myVerbsIDMap
[idCmd
] = verb
;
2063 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnoreCaseSensitive
;
2064 myIDMap
[idCmd
++] = ShellMenuDeleteIgnoreCaseSensitive
;
2068 MAKESTRING(IDS_MENUIGNOREMULTIPLE
);
2069 _stprintf_s(ignorepath
, MAX_PATH
, stringtablebuffer
, files_
.size());
2070 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2071 stdstring verb
= stdstring(ignorepath
);
2072 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2073 myVerbsMap
[verb
] = idCmd
;
2074 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2075 myVerbsIDMap
[idCmd
] = verb
;
2076 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
2077 myIDMap
[idCmd
++] = ShellMenuIgnore
;
2079 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK
);
2080 _stprintf_s(ignorepath
, MAX_PATH
, stringtablebuffer
, files_
.size());
2081 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2082 verb
= stdstring(ignorepath
);
2083 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2084 myVerbsMap
[verb
] = idCmd
;
2085 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2086 myVerbsIDMap
[idCmd
] = verb
;
2087 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2088 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2093 if (bShowIgnoreMenu
)
2095 MENUITEMINFO menuiteminfo
;
2096 SecureZeroMemory(&menuiteminfo
, sizeof(menuiteminfo
));
2097 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
2098 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
2101 menuiteminfo
.fMask
|= MIIM_BITMAP
;
2102 menuiteminfo
.hbmpItem
= (SysInfo::Instance().IsVistaOrLater()) ? IconToBitmapPARGB32(icon
) : HBMMENU_CALLBACK
;
2104 menuiteminfo
.fType
= MFT_STRING
;
2105 menuiteminfo
.hSubMenu
= ignoresubmenu
;
2106 menuiteminfo
.wID
= idCmd
;
2107 SecureZeroMemory(stringtablebuffer
, sizeof(stringtablebuffer
));
2108 if (itemStates
& ITEMIS_IGNORED
)
2109 GetMenuTextFromResource(ShellMenuUnIgnoreSub
);
2110 else if (itemStates
& ITEMIS_INGIT
)
2111 GetMenuTextFromResource(ShellMenuDeleteIgnoreSub
);
2113 GetMenuTextFromResource(ShellMenuIgnoreSub
);
2114 menuiteminfo
.dwTypeData
= stringtablebuffer
;
2115 menuiteminfo
.cch
= (UINT
)min(_tcslen(menuiteminfo
.dwTypeData
), UINT_MAX
);
2117 InsertMenuItem((topmenu
& MENUIGNORE
) ? hMenu
: subMenu
, (topmenu
& MENUIGNORE
) ? indexMenu
++ : indexSubMenu
++, TRUE
, &menuiteminfo
);
2118 if (itemStates
& ITEMIS_IGNORED
)
2120 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreSub
;
2121 myIDMap
[idCmd
++] = ShellMenuUnIgnoreSub
;
2125 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreSub
;
2126 myIDMap
[idCmd
++] = ShellMenuIgnoreSub
;
2129 return bShowIgnoreMenu
;
2132 HBITMAP
CShellExt::IconToBitmapPARGB32(UINT uIcon
)
2134 std::map
<UINT
, HBITMAP
>::iterator bitmap_it
= bitmaps
.lower_bound(uIcon
);
2135 if (bitmap_it
!= bitmaps
.end() && bitmap_it
->first
== uIcon
)
2136 return bitmap_it
->second
;
2138 HICON hIcon
= (HICON
)LoadImage(g_hResInst
, MAKEINTRESOURCE(uIcon
), IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
2142 if (pfnBeginBufferedPaint
== NULL
|| pfnEndBufferedPaint
== NULL
|| pfnGetBufferedPaintBits
== NULL
)
2146 sizIcon
.cx
= GetSystemMetrics(SM_CXSMICON
);
2147 sizIcon
.cy
= GetSystemMetrics(SM_CYSMICON
);
2150 SetRect(&rcIcon
, 0, 0, sizIcon
.cx
, sizIcon
.cy
);
2151 HBITMAP hBmp
= NULL
;
2153 HDC hdcDest
= CreateCompatibleDC(NULL
);
2156 if (SUCCEEDED(Create32BitHBITMAP(hdcDest
, &sizIcon
, NULL
, &hBmp
)))
2158 HBITMAP hbmpOld
= (HBITMAP
)SelectObject(hdcDest
, hBmp
);
2161 BLENDFUNCTION bfAlpha
= { AC_SRC_OVER
, 0, 255, AC_SRC_ALPHA
};
2162 BP_PAINTPARAMS paintParams
= {0};
2163 paintParams
.cbSize
= sizeof(paintParams
);
2164 paintParams
.dwFlags
= BPPF_ERASE
;
2165 paintParams
.pBlendFunction
= &bfAlpha
;
2168 HPAINTBUFFER hPaintBuffer
= pfnBeginBufferedPaint(hdcDest
, &rcIcon
, BPBF_DIB
, &paintParams
, &hdcBuffer
);
2171 if (DrawIconEx(hdcBuffer
, 0, 0, hIcon
, sizIcon
.cx
, sizIcon
.cy
, 0, NULL
, DI_NORMAL
))
2173 // If icon did not have an alpha channel we need to convert buffer to PARGB
2174 ConvertBufferToPARGB32(hPaintBuffer
, hdcDest
, hIcon
, sizIcon
);
2177 // This will write the buffer contents to the destination bitmap
2178 pfnEndBufferedPaint(hPaintBuffer
, TRUE
);
2181 SelectObject(hdcDest
, hbmpOld
);
2191 bitmaps
.insert(bitmap_it
, std::make_pair(uIcon
, hBmp
));
2195 HRESULT
CShellExt::Create32BitHBITMAP(HDC hdc
, const SIZE
*psize
, __deref_opt_out
void **ppvBits
, __out HBITMAP
* phBmp
)
2200 SecureZeroMemory(&bmi
, sizeof(bmi
));
2201 bmi
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
2202 bmi
.bmiHeader
.biPlanes
= 1;
2203 bmi
.bmiHeader
.biCompression
= BI_RGB
;
2205 bmi
.bmiHeader
.biWidth
= psize
->cx
;
2206 bmi
.bmiHeader
.biHeight
= psize
->cy
;
2207 bmi
.bmiHeader
.biBitCount
= 32;
2209 HDC hdcUsed
= hdc
? hdc
: GetDC(NULL
);
2212 *phBmp
= CreateDIBSection(hdcUsed
, &bmi
, DIB_RGB_COLORS
, ppvBits
, NULL
, 0);
2215 ReleaseDC(NULL
, hdcUsed
);
2218 return (NULL
== *phBmp
) ? E_OUTOFMEMORY
: S_OK
;
2221 HRESULT
CShellExt::ConvertBufferToPARGB32(HPAINTBUFFER hPaintBuffer
, HDC hdc
, HICON hicon
, SIZE
& sizIcon
)
2225 HRESULT hr
= pfnGetBufferedPaintBits(hPaintBuffer
, &prgbQuad
, &cxRow
);
2228 ARGB
*pargb
= reinterpret_cast<ARGB
*>(prgbQuad
);
2229 if (!HasAlpha(pargb
, sizIcon
, cxRow
))
2232 if (GetIconInfo(hicon
, &info
))
2236 hr
= ConvertToPARGB32(hdc
, pargb
, info
.hbmMask
, sizIcon
, cxRow
);
2239 DeleteObject(info
.hbmColor
);
2240 DeleteObject(info
.hbmMask
);
2248 bool CShellExt::HasAlpha(__in ARGB
*pargb
, SIZE
& sizImage
, int cxRow
)
2250 ULONG cxDelta
= cxRow
- sizImage
.cx
;
2251 for (ULONG y
= sizImage
.cy
; y
; --y
)
2253 for (ULONG x
= sizImage
.cx
; x
; --x
)
2255 if (*pargb
++ & 0xFF000000)
2267 HRESULT
CShellExt::ConvertToPARGB32(HDC hdc
, __inout ARGB
*pargb
, HBITMAP hbmp
, SIZE
& sizImage
, int cxRow
)
2270 SecureZeroMemory(&bmi
, sizeof(bmi
));
2271 bmi
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
2272 bmi
.bmiHeader
.biPlanes
= 1;
2273 bmi
.bmiHeader
.biCompression
= BI_RGB
;
2275 bmi
.bmiHeader
.biWidth
= sizImage
.cx
;
2276 bmi
.bmiHeader
.biHeight
= sizImage
.cy
;
2277 bmi
.bmiHeader
.biBitCount
= 32;
2279 HRESULT hr
= E_OUTOFMEMORY
;
2280 HANDLE hHeap
= GetProcessHeap();
2281 void *pvBits
= HeapAlloc(hHeap
, 0, bmi
.bmiHeader
.biWidth
* 4 * bmi
.bmiHeader
.biHeight
);
2285 if (GetDIBits(hdc
, hbmp
, 0, bmi
.bmiHeader
.biHeight
, pvBits
, &bmi
, DIB_RGB_COLORS
) == bmi
.bmiHeader
.biHeight
)
2287 ULONG cxDelta
= cxRow
- bmi
.bmiHeader
.biWidth
;
2288 ARGB
*pargbMask
= static_cast<ARGB
*>(pvBits
);
2290 for (ULONG y
= bmi
.bmiHeader
.biHeight
; y
; --y
)
2292 for (ULONG x
= bmi
.bmiHeader
.biWidth
; x
; --x
)
2296 // transparent pixel
2302 *pargb
++ |= 0xFF000000;
2312 HeapFree(hHeap
, 0, pvBits
);
2318 void CShellExt::RunCommand(const tstring
& path
, const tstring
& command
, LPCTSTR errorMessage
)
2320 if (CCreateProcessHelper::CreateProcessDetached(path
.c_str(), const_cast<TCHAR
*>(command
.c_str())))
2322 // process started - exit
2326 MessageBox(NULL
, CFormatMessageWrapper(), errorMessage
, MB_OK
| MB_ICONINFORMATION
);
2329 bool CShellExt::ShouldInsertItem(const MenuInfo
& item
) const
2331 return ShouldEnableMenu(item
.first
) || ShouldEnableMenu(item
.second
) ||
2332 ShouldEnableMenu(item
.third
) || ShouldEnableMenu(item
.fourth
);
2335 bool CShellExt::ShouldEnableMenu(const YesNoPair
& pair
) const
2337 if (pair
.yes
&& pair
.no
)
2339 if (((pair
.yes
& itemStates
) == pair
.yes
) && ((pair
.no
& (~itemStates
)) == pair
.no
))
2342 else if ((pair
.yes
) && ((pair
.yes
& itemStates
) == pair
.yes
))
2344 else if ((pair
.no
) && ((pair
.no
& (~itemStates
)) == pair
.no
))