1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2011 - TortoiseSVN
4 // Copyright (C) 2008-2011 - 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 "GitProperties.h"
26 #include "GitStatus.h"
28 #include "CreateProcessHelper.h"
29 #include "FormatMessageWrapper.h"
31 #define GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
32 #define GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
34 int g_shellidlist
=RegisterClipboardFormat(CFSTR_SHELLIDLIST
);
36 extern MenuInfo menuInfo
[];
39 STDMETHODIMP
CShellExt::Initialize(LPCITEMIDLIST pIDFolder
,
40 LPDATAOBJECT pDataObj
,
44 ATLTRACE("Shell :: Initialize\n");
45 PreserveChdir preserveChdir
;
53 git_wc_status_kind fetchedstatus
= git_wc_status_none
;
54 // get selected files/folders
58 FORMATETC fmte
= {(CLIPFORMAT
)g_shellidlist
,
59 (DVTARGETDEVICE FAR
*)NULL
,
63 HRESULT hres
= pDataObj
->GetData(&fmte
, &medium
);
65 if (SUCCEEDED(hres
) && medium
.hGlobal
)
67 if (m_State
== FileStateDropHandler
)
70 FORMATETC etc
= { CF_HDROP
, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
};
71 STGMEDIUM stg
= { TYMED_HGLOBAL
};
72 if ( FAILED( pDataObj
->GetData ( &etc
, &stg
)))
74 ReleaseStgMedium ( &medium
);
79 HDROP drop
= (HDROP
)GlobalLock(stg
.hGlobal
);
82 ReleaseStgMedium ( &stg
);
83 ReleaseStgMedium ( &medium
);
87 int count
= DragQueryFile(drop
, (UINT
)-1, NULL
, 0);
89 itemStates
|= ITEMIS_ONLYONE
;
90 for (int i
= 0; i
< count
; i
++)
92 // find the path length in chars
93 UINT len
= DragQueryFile(drop
, i
, NULL
, 0);
96 TCHAR
* szFileName
= new TCHAR
[len
+1];
97 if (0 == DragQueryFile(drop
, i
, szFileName
, len
+1))
102 stdstring str
= stdstring(szFileName
);
103 delete [] szFileName
;
104 if ((str
.empty() == false)&&(g_ShellCache
.IsContextPathAllowed(szFileName
)))
106 if (itemStates
& ITEMIS_ONLYONE
)
109 strpath
.SetFromWin(str
.c_str());
110 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE
: 0;
111 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE
: 0;
113 files_
.push_back(str
);
116 //get the Subversion status of the item
117 git_wc_status_kind status
= git_wc_status_none
;
119 askedpath
.SetFromWin(str
.c_str());
123 stat
.GetStatus(CTGitPath(str
.c_str()), false, false, true);
127 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
128 fetchedstatus
= status
;
129 //if ((stat.status->entry)&&(stat.status->entry->lock_token))
130 // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
131 if ( askedpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
133 itemStates
|= ITEMIS_FOLDER
;
134 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
135 itemStates
|= ITEMIS_FOLDERINGIT
;
137 //if ((stat.status->entry)&&(stat.status->entry->present_props))
139 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
140 // itemStates |= ITEMIS_NEEDSLOCK;
142 //if ((stat.status->entry)&&(stat.status->entry->uuid))
143 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
147 // sometimes, git_client_status() returns with an error.
148 // in that case, we have to check if the working copy is versioned
149 // anyway to show the 'correct' context menu
150 if (askedpath
.HasAdminDir())
151 status
= git_wc_status_normal
;
156 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
159 // TODO: should we really assume any sub-directory to be versioned
160 // or only if it contains versioned files
161 itemStates
|= askedpath
.GetAdminDirMask();
163 if ((status
== git_wc_status_unversioned
) || (status
== git_wc_status_ignored
) || (status
== git_wc_status_none
))
164 itemStates
&= ~ITEMIS_INGIT
;
166 if (status
== git_wc_status_ignored
)
167 itemStates
|= ITEMIS_IGNORED
;
168 if (status
== git_wc_status_normal
)
169 itemStates
|= ITEMIS_NORMAL
;
170 if (status
== git_wc_status_conflicted
)
171 itemStates
|= ITEMIS_CONFLICTED
;
172 if (status
== git_wc_status_added
)
173 itemStates
|= ITEMIS_ADDED
;
174 if (status
== git_wc_status_deleted
)
175 itemStates
|= ITEMIS_DELETED
;
178 } // for (int i = 0; i < count; i++)
179 GlobalUnlock ( drop
);
180 ReleaseStgMedium ( &stg
);
182 } // if (m_State == FileStateDropHandler)
186 //Enumerate PIDLs which the user has selected
187 CIDA
* cida
= (CIDA
*)GlobalLock(medium
.hGlobal
);
188 ItemIDList
parent( GetPIDLFolder (cida
));
190 int count
= cida
->cidl
;
191 BOOL statfetched
= FALSE
;
192 for (int i
= 0; i
< count
; ++i
)
194 ItemIDList
child (GetPIDLItem (cida
, i
), &parent
);
195 stdstring str
= child
.toString();
196 if ((str
.empty() == false)&&(g_ShellCache
.IsContextPathAllowed(str
.c_str())))
198 //check if our menu is requested for a subversion admin directory
199 if (g_GitAdminDir
.IsAdminDirPath(str
.c_str()))
202 files_
.push_back(str
);
204 strpath
.SetFromWin(str
.c_str());
205 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE
: 0;
206 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE
: 0;
209 //get the Subversion status of the item
210 git_wc_status_kind status
= git_wc_status_none
;
211 if ((g_ShellCache
.IsSimpleContext())&&(strpath
.IsDirectory()))
213 if (strpath
.HasAdminDir())
214 status
= git_wc_status_normal
;
221 if (strpath
.HasAdminDir())
222 stat
.GetStatus(strpath
, false, false, true);
226 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
227 fetchedstatus
= status
;
228 //if ((stat.status->entry)&&(stat.status->entry->lock_token))
229 // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
230 if ( strpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
232 itemStates
|= ITEMIS_FOLDER
;
233 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
234 itemStates
|= ITEMIS_FOLDERINGIT
;
236 // TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
237 if (status
== git_wc_status_conflicted
)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
238 itemStates
|= ITEMIS_CONFLICTED
;
239 //if ((stat.status->entry)&&(stat.status->entry->present_props))
241 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
242 // itemStates |= ITEMIS_NEEDSLOCK;
244 //if ((stat.status->entry)&&(stat.status->entry->uuid))
245 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
249 // sometimes, git_client_status() returns with an error.
250 // in that case, we have to check if the working copy is versioned
251 // anyway to show the 'correct' context menu
252 if (strpath
.HasAdminDir())
254 status
= git_wc_status_normal
;
255 fetchedstatus
= status
;
262 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
266 itemStates
|= strpath
.GetAdminDirMask();
268 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
269 itemStates
&= ~ITEMIS_INGIT
;
270 if (status
== git_wc_status_ignored
)
272 itemStates
|= ITEMIS_IGNORED
;
273 // the item is ignored. Get the svn:ignored properties so we can (maybe) later
274 // offer a 'remove from ignored list' entry
275 // GitProperties props(strpath.GetContainingDirectory(), false);
276 // ignoredprops.empty();
277 // for (int p=0; p<props.GetCount(); ++p)
279 // if (props.GetItemName(p).compare(stdstring(_T("svn:ignore")))==0)
281 // std::string st = props.GetItemValue(p);
282 // ignoredprops = MultibyteToWide(st.c_str());
283 // // remove all escape chars ('\\')
284 // std::remove(ignoredprops.begin(), ignoredprops.end(), '\\');
290 if (status
== git_wc_status_normal
)
291 itemStates
|= ITEMIS_NORMAL
;
292 if (status
== git_wc_status_conflicted
)
293 itemStates
|= ITEMIS_CONFLICTED
;
294 if (status
== git_wc_status_added
)
295 itemStates
|= ITEMIS_ADDED
;
296 if (status
== git_wc_status_deleted
)
297 itemStates
|= ITEMIS_DELETED
;
300 } // for (int i = 0; i < count; ++i)
301 ItemIDList
child (GetPIDLItem (cida
, 0), &parent
);
302 if (g_ShellCache
.HasGITAdminDir(child
.toString().c_str(), FALSE
))
303 itemStates
|= ITEMIS_INVERSIONEDFOLDER
;
305 if (itemStates
== 0 && g_GitAdminDir
.IsBareRepo(child
.toString().c_str()))
306 itemStates
= ITEMIS_BAREREPO
;
308 GlobalUnlock(medium
.hGlobal
);
310 // if the item is a versioned folder, check if there's a patch file
311 // in the clipboard to be used in "Apply Patch"
312 UINT cFormatDiff
= RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
315 if (IsClipboardFormatAvailable(cFormatDiff
))
316 itemStates
|= ITEMIS_PATCHINCLIPBOARD
;
318 if (IsClipboardFormatAvailable(CF_HDROP
))
319 itemStates
|= ITEMIS_PATHINCLIPBOARD
;
323 ReleaseStgMedium ( &medium
);
324 if (medium
.pUnkForRelease
)
326 IUnknown
* relInterface
= (IUnknown
*)medium
.pUnkForRelease
;
327 relInterface
->Release();
332 // get folder background
336 ItemIDList
list(pIDFolder
);
337 folder_
= list
.toString();
338 git_wc_status_kind status
= git_wc_status_none
;
339 if (IsClipboardFormatAvailable(CF_HDROP
))
340 itemStatesFolder
|= ITEMIS_PATHINCLIPBOARD
;
343 askedpath
.SetFromWin(folder_
.c_str());
345 if (g_ShellCache
.IsContextPathAllowed(folder_
.c_str()))
347 if (folder_
.compare(statuspath
)!=0)
353 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
356 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
357 // if ((stat.status->entry)&&(stat.status->entry->lock_token))
358 // itemStatesFolder |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
359 // if ((stat.status->entry)&&(stat.status->entry->present_props))
361 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
362 // itemStatesFolder |= ITEMIS_NEEDSLOCK;
364 // if ((stat.status->entry)&&(stat.status->entry->uuid))
365 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
370 // sometimes, git_client_status() returns with an error.
371 // in that case, we have to check if the working copy is versioned
372 // anyway to show the 'correct' context menu
373 if (askedpath
.HasAdminDir())
374 status
= git_wc_status_normal
;
377 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
378 itemStatesFolder
|= askedpath
.GetAdminDirMask();
380 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
381 itemStates
&= ~ITEMIS_INGIT
;
383 if (status
== git_wc_status_normal
)
384 itemStatesFolder
|= ITEMIS_NORMAL
;
385 if (status
== git_wc_status_conflicted
)
386 itemStatesFolder
|= ITEMIS_CONFLICTED
;
387 if (status
== git_wc_status_added
)
388 itemStatesFolder
|= ITEMIS_ADDED
;
389 if (status
== git_wc_status_deleted
)
390 itemStatesFolder
|= ITEMIS_DELETED
;
395 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
400 status
= fetchedstatus
;
402 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
403 itemStatesFolder
|= askedpath
.GetAdminDirMask();
405 if (status
== git_wc_status_ignored
)
406 itemStatesFolder
|= ITEMIS_IGNORED
;
407 itemStatesFolder
|= ITEMIS_FOLDER
;
408 if (files_
.size() == 0)
409 itemStates
|= ITEMIS_ONLYONE
;
410 if (m_State
!= FileStateDropHandler
)
411 itemStates
|= itemStatesFolder
;
416 status
= fetchedstatus
;
419 if (files_
.size() == 2)
420 itemStates
|= ITEMIS_TWO
;
421 if ((files_
.size() == 1)&&(g_ShellCache
.IsContextPathAllowed(files_
.front().c_str())))
424 itemStates
|= ITEMIS_ONLYONE
;
425 if (m_State
!= FileStateDropHandler
)
427 if (PathIsDirectory(files_
.front().c_str()))
429 folder_
= files_
.front();
430 git_wc_status_kind status
= git_wc_status_none
;
432 askedpath
.SetFromWin(folder_
.c_str());
434 if (folder_
.compare(statuspath
)!=0)
439 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
442 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
443 // if ((stat.status->entry)&&(stat.status->entry->lock_token))
444 // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
445 // if ((stat.status->entry)&&(stat.status->entry->present_props))
447 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
448 // itemStates |= ITEMIS_NEEDSLOCK;
450 // if ((stat.status->entry)&&(stat.status->entry->uuid))
451 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
456 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
461 status
= fetchedstatus
;
463 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
464 itemStates
|= askedpath
.GetAdminDirMask();
466 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
467 itemStates
&= ~ITEMIS_INGIT
;
469 if (status
== git_wc_status_ignored
)
470 itemStates
|= ITEMIS_IGNORED
;
471 itemStates
|= ITEMIS_FOLDER
;
472 if (status
== git_wc_status_added
)
473 itemStates
|= ITEMIS_ADDED
;
474 if (status
== git_wc_status_deleted
)
475 itemStates
|= ITEMIS_DELETED
;
485 void CShellExt::InsertGitMenu(BOOL istop
, HMENU menu
, UINT pos
, UINT_PTR id
, UINT stringid
, UINT icon
, UINT idCmdFirst
, GitCommands com
, UINT
/*uFlags*/)
487 TCHAR menutextbuffer
[512] = {0};
488 TCHAR verbsbuffer
[255] = {0};
489 MAKESTRING(stringid
);
493 //menu entry for the top context menu, so append an "Git " before
494 //the menu text to indicate where the entry comes from
495 _tcscpy_s(menutextbuffer
, 255, _T("Git "));
497 _tcscat_s(menutextbuffer
, 255, stringtablebuffer
);
499 // insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
500 // so we have an easy and fast way to check the current branch
501 // (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
502 if (com
== ShellMenuCommit
)
505 CTGitPath
path(folder_
.empty() ? files_
.front().c_str() : folder_
.c_str());
506 CString sProjectRoot
;
509 if (path
.HasAdminDir(&sProjectRoot
) && !g_Git
.GetCurrentBranchFromFile(sProjectRoot
, sBranchName
))
511 if (sBranchName
.GetLength() == 40)
513 // if SHA1 only show 4 first bytes
515 for (int i
=0; i
<40; i
++)
516 if ( !iswxdigit(sBranchName
[i
]) )
522 sBranchName
= sBranchName
.Left(8) + _T("....");
526 if (sBranchName
.GetLength() > 64)
527 sBranchName
= sBranchName
.Left(64) + _T("...");
529 // scan to before "..."
530 LPTSTR s
= menutextbuffer
+ _tcslen(menutextbuffer
)-1;
531 if (s
> menutextbuffer
)
533 while (s
> menutextbuffer
)
548 // append branch name and end with ...
549 _tcscpy(s
, _T(" -> \"") + sBranchName
+ _T("\"..."));
553 MENUITEMINFO menuiteminfo
;
554 SecureZeroMemory(&menuiteminfo
, sizeof(menuiteminfo
));
555 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
556 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_STRING
;
557 menuiteminfo
.fType
= MFT_STRING
;
558 menuiteminfo
.dwTypeData
= menutextbuffer
;
561 menuiteminfo
.fMask
|= MIIM_BITMAP
;
562 menuiteminfo
.hbmpItem
= (SysInfo::Instance().IsVistaOrLater()) ? IconToBitmapPARGB32(icon
) : HBMMENU_CALLBACK
;
564 menuiteminfo
.wID
= id
;
565 InsertMenuItem(menu
, pos
, TRUE
, &menuiteminfo
);
569 //menu entry for the top context menu, so append an "Git " before
570 //the menu text to indicate where the entry comes from
571 _tcscpy_s(menutextbuffer
, 255, _T("Git "));
573 LoadString(g_hResInst
, stringid
, verbsbuffer
, sizeof(verbsbuffer
));
574 _tcscat_s(menutextbuffer
, 255, verbsbuffer
);
575 stdstring verb
= stdstring(menutextbuffer
);
576 if (verb
.find('&') != -1)
578 verb
.erase(verb
.find('&'),1);
580 myVerbsMap
[verb
] = id
- idCmdFirst
;
581 myVerbsMap
[verb
] = id
;
582 myVerbsIDMap
[id
- idCmdFirst
] = verb
;
583 myVerbsIDMap
[id
] = verb
;
584 // We store the relative and absolute diameter
585 // (drawitem callback uses absolute, others relative)
586 myIDMap
[id
- idCmdFirst
] = com
;
589 mySubMenuMap
[pos
] = com
;
592 HBITMAP
CShellExt::IconToBitmap(UINT uIcon
)
594 std::map
<UINT
, HBITMAP
>::iterator bitmap_it
= bitmaps
.lower_bound(uIcon
);
595 if (bitmap_it
!= bitmaps
.end() && bitmap_it
->first
== uIcon
)
596 return bitmap_it
->second
;
598 HICON hIcon
= (HICON
)LoadImage(g_hResInst
, MAKEINTRESOURCE(uIcon
), IMAGE_ICON
, 12, 12, LR_DEFAULTCOLOR
);
604 rect
.right
= ::GetSystemMetrics(SM_CXMENUCHECK
);
605 rect
.bottom
= ::GetSystemMetrics(SM_CYMENUCHECK
);
607 rect
.left
= rect
.top
= 0;
609 HWND desktop
= ::GetDesktopWindow();
616 HDC screen_dev
= ::GetDC(desktop
);
617 if (screen_dev
== NULL
)
623 // Create a compatible DC
624 HDC dst_hdc
= ::CreateCompatibleDC(screen_dev
);
628 ::ReleaseDC(desktop
, screen_dev
);
632 // Create a new bitmap of icon size
633 HBITMAP bmp
= ::CreateCompatibleBitmap(screen_dev
, rect
.right
, rect
.bottom
);
638 ::ReleaseDC(desktop
, screen_dev
);
642 // Select it into the compatible DC
643 HBITMAP old_dst_bmp
= (HBITMAP
)::SelectObject(dst_hdc
, bmp
);
644 if (old_dst_bmp
== NULL
)
650 // Fill the background of the compatible DC with the white color
651 // that is taken by menu routines as transparent
652 ::SetBkColor(dst_hdc
, RGB(255, 255, 255));
653 ::ExtTextOut(dst_hdc
, 0, 0, ETO_OPAQUE
, &rect
, NULL
, 0, NULL
);
655 // Draw the icon into the compatible DC
656 ::DrawIconEx(dst_hdc
, 0, 0, hIcon
, rect
.right
, rect
.bottom
, 0, NULL
, DI_NORMAL
);
659 ::SelectObject(dst_hdc
, old_dst_bmp
);
661 ::ReleaseDC(desktop
, screen_dev
);
664 bitmaps
.insert(bitmap_it
, std::make_pair(uIcon
, bmp
));
668 bool CShellExt::WriteClipboardPathsToTempFile(stdstring
& tempfile
)
671 tempfile
= stdstring();
672 //write all selected files and paths to a temporary file
673 //for TortoiseProc.exe to read out again.
675 DWORD pathlength
= GetTempPath(0, NULL
);
676 TCHAR
* path
= new TCHAR
[pathlength
+1];
677 TCHAR
* tempFile
= new TCHAR
[pathlength
+ 100];
678 GetTempPath (pathlength
+1, path
);
679 GetTempFileName (path
, _T("git"), 0, tempFile
);
680 tempfile
= stdstring(tempFile
);
682 CAutoFile file
= ::CreateFile (tempFile
,
687 FILE_ATTRIBUTE_TEMPORARY
,
695 if (!IsClipboardFormatAvailable(CF_HDROP
))
697 if (!OpenClipboard(NULL
))
700 stdstring sClipboardText
;
701 HGLOBAL hglb
= GetClipboardData(CF_HDROP
);
702 HDROP hDrop
= (HDROP
)GlobalLock(hglb
);
705 TCHAR szFileName
[MAX_PATH
];
706 UINT cFiles
= DragQueryFile(hDrop
, 0xFFFFFFFF, NULL
, 0);
707 for(UINT i
= 0; i
< cFiles
; ++i
)
709 DragQueryFile(hDrop
, i
, szFileName
, sizeof(szFileName
));
710 stdstring filename
= szFileName
;
711 ::WriteFile (file
, filename
.c_str(), filename
.size()*sizeof(TCHAR
), &written
, 0);
712 ::WriteFile (file
, _T("\n"), 2, &written
, 0);
724 stdstring
CShellExt::WriteFileListToTempFile()
726 //write all selected files and paths to a temporary file
727 //for TortoiseProc.exe to read out again.
728 DWORD pathlength
= GetTempPath(0, NULL
);
729 TCHAR
* path
= new TCHAR
[pathlength
+1];
730 TCHAR
* tempFile
= new TCHAR
[pathlength
+ 100];
731 GetTempPath (pathlength
+1, path
);
732 GetTempFileName (path
, _T("git"), 0, tempFile
);
733 stdstring retFilePath
= stdstring(tempFile
);
735 CAutoFile file
= ::CreateFile (tempFile
,
740 FILE_ATTRIBUTE_TEMPORARY
,
751 ::WriteFile (file
, folder_
.c_str(), folder_
.size()*sizeof(TCHAR
), &written
, 0);
752 ::WriteFile (file
, _T("\n"), 2, &written
, 0);
755 for (std::vector
<stdstring
>::iterator I
= files_
.begin(); I
!= files_
.end(); ++I
)
757 ::WriteFile (file
, I
->c_str(), I
->size()*sizeof(TCHAR
), &written
, 0);
758 ::WriteFile (file
, _T("\n"), 2, &written
, 0);
763 STDMETHODIMP
CShellExt::QueryDropContext(UINT uFlags
, UINT idCmdFirst
, HMENU hMenu
, UINT
&indexMenu
)
765 PreserveChdir preserveChdir
;
768 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
769 return S_OK
; //we don't change the default action
771 if ((files_
.size() == 0)||(folder_
.size() == 0))
774 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
777 if (itemStatesFolder
& ITEMIS_FOLDER
) // we do not support folders atm, see issue #963
780 bool bSourceAndTargetFromSameRepository
= (uuidSource
.compare(uuidTarget
) == 0) || uuidSource
.empty() || uuidTarget
.empty();
782 //the drop handler only has eight commands, but not all are visible at the same time:
783 //if the source file(s) are under version control then those files can be moved
784 //to the new location or they can be moved with a rename,
785 //if they are unversioned then they can be added to the working copy
786 //if they are versioned, they also can be exported to an unversioned location
787 UINT idCmd
= idCmdFirst
;
790 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
791 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINGIT
)&&((itemStates
& ITEMIS_INGIT
)&&((~itemStates
) & ITEMIS_ADDED
)))
792 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVEMENU
, 0, idCmdFirst
, ShellMenuDropMove
, uFlags
);
794 // Git move and rename here
795 // 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
796 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINGIT
)&&(itemStates
& ITEMIS_INGIT
)&&(itemStates
& ITEMIS_ONLYONE
)&&((~itemStates
) & ITEMIS_ADDED
))
797 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVERENAMEMENU
, 0, idCmdFirst
, ShellMenuDropMoveRename
, uFlags
);
800 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
801 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINGIT
)&&(itemStates
& ITEMIS_INGIT
)&&((~itemStates
) & ITEMIS_ADDED
))
802 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYMENU
, 0, idCmdFirst
, ShellMenuDropCopy
, uFlags
);
804 // Git copy and rename here, source and target from same repository
805 // available if source is a single, versioned but not added item, target is versioned or target folder is added
806 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINGIT
)&&(itemStates
& ITEMIS_INGIT
)&&(itemStates
& ITEMIS_ONLYONE
)&&((~itemStates
) & ITEMIS_ADDED
))
807 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYRENAMEMENU
, 0, idCmdFirst
, ShellMenuDropCopyRename
, uFlags
);
810 // available if target is versioned and source is either unversioned or from another repository
811 if ((itemStatesFolder
& ITEMIS_FOLDERINGIT
)&&(((~itemStates
) & ITEMIS_INGIT
)||!bSourceAndTargetFromSameRepository
))
812 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYADDMENU
, 0, idCmdFirst
, ShellMenuDropCopyAdd
, uFlags
);
815 // available if source is versioned and a folder
816 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
817 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
819 // Git export all here
820 // available if source is versioned and a folder
821 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
822 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
825 // available if source is a patchfile
826 if (itemStates
& ITEMIS_PATCHFILE
)
827 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_MENUAPPLYPATCH
, 0, idCmdFirst
, ShellMenuApplyPatch
, uFlags
);
830 if (idCmd
!= idCmdFirst
)
831 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, NULL
);
835 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
838 STDMETHODIMP
CShellExt::QueryContextMenu(HMENU hMenu
,
844 ATLTRACE("Shell :: QueryContextMenu\n");
845 PreserveChdir preserveChdir
;
847 //first check if our drop handler is called
848 //and then (if true) provide the context menu for the
850 if (m_State
== FileStateDropHandler
)
852 return QueryDropContext(uFlags
, idCmdFirst
, hMenu
, indexMenu
);
855 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
856 return S_OK
; //we don't change the default action
858 if ((files_
.size() == 0)&&(folder_
.size() == 0))
861 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
868 CSIDL_COMMON_FAVORITES
,
869 CSIDL_COMMON_STARTMENU
,
870 CSIDL_COMPUTERSNEARME
,
878 CSIDL_INTERNET_CACHE
,
888 if (IsIllegalFolder(folder_
, csidlarray
))
893 // folder is empty, but maybe files are selected
894 if (files_
.size() == 0)
895 return S_OK
; // nothing selected - we don't have a menu to show
896 // check whether a selected entry is an UID - those are namespace extensions
897 // which we can't handle
898 for (std::vector
<stdstring
>::const_iterator it
= files_
.begin(); it
!= files_
.end(); ++it
)
900 if (_tcsncmp(it
->c_str(), _T("::{"), 3)==0)
905 if (((uFlags
& CMF_EXTENDEDVERBS
) == 0) && g_ShellCache
.HideMenusForUnversionedItems())
907 if ((itemStates
& (ITEMIS_INGIT
|ITEMIS_INVERSIONEDFOLDER
|ITEMIS_FOLDERINGIT
|ITEMIS_BAREREPO
))==0)
911 //check if our menu is requested for a subversion admin directory
912 if (g_GitAdminDir
.IsAdminDirPath(folder_
.c_str()))
915 if (uFlags
& CMF_EXTENDEDVERBS
)
916 itemStates
|= ITEMIS_EXTENDED
;
918 const BOOL bShortcut
= !!(uFlags
& CMF_VERBSONLY
);
919 if ( bShortcut
&& (files_
.size()==1))
921 // Don't show the context menu for a link if the
922 // destination is not part of a working copy.
923 // It would only show the standard menu items
924 // which are already shown for the lnk-file.
925 CString path
= files_
.front().c_str();
926 if ( !g_GitAdminDir
.HasAdminDir(path
) )
932 //check if we already added our menu entry for a folder.
933 //we check that by iterating through all menu entries and check if
934 //the dwItemData member points to our global ID string. That string is set
935 //by our shell extension when the folder menu is inserted.
936 TCHAR menubuf
[MAX_PATH
];
937 int count
= GetMenuItemCount(hMenu
);
938 for (int i
=0; i
<count
; ++i
)
941 SecureZeroMemory(&miif
, sizeof(MENUITEMINFO
));
942 miif
.cbSize
= sizeof(MENUITEMINFO
);
943 miif
.fMask
= MIIM_DATA
;
944 miif
.dwTypeData
= menubuf
;
945 miif
.cch
= _countof(menubuf
);
946 GetMenuItemInfo(hMenu
, i
, TRUE
, &miif
);
947 if (miif
.dwItemData
== (ULONG_PTR
)g_MenuIDString
)
952 UINT idCmd
= idCmdFirst
;
954 //create the sub menu
955 HMENU subMenu
= CreateMenu();
956 int indexSubMenu
= 0;
958 unsigned __int64 topmenu
= g_ShellCache
.GetMenuLayout();
959 unsigned __int64 menumask
= g_ShellCache
.GetMenuMask();
960 unsigned __int64 menuex
= g_ShellCache
.GetMenuExt();
963 bool bAddSeparator
= false;
964 bool bMenuEntryAdded
= false;
965 bool bMenuEmpty
= true;
966 // insert separator at start
967 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, NULL
); idCmd
++;
968 bool bShowIcons
= !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\ShowContextMenuIcons"), TRUE
));
971 if (itemStates
& (ITEMIS_INSVN
|ITEMIS_FOLDERINSVN
))
973 // show current branch name (as a "read-only" menu entry)
975 CTGitPath
path(folder_
.empty() ? files_
.front().c_str() : folder_
.c_str());
976 CString sProjectRoot
;
979 if (path
.HasAdminDir(&sProjectRoot
) && !g_Git
.GetCurrentBranchFromFile(sProjectRoot
, sBranchName
))
981 if (sBranchName
.GetLength() == 40)
983 // if SHA1 only show 4 first bytes
985 for (int i
=0; i
<40; i
++)
986 if ( !iswxdigit(sBranchName
[i
]) )
992 sBranchName
= sBranchName
.Left(8) + _T("....");
995 sBranchName
= _T('"') + sBranchName
+ _T('"');
997 const int icon
= IDI_COPY
;
998 const int pos
= indexMenu
++;
999 const int id
= idCmd
++;
1001 MENUITEMINFO menuiteminfo
;
1002 SecureZeroMemory(&menuiteminfo
, sizeof(menuiteminfo
));
1003 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
1004 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_STRING
| MIIM_STATE
;
1005 menuiteminfo
.fState
= MFS_DISABLED
;
1006 menuiteminfo
.fType
= MFT_STRING
;
1007 menuiteminfo
.dwTypeData
= (LPWSTR
)sBranchName
.GetString();
1010 menuiteminfo
.fMask
|= MIIM_BITMAP
;
1011 menuiteminfo
.hbmpItem
= (SysInfo::Instance().IsVistaOrLater()) ? IconToBitmapPARGB32(icon
) : HBMMENU_CALLBACK
;
1013 if (menuiteminfo
.hbmpItem
== HBMMENU_CALLBACK
)
1015 // WM_DRAWITEM uses myIDMap to get icon, we use the same icon as create branch
1016 myIDMap
[id
- idCmdFirst
] = ShellMenuBranch
;
1017 myIDMap
[id
] = ShellMenuBranch
;
1020 menuiteminfo
.wID
= id
;
1021 InsertMenuItem(hMenu
, pos
, TRUE
, &menuiteminfo
);
1026 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1028 if (menuInfo
[menuIndex
].command
== ShellSeparator
)
1030 // we don't add a separator immediately. Because there might not be
1031 // another 'normal' menu entry after we insert a separator.
1032 // we simply set a flag here, indicating that before the next
1033 // 'normal' menu entry, a separator should be added.
1035 bAddSeparator
= true;
1039 // check the conditions whether to show the menu entry or not
1040 bool bInsertMenu
= false;
1042 if (menuInfo
[menuIndex
].firstyes
&& menuInfo
[menuIndex
].firstno
)
1044 if (((menuInfo
[menuIndex
].firstyes
& itemStates
) == menuInfo
[menuIndex
].firstyes
)
1046 ((menuInfo
[menuIndex
].firstno
& (~itemStates
)) == menuInfo
[menuIndex
].firstno
))
1049 else if ((menuInfo
[menuIndex
].firstyes
)&&((menuInfo
[menuIndex
].firstyes
& itemStates
) == menuInfo
[menuIndex
].firstyes
))
1051 else if ((menuInfo
[menuIndex
].firstno
)&&((menuInfo
[menuIndex
].firstno
& (~itemStates
)) == menuInfo
[menuIndex
].firstno
))
1054 if (menuInfo
[menuIndex
].secondyes
&& menuInfo
[menuIndex
].secondno
)
1056 if (((menuInfo
[menuIndex
].secondyes
& itemStates
) == menuInfo
[menuIndex
].secondyes
)
1058 ((menuInfo
[menuIndex
].secondno
& (~itemStates
)) == menuInfo
[menuIndex
].secondno
))
1061 else if ((menuInfo
[menuIndex
].secondyes
)&&((menuInfo
[menuIndex
].secondyes
& itemStates
) == menuInfo
[menuIndex
].secondyes
))
1063 else if ((menuInfo
[menuIndex
].secondno
)&&((menuInfo
[menuIndex
].secondno
& (~itemStates
)) == menuInfo
[menuIndex
].secondno
))
1066 if (menuInfo
[menuIndex
].thirdyes
&& menuInfo
[menuIndex
].thirdno
)
1068 if (((menuInfo
[menuIndex
].thirdyes
& itemStates
) == menuInfo
[menuIndex
].thirdyes
)
1070 ((menuInfo
[menuIndex
].thirdno
& (~itemStates
)) == menuInfo
[menuIndex
].thirdno
))
1073 else if ((menuInfo
[menuIndex
].thirdyes
)&&((menuInfo
[menuIndex
].thirdyes
& itemStates
) == menuInfo
[menuIndex
].thirdyes
))
1075 else if ((menuInfo
[menuIndex
].thirdno
)&&((menuInfo
[menuIndex
].thirdno
& (~itemStates
)) == menuInfo
[menuIndex
].thirdno
))
1078 if (menuInfo
[menuIndex
].fourthyes
&& menuInfo
[menuIndex
].fourthno
)
1080 if (((menuInfo
[menuIndex
].fourthyes
& itemStates
) == menuInfo
[menuIndex
].fourthyes
)
1082 ((menuInfo
[menuIndex
].fourthno
& (~itemStates
)) == menuInfo
[menuIndex
].fourthno
))
1085 else if ((menuInfo
[menuIndex
].fourthyes
)&&((menuInfo
[menuIndex
].fourthyes
& itemStates
) == menuInfo
[menuIndex
].fourthyes
))
1087 else if ((menuInfo
[menuIndex
].fourthno
)&&((menuInfo
[menuIndex
].fourthno
& (~itemStates
)) == menuInfo
[menuIndex
].fourthno
))
1090 if (menuInfo
[menuIndex
].menuID
& menuex
)
1092 if( !(itemStates
& ITEMIS_EXTENDED
) )
1094 bInsertMenu
= false;
1098 if (menuInfo
[menuIndex
].menuID
& (~menumask
))
1102 bool bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1103 // insert a separator
1104 if ((bMenuEntryAdded
)&&(bAddSeparator
)&&(!bIsTop
))
1106 bAddSeparator
= false;
1107 bMenuEntryAdded
= false;
1108 InsertMenu(subMenu
, indexSubMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, NULL
);
1112 // handle special cases (sub menus)
1113 if ((menuInfo
[menuIndex
].command
== ShellMenuIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuUnIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuDeleteIgnoreSub
))
1115 if(InsertIgnoreSubmenus(idCmd
, idCmdFirst
, hMenu
, subMenu
, indexMenu
, indexSubMenu
, topmenu
, bShowIcons
, uFlags
))
1117 bMenuEntryAdded
= true;
1123 bool bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1125 // insert the menu entry
1126 InsertGitMenu( bIsTop
,
1127 bIsTop
? hMenu
: subMenu
,
1128 bIsTop
? indexMenu
++ : indexSubMenu
++,
1130 menuInfo
[menuIndex
].menuTextID
,
1131 bShowIcons
? menuInfo
[menuIndex
].iconID
: 0,
1133 menuInfo
[menuIndex
].command
,
1137 bMenuEntryAdded
= true;
1139 bAddSeparator
= false;
1148 //add sub menu to main context menu
1149 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1150 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1151 MAKESTRING(IDS_MENUSUBMENU
);
1152 MENUITEMINFO menuiteminfo
;
1153 SecureZeroMemory(&menuiteminfo
, sizeof(menuiteminfo
));
1154 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
1155 menuiteminfo
.fType
= MFT_STRING
;
1156 menuiteminfo
.dwTypeData
= stringtablebuffer
;
1158 UINT uIcon
= bShowIcons
? IDI_APP
: 0;
1161 uIcon
= bShowIcons
? IDI_MENUFOLDER
: 0;
1162 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFolder
;
1163 myIDMap
[idCmd
] = ShellSubMenuFolder
;
1164 menuiteminfo
.dwItemData
= (ULONG_PTR
)g_MenuIDString
;
1166 else if (!bShortcut
&& (files_
.size()==1))
1168 uIcon
= bShowIcons
? IDI_MENUFILE
: 0;
1169 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFile
;
1170 myIDMap
[idCmd
] = ShellSubMenuFile
;
1172 else if (bShortcut
&& (files_
.size()==1))
1174 uIcon
= bShowIcons
? IDI_MENULINK
: 0;
1175 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuLink
;
1176 myIDMap
[idCmd
] = ShellSubMenuLink
;
1178 else if (files_
.size() > 1)
1180 uIcon
= bShowIcons
? IDI_MENUMULTIPLE
: 0;
1181 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuMultiple
;
1182 myIDMap
[idCmd
] = ShellSubMenuMultiple
;
1186 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenu
;
1187 myIDMap
[idCmd
] = ShellSubMenu
;
1190 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
1193 menuiteminfo
.fMask
|= MIIM_BITMAP
;
1194 menuiteminfo
.hbmpItem
= (SysInfo::Instance().IsVistaOrLater()) ? IconToBitmapPARGB32(uIcon
) : HBMMENU_CALLBACK
;
1196 menuiteminfo
.hSubMenu
= subMenu
;
1197 menuiteminfo
.wID
= idCmd
++;
1198 InsertMenuItem(hMenu
, indexMenu
++, TRUE
, &menuiteminfo
);
1201 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, NULL
); idCmd
++;
1205 //return number of menu items added
1206 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
1209 void CShellExt::TweakMenu(HMENU hMenu
)
1211 MENUINFO MenuInfo
= {};
1212 MenuInfo
.cbSize
= sizeof(MenuInfo
);
1213 MenuInfo
.fMask
= MIM_STYLE
| MIM_APPLYTOSUBMENUS
;
1214 MenuInfo
.dwStyle
= MNS_CHECKORBMP
;
1215 SetMenuInfo(hMenu
, &MenuInfo
);
1218 // This is called when you invoke a command on the menu:
1219 STDMETHODIMP
CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi
)
1221 PreserveChdir preserveChdir
;
1222 HRESULT hr
= E_INVALIDARG
;
1226 std::string command
;
1230 if ((files_
.size() > 0)||(folder_
.size() > 0))
1232 UINT idCmd
= LOWORD(lpcmi
->lpVerb
);
1234 if (HIWORD(lpcmi
->lpVerb
))
1236 stdstring verb
= stdstring(MultibyteToWide(lpcmi
->lpVerb
));
1237 std::map
<stdstring
, UINT_PTR
>::const_iterator verb_it
= myVerbsMap
.lower_bound(verb
);
1238 if (verb_it
!= myVerbsMap
.end() && verb_it
->first
== verb
)
1239 idCmd
= verb_it
->second
;
1244 // See if we have a handler interface for this id
1245 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1246 if (id_it
!= myIDMap
.end() && id_it
->first
== idCmd
)
1248 CRegStdString
tortoiseProcPath(_T("Software\\TortoiseGit\\ProcPath"), _T("TortoiseProc.exe"), false, HKEY_LOCAL_MACHINE
);
1249 CRegStdString
tortoiseMergePath(_T("Software\\TortoiseGit\\TMergePath"), _T("TortoiseMerge.exe"), false, HKEY_LOCAL_MACHINE
);
1251 //TortoiseProc expects a command line of the form:
1252 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1254 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1256 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1257 //* pathfile is a path to a temporary file which contains a list of file paths
1258 stdstring gitCmd
= _T(" /command:");
1260 switch (id_it
->second
)
1264 gitCmd
+= _T("sync /path:\"");
1265 if (files_
.size() > 0)
1266 gitCmd
+= files_
.front();
1271 case ShellMenuUpdate
:
1272 tempfile
= WriteFileListToTempFile();
1273 gitCmd
+= _T("update /pathfile:\"");
1276 gitCmd
+= _T(" /deletepathfile");
1278 case ShellMenuSubSync
:
1279 tempfile
= WriteFileListToTempFile();
1280 gitCmd
+= _T("subsync /pathfile:\"");
1283 gitCmd
+= _T(" /deletepathfile");
1284 if (itemStatesFolder
& ITEMIS_SUBMODULECONTAINER
|| (itemStates
& ITEMIS_SUBMODULECONTAINER
&& itemStates
& ITEMIS_WCROOT
&& itemStates
& ITEMIS_ONLYONE
))
1286 gitCmd
+= _T(" /bkpath:\"");
1291 case ShellMenuUpdateExt
:
1292 tempfile
= WriteFileListToTempFile();
1293 gitCmd
+= _T("subupdate /pathfile:\"");
1296 gitCmd
+= _T(" /deletepathfile");
1297 if (itemStatesFolder
& ITEMIS_SUBMODULECONTAINER
|| (itemStates
& ITEMIS_SUBMODULECONTAINER
&& itemStates
& ITEMIS_WCROOT
&& itemStates
& ITEMIS_ONLYONE
))
1299 gitCmd
+= _T(" /bkpath:\"");
1304 case ShellMenuCommit
:
1305 tempfile
= WriteFileListToTempFile();
1306 gitCmd
+= _T("commit /pathfile:\"");
1309 gitCmd
+= _T(" /deletepathfile");
1312 case ShellMenuAddAsReplacement
:
1313 tempfile
= WriteFileListToTempFile();
1314 gitCmd
+= _T("add /pathfile:\"");
1317 gitCmd
+= _T(" /deletepathfile");
1319 case ShellMenuIgnore
:
1320 tempfile
= WriteFileListToTempFile();
1321 gitCmd
+= _T("ignore /pathfile:\"");
1324 gitCmd
+= _T(" /deletepathfile");
1326 case ShellMenuIgnoreCaseSensitive
:
1327 tempfile
= WriteFileListToTempFile();
1328 gitCmd
+= _T("ignore /pathfile:\"");
1331 gitCmd
+= _T(" /deletepathfile");
1332 gitCmd
+= _T(" /onlymask");
1334 case ShellMenuDeleteIgnore
:
1335 tempfile
= WriteFileListToTempFile();
1336 gitCmd
+= _T("ignore /delete /pathfile:\"");
1339 gitCmd
+= _T(" /deletepathfile");
1341 case ShellMenuDeleteIgnoreCaseSensitive
:
1342 tempfile
= WriteFileListToTempFile();
1343 gitCmd
+= _T("ignore /delete /pathfile:\"");
1346 gitCmd
+= _T(" /deletepathfile");
1347 gitCmd
+= _T(" /onlymask");
1349 case ShellMenuUnIgnore
:
1350 tempfile
= WriteFileListToTempFile();
1351 gitCmd
+= _T("unignore /pathfile:\"");
1354 gitCmd
+= _T(" /deletepathfile");
1356 case ShellMenuUnIgnoreCaseSensitive
:
1357 tempfile
= WriteFileListToTempFile();
1358 gitCmd
+= _T("unignore /pathfile:\"");
1361 gitCmd
+= _T(" /deletepathfile");
1362 gitCmd
+= _T(" /onlymask");
1364 case ShellMenuRevert
:
1365 tempfile
= WriteFileListToTempFile();
1366 gitCmd
+= _T("revert /pathfile:\"");
1369 gitCmd
+= _T(" /deletepathfile");
1371 case ShellMenuCleanup
:
1372 tempfile
= WriteFileListToTempFile();
1373 gitCmd
+= _T("cleanup /pathfile:\"");
1376 gitCmd
+= _T(" /deletepathfile");
1378 case ShellMenuSendMail
:
1379 tempfile
= WriteFileListToTempFile();
1380 gitCmd
+= _T("sendmail /pathfile:\"");
1383 gitCmd
+= _T(" /deletepathfile");
1385 case ShellMenuResolve
:
1386 tempfile
= WriteFileListToTempFile();
1387 gitCmd
+= _T("resolve /pathfile:\"");
1390 gitCmd
+= _T(" /deletepathfile");
1392 case ShellMenuSwitch
:
1393 gitCmd
+= _T("switch /path:\"");
1394 if (files_
.size() > 0)
1395 gitCmd
+= files_
.front();
1400 case ShellMenuExport
:
1401 gitCmd
+= _T("export /path:\"");
1402 if (files_
.size() > 0)
1403 gitCmd
+= files_
.front();
1408 case ShellMenuAbout
:
1409 gitCmd
+= _T("about");
1411 case ShellMenuCreateRepos
:
1412 gitCmd
+= _T("repocreate /path:\"");
1413 if (files_
.size() > 0)
1414 gitCmd
+= files_
.front();
1419 case ShellMenuMerge
:
1420 gitCmd
+= _T("merge /path:\"");
1421 if (files_
.size() > 0)
1422 gitCmd
+= files_
.front();
1428 gitCmd
+= _T("copy /path:\"");
1429 if (files_
.size() > 0)
1430 gitCmd
+= files_
.front();
1435 case ShellMenuSettings
:
1436 gitCmd
+= _T("settings /path:\"");
1437 if (files_
.size() > 0)
1438 gitCmd
+= files_
.front();
1444 gitCmd
+= _T("help");
1446 case ShellMenuRename
:
1447 gitCmd
+= _T("rename /path:\"");
1448 if (files_
.size() > 0)
1449 gitCmd
+= files_
.front();
1454 case ShellMenuRemove
:
1455 tempfile
= WriteFileListToTempFile();
1456 gitCmd
+= _T("remove /pathfile:\"");
1459 gitCmd
+= _T(" /deletepathfile");
1461 case ShellMenuRemoveKeep
:
1462 tempfile
= WriteFileListToTempFile();
1463 gitCmd
+= _T("remove /pathfile:\"");
1466 gitCmd
+= _T(" /deletepathfile");
1467 gitCmd
+= _T(" /keep");
1470 gitCmd
+= _T("diff /path:\"");
1471 if (files_
.size() == 1)
1472 gitCmd
+= files_
.front();
1473 else if (files_
.size() == 2)
1475 std::vector
<stdstring
>::iterator I
= files_
.begin();
1478 gitCmd
+= _T("\" /path2:\"");
1484 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1485 gitCmd
+= _T(" /alternative");
1487 case ShellMenuPrevDiff
:
1488 gitCmd
+= _T("prevdiff /path:\"");
1489 if (files_
.size() == 1)
1490 gitCmd
+= files_
.front();
1494 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1495 gitCmd
+= _T(" /alternative");
1497 case ShellMenuDiffTwo
:
1498 gitCmd
+= _T("diffcommits /path:\"");
1499 if (files_
.size() == 1)
1500 gitCmd
+= files_
.front();
1505 case ShellMenuDropCopyAdd
:
1506 tempfile
= WriteFileListToTempFile();
1507 gitCmd
+= _T("dropcopyadd /pathfile:\"");
1510 gitCmd
+= _T(" /deletepathfile");
1511 gitCmd
+= _T(" /droptarget:\"");
1515 case ShellMenuDropCopy
:
1516 tempfile
= WriteFileListToTempFile();
1517 gitCmd
+= _T("dropcopy /pathfile:\"");
1520 gitCmd
+= _T(" /deletepathfile");
1521 gitCmd
+= _T(" /droptarget:\"");
1525 case ShellMenuDropCopyRename
:
1526 tempfile
= WriteFileListToTempFile();
1527 gitCmd
+= _T("dropcopy /pathfile:\"");
1530 gitCmd
+= _T(" /deletepathfile");
1531 gitCmd
+= _T(" /droptarget:\"");
1533 gitCmd
+= _T("\" /rename";)
1535 case ShellMenuDropMove
:
1536 tempfile
= WriteFileListToTempFile();
1537 gitCmd
+= _T("dropmove /pathfile:\"");
1540 gitCmd
+= _T(" /deletepathfile");
1541 gitCmd
+= _T(" /droptarget:\"");
1545 case ShellMenuDropMoveRename
:
1546 tempfile
= WriteFileListToTempFile();
1547 gitCmd
+= _T("dropmove /pathfile:\"");
1550 gitCmd
+= _T(" /deletepathfile");
1551 gitCmd
+= _T(" /droptarget:\"");
1553 gitCmd
+= _T("\" /rename";)
1555 case ShellMenuDropExport
:
1556 tempfile
= WriteFileListToTempFile();
1557 gitCmd
+= _T("dropexport /pathfile:\"");
1560 gitCmd
+= _T(" /deletepathfile");
1561 gitCmd
+= _T(" /droptarget:\"");
1565 case ShellMenuDropExportExtended
:
1566 tempfile
= WriteFileListToTempFile();
1567 gitCmd
+= _T("dropexport /pathfile:\"");
1570 gitCmd
+= _T(" /deletepathfile");
1571 gitCmd
+= _T(" /droptarget:\"");
1574 gitCmd
+= _T(" /extended");
1577 gitCmd
+= _T("log /path:\"");
1578 if (files_
.size() > 0)
1579 gitCmd
+= files_
.front();
1584 case ShellMenuConflictEditor
:
1585 gitCmd
+= _T("conflicteditor /path:\"");
1586 if (files_
.size() > 0)
1587 gitCmd
+= files_
.front();
1592 case ShellMenuGitSVNRebase
:
1593 gitCmd
+= _T("svnrebase /path:\"");
1594 if (files_
.size() > 0)
1595 gitCmd
+= files_
.front();
1600 case ShellMenuGitSVNDCommit
:
1601 gitCmd
+= _T("svndcommit /path:\"");
1602 if (files_
.size() > 0)
1603 gitCmd
+= files_
.front();
1608 case ShellMenuGitSVNDFetch
:
1609 gitCmd
+= _T("svnfetch /path:\"");
1610 if (files_
.size() > 0)
1611 gitCmd
+= files_
.front();
1616 case ShellMenuGitSVNIgnore
:
1617 gitCmd
+= _T("svnignore /path:\"");
1618 if (files_
.size() > 0)
1619 gitCmd
+= files_
.front();
1624 case ShellMenuRebase
:
1625 gitCmd
+= _T("rebase /path:\"");
1626 if (files_
.size() > 0)
1627 gitCmd
+= files_
.front();
1632 case ShellMenuShowChanged
:
1633 if (files_
.size() > 1)
1635 tempfile
= WriteFileListToTempFile();
1636 gitCmd
+= _T("repostatus /pathfile:\"");
1639 gitCmd
+= _T(" /deletepathfile");
1643 gitCmd
+= _T("repostatus /path:\"");
1644 if (files_
.size() > 0)
1645 gitCmd
+= files_
.front();
1651 case ShellMenuRefBrowse
:
1652 gitCmd
+= _T("refbrowse /path:\"");
1653 if (files_
.size() > 0)
1654 gitCmd
+= files_
.front();
1659 case ShellMenuRefLog
:
1660 gitCmd
+= _T("reflog /path:\"");
1661 if (files_
.size() > 0)
1662 gitCmd
+= files_
.front();
1668 case ShellMenuStashSave
:
1669 gitCmd
+= _T("stashsave /path:\"");
1670 if (files_
.size() > 0)
1671 gitCmd
+= files_
.front();
1677 case ShellMenuStashApply
:
1678 gitCmd
+= _T("stashapply /path:\"");
1679 if (files_
.size() > 0)
1680 gitCmd
+= files_
.front();
1686 case ShellMenuStashPop
:
1687 gitCmd
+= _T("stashpop /path:\"");
1688 if (files_
.size() > 0)
1689 gitCmd
+= files_
.front();
1696 case ShellMenuStashList
:
1697 gitCmd
+= _T("reflog /path:\"");
1698 if (files_
.size() > 0)
1699 gitCmd
+= files_
.front();
1702 gitCmd
+= _T("\" /ref:refs/stash");
1705 case ShellMenuBisectStart
:
1706 gitCmd
+= _T("bisect /path:\"");
1707 if (files_
.size() > 0)
1708 gitCmd
+= files_
.front();
1711 gitCmd
+= _T("\" /start");
1714 case ShellMenuBisectGood
:
1715 gitCmd
+= _T("bisect /path:\"");
1716 if (files_
.size() > 0)
1717 gitCmd
+= files_
.front();
1720 gitCmd
+= _T("\" /good");
1724 case ShellMenuBisectBad
:
1725 gitCmd
+= _T("bisect /path:\"");
1726 if (files_
.size() > 0)
1727 gitCmd
+= files_
.front();
1730 gitCmd
+= _T("\" /bad");
1733 case ShellMenuBisectReset
:
1734 gitCmd
+= _T("bisect /path:\"");
1735 if (files_
.size() > 0)
1736 gitCmd
+= files_
.front();
1739 gitCmd
+= _T("\" /reset");
1742 case ShellMenuSubAdd
:
1743 gitCmd
+= _T("subadd /path:\"");
1744 if (files_
.size() > 0)
1745 gitCmd
+= files_
.front();
1751 case ShellMenuBlame
:
1752 gitCmd
+= _T("blame /path:\"");
1753 if (files_
.size() > 0)
1754 gitCmd
+= files_
.front();
1759 case ShellMenuApplyPatch
:
1760 if ((itemStates
& ITEMIS_PATCHINCLIPBOARD
) && ((~itemStates
) & ITEMIS_PATCHFILE
))
1762 // if there's a patch file in the clipboard, we save it
1763 // to a temporary file and tell TortoiseMerge to use that one
1764 UINT cFormat
= RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
1765 if ((cFormat
)&&(OpenClipboard(NULL
)))
1767 HGLOBAL hglb
= GetClipboardData(cFormat
);
1768 LPCSTR lpstr
= (LPCSTR
)GlobalLock(hglb
);
1770 DWORD len
= GetTempPath(0, NULL
);
1771 TCHAR
* path
= new TCHAR
[len
+1];
1772 TCHAR
* tempF
= new TCHAR
[len
+100];
1773 GetTempPath (len
+1, path
);
1774 GetTempFileName (path
, TEXT("git"), 0, tempF
);
1775 std::wstring sTempFile
= std::wstring(tempF
);
1780 size_t patchlen
= strlen(lpstr
);
1781 _tfopen_s(&outFile
, sTempFile
.c_str(), _T("wb"));
1784 size_t size
= fwrite(lpstr
, sizeof(char), patchlen
, outFile
);
1785 if (size
== patchlen
)
1787 itemStates
|= ITEMIS_PATCHFILE
;
1789 files_
.push_back(sTempFile
);
1797 if (itemStates
& ITEMIS_PATCHFILE
)
1799 gitCmd
= _T(" /diff:\"");
1800 if (files_
.size() > 0)
1802 gitCmd
+= files_
.front();
1803 if (itemStatesFolder
& ITEMIS_FOLDERINGIT
)
1805 gitCmd
+= _T("\" /patchpath:\"");
1811 if (itemStates
& ITEMIS_INVERSIONEDFOLDER
)
1812 gitCmd
+= _T("\" /wc");
1818 gitCmd
= _T(" /patchpath:\"");
1819 if (files_
.size() > 0)
1820 gitCmd
+= files_
.front();
1826 myVerbsIDMap
.clear();
1828 RunCommand(tortoiseMergePath
, gitCmd
, _T("TortoiseMerge launch failed"));
1831 case ShellMenuProperties
:
1832 tempfile
= WriteFileListToTempFile();
1833 gitCmd
+= _T("properties /pathfile:\"");
1836 gitCmd
+= _T(" /deletepathfile");
1838 case ShellMenuClipPaste
:
1839 if (WriteClipboardPathsToTempFile(tempfile
))
1842 UINT cPrefDropFormat
= RegisterClipboardFormat(_T("Preferred DropEffect"));
1843 if (cPrefDropFormat
)
1845 if (OpenClipboard(lpcmi
->hwnd
))
1847 HGLOBAL hglb
= GetClipboardData(cPrefDropFormat
);
1850 DWORD
* effect
= (DWORD
*) GlobalLock(hglb
);
1851 if (*effect
== DROPEFFECT_MOVE
)
1860 gitCmd
+= _T("pastecopy /pathfile:\"");
1862 gitCmd
+= _T("pastemove /pathfile:\"");
1865 gitCmd
+= _T(" /deletepathfile");
1866 gitCmd
+= _T(" /droptarget:\"");
1872 case ShellMenuClone
:
1873 gitCmd
+= _T("clone /path:\"");
1874 if (files_
.size() > 0)
1875 gitCmd
+= files_
.front();
1881 gitCmd
+= _T("pull /path:\"");
1882 if (files_
.size() > 0)
1883 gitCmd
+= files_
.front();
1889 gitCmd
+= _T("push /path:\"");
1890 if (files_
.size() > 0)
1891 gitCmd
+= files_
.front();
1896 case ShellMenuBranch
:
1897 gitCmd
+= _T("branch /path:\"");
1898 if (files_
.size() > 0)
1899 gitCmd
+= files_
.front();
1906 gitCmd
+= _T("tag /path:\"");
1907 if (files_
.size() > 0)
1908 gitCmd
+= files_
.front();
1914 case ShellMenuFormatPatch
:
1915 gitCmd
+= _T("formatpatch /path:\"");
1916 if (files_
.size() > 0)
1917 gitCmd
+= files_
.front();
1923 case ShellMenuImportPatch
:
1924 tempfile
= WriteFileListToTempFile();
1925 gitCmd
+= _T("importpatch /pathfile:\"");
1928 gitCmd
+= _T(" /deletepathfile");
1931 case ShellMenuCherryPick
:
1932 gitCmd
+= _T("cherrypick /path:\"");
1933 if (files_
.size() > 0)
1934 gitCmd
+= files_
.front();
1939 case ShellMenuFetch
:
1940 gitCmd
+= _T("fetch /path:\"");
1941 if (files_
.size() > 0)
1942 gitCmd
+= files_
.front();
1951 } // switch (id_it->second)
1952 gitCmd
+= _T(" /hwnd:");
1954 _stprintf_s(buf
, 30, _T("%d"), lpcmi
->hwnd
);
1957 myVerbsIDMap
.clear();
1959 RunCommand(tortoiseProcPath
, gitCmd
, _T("TortoiseProc launch failed"));
1961 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1962 } // if ((files_.size() > 0)||(folder_.size() > 0))
1967 // This is for the status bar and things like that:
1968 STDMETHODIMP
CShellExt::GetCommandString(UINT_PTR idCmd
,
1970 UINT FAR
* /*reserved*/,
1974 PreserveChdir preserveChdir
;
1975 //do we know the id?
1976 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1977 if (id_it
== myIDMap
.end() || id_it
->first
!= idCmd
)
1979 return E_INVALIDARG
; //no, we don't
1983 HRESULT hr
= E_INVALIDARG
;
1985 MAKESTRING(IDS_MENUDESCDEFAULT
);
1987 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1989 if (menuInfo
[menuIndex
].command
== (GitCommands
)id_it
->second
)
1991 MAKESTRING(menuInfo
[menuIndex
].menuDescID
);
1997 const TCHAR
* desc
= stringtablebuffer
;
2002 std::string help
= WideToMultibyte(desc
);
2003 lstrcpynA(pszName
, help
.c_str(), cchMax
);
2009 wide_string help
= desc
;
2010 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
);
2016 std::map
<UINT_PTR
, stdstring
>::const_iterator verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
2017 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
2019 std::string help
= WideToMultibyte(verb_id_it
->second
);
2020 lstrcpynA(pszName
, help
.c_str(), cchMax
);
2027 std::map
<UINT_PTR
, stdstring
>::const_iterator verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
2028 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
2030 wide_string help
= verb_id_it
->second
;
2031 ATLTRACE("verb : %ws\n", help
.c_str());
2032 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
);
2041 STDMETHODIMP
CShellExt::HandleMenuMsg(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
2044 return HandleMenuMsg2(uMsg
, wParam
, lParam
, &res
);
2047 STDMETHODIMP
CShellExt::HandleMenuMsg2(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*pResult
)
2049 PreserveChdir preserveChdir
;
2052 if (pResult
== NULL
)
2059 case WM_MEASUREITEM
:
2061 MEASUREITEMSTRUCT
* lpmis
= (MEASUREITEMSTRUCT
*)lParam
;
2062 if (lpmis
==NULL
||lpmis
->CtlType
!=ODT_MENU
)
2064 lpmis
->itemWidth
= 16;
2065 lpmis
->itemHeight
= 16;
2072 DRAWITEMSTRUCT
* lpdis
= (DRAWITEMSTRUCT
*)lParam
;
2073 if ((lpdis
==NULL
)||(lpdis
->CtlType
!= ODT_MENU
))
2074 return S_OK
; //not for a menu
2075 resource
= GetMenuTextFromResource(myIDMap
[lpdis
->itemID
]);
2076 if (resource
== NULL
)
2078 HICON hIcon
= (HICON
)LoadImage(g_hResInst
, resource
, IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
2081 DrawIconEx(lpdis
->hDC
,
2083 lpdis
->rcItem
.top
+ (lpdis
->rcItem
.bottom
- lpdis
->rcItem
.top
- 16) / 2,
2085 0, NULL
, DI_NORMAL
);
2094 if (HIWORD(wParam
) != MF_POPUP
)
2096 int nChar
= LOWORD(wParam
);
2097 if (_istascii((wint_t)nChar
) && _istupper((wint_t)nChar
))
2098 nChar
= tolower(nChar
);
2099 // we have the char the user pressed, now search that char in all our
2101 std::vector
<int> accmenus
;
2102 for (std::map
<UINT_PTR
, UINT_PTR
>::iterator It
= mySubMenuMap
.begin(); It
!= mySubMenuMap
.end(); ++It
)
2104 resource
= GetMenuTextFromResource(mySubMenuMap
[It
->first
]);
2105 if (resource
== NULL
)
2107 szItem
= stringtablebuffer
;
2108 TCHAR
* amp
= _tcschr(szItem
, '&');
2112 int ampChar
= LOWORD(*amp
);
2113 if (_istascii((wint_t)ampChar
) && _istupper((wint_t)ampChar
))
2114 ampChar
= tolower(ampChar
);
2115 if (ampChar
== nChar
)
2117 // yep, we found a menu which has the pressed key
2118 // as an accelerator. Add that menu to the list to
2120 accmenus
.push_back(It
->first
);
2123 if (accmenus
.size() == 0)
2125 // no menu with that accelerator key.
2126 *pResult
= MAKELONG(0, MNC_IGNORE
);
2129 if (accmenus
.size() == 1)
2131 // Only one menu with that accelerator key. We're lucky!
2132 // So just execute that menu entry.
2133 *pResult
= MAKELONG(accmenus
[0], MNC_EXECUTE
);
2136 if (accmenus
.size() > 1)
2138 // we have more than one menu item with this accelerator key!
2140 mif
.cbSize
= sizeof(MENUITEMINFO
);
2141 mif
.fMask
= MIIM_STATE
;
2142 for (std::vector
<int>::iterator it
= accmenus
.begin(); it
!= accmenus
.end(); ++it
)
2144 GetMenuItemInfo((HMENU
)lParam
, *it
, TRUE
, &mif
);
2145 if (mif
.fState
== MFS_HILITE
)
2147 // this is the selected item, so select the next one
2149 if (it
== accmenus
.end())
2150 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
2152 *pResult
= MAKELONG(*it
, MNC_SELECT
);
2156 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
2167 LPCTSTR
CShellExt::GetMenuTextFromResource(int id
)
2170 LPCTSTR resource
= NULL
;
2171 unsigned __int64 layout
= g_ShellCache
.GetMenuLayout();
2175 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
2177 if (menuInfo
[menuIndex
].command
== id
)
2179 MAKESTRING(menuInfo
[menuIndex
].menuTextID
);
2180 resource
= MAKEINTRESOURCE(menuInfo
[menuIndex
].iconID
);
2183 case ShellSubMenuMultiple
:
2184 case ShellSubMenuLink
:
2185 case ShellSubMenuFolder
:
2186 case ShellSubMenuFile
:
2191 space
= layout
& menuInfo
[menuIndex
].menuID
? 0 : 6;
2192 if (layout
& (menuInfo
[menuIndex
].menuID
))
2194 _tcscpy_s(textbuf
, 255, _T("Git "));
2195 _tcscat_s(textbuf
, 255, stringtablebuffer
);
2196 _tcscpy_s(stringtablebuffer
, 255, textbuf
);
2207 bool CShellExt::IsIllegalFolder(std::wstring folder
, int * cslidarray
)
2210 TCHAR buf
[MAX_PATH
]; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
2211 LPITEMIDLIST pidl
= NULL
;
2212 while (cslidarray
[i
])
2216 if (SHGetFolderLocation(NULL
, cslidarray
[i
-1], NULL
, 0, &pidl
)!=S_OK
)
2218 if (!SHGetPathFromIDList(pidl
, buf
))
2220 // not a file system path, definitely illegal for our use
2221 CoTaskMemFree(pidl
);
2224 CoTaskMemFree(pidl
);
2225 if (_tcslen(buf
)==0)
2227 if (_tcscmp(buf
, folder
.c_str())==0)
2233 bool CShellExt::InsertIgnoreSubmenus(UINT
&idCmd
, UINT idCmdFirst
, HMENU hMenu
, HMENU subMenu
, UINT
&indexMenu
, int &indexSubMenu
, unsigned __int64 topmenu
, bool bShowIcons
, UINT uFlags
)
2235 HMENU ignoresubmenu
= NULL
;
2236 int indexignoresub
= 0;
2237 bool bShowIgnoreMenu
= false;
2238 TCHAR maskbuf
[MAX_PATH
]; // MAX_PATH is ok, since this only holds a filename
2239 TCHAR ignorepath
[MAX_PATH
]; // MAX_PATH is ok, since this only holds a filename
2240 if (files_
.size() == 0)
2242 UINT icon
= bShowIcons
? IDI_IGNORE
: 0;
2244 std::vector
<stdstring
>::iterator I
= files_
.begin();
2245 if (_tcsrchr(I
->c_str(), '\\'))
2246 _tcscpy_s(ignorepath
, MAX_PATH
, _tcsrchr(I
->c_str(), '\\')+1);
2248 _tcscpy_s(ignorepath
, MAX_PATH
, I
->c_str());
2249 if ((itemStates
& ITEMIS_IGNORED
)&&(ignoredprops
.size() > 0))
2251 // check if the item name is ignored or the mask
2253 while ( (p
=ignoredprops
.find( ignorepath
,p
)) != -1 )
2255 if ( (p
==0 || ignoredprops
[p
-1]==TCHAR('\n'))
2256 && (p
+_tcslen(ignorepath
)==ignoredprops
.length() || ignoredprops
[p
+_tcslen(ignorepath
)+1]==TCHAR('\n')) )
2264 ignoresubmenu
= CreateMenu();
2265 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2266 stdstring verb
= stdstring(ignorepath
);
2267 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2268 myVerbsMap
[verb
] = idCmd
;
2269 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2270 myVerbsIDMap
[idCmd
] = verb
;
2271 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnore
;
2272 myIDMap
[idCmd
++] = ShellMenuUnIgnore
;
2273 bShowIgnoreMenu
= true;
2275 _tcscpy_s(maskbuf
, MAX_PATH
, _T("*"));
2276 if (_tcsrchr(ignorepath
, '.'))
2278 _tcscat_s(maskbuf
, MAX_PATH
, _tcsrchr(ignorepath
, '.'));
2279 p
= ignoredprops
.find(maskbuf
);
2281 ((ignoredprops
.compare(maskbuf
)==0) || (ignoredprops
.find('\n', p
)==p
+_tcslen(maskbuf
)+1) || (ignoredprops
.rfind('\n', p
)==p
-1)))
2283 if (ignoresubmenu
==NULL
)
2284 ignoresubmenu
= CreateMenu();
2286 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2287 stdstring verb
= stdstring(maskbuf
);
2288 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2289 myVerbsMap
[verb
] = idCmd
;
2290 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2291 myVerbsIDMap
[idCmd
] = verb
;
2292 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreCaseSensitive
;
2293 myIDMap
[idCmd
++] = ShellMenuUnIgnoreCaseSensitive
;
2294 bShowIgnoreMenu
= true;
2298 else if ((itemStates
& ITEMIS_IGNORED
) == 0)
2300 bShowIgnoreMenu
= true;
2301 ignoresubmenu
= CreateMenu();
2302 if (itemStates
& ITEMIS_ONLYONE
)
2304 if (itemStates
& ITEMIS_INGIT
)
2306 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2307 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnore
;
2308 myIDMap
[idCmd
++] = ShellMenuDeleteIgnore
;
2310 _tcscpy_s(maskbuf
, MAX_PATH
, _T("*"));
2311 if (_tcsrchr(ignorepath
, '.'))
2313 _tcscat_s(maskbuf
, MAX_PATH
, _tcsrchr(ignorepath
, '.'));
2314 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2315 stdstring verb
= stdstring(maskbuf
);
2316 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2317 myVerbsMap
[verb
] = idCmd
;
2318 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2319 myVerbsIDMap
[idCmd
] = verb
;
2320 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnoreCaseSensitive
;
2321 myIDMap
[idCmd
++] = ShellMenuDeleteIgnoreCaseSensitive
;
2326 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2327 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
2328 myIDMap
[idCmd
++] = ShellMenuIgnore
;
2330 _tcscpy_s(maskbuf
, MAX_PATH
, _T("*"));
2331 if (_tcsrchr(ignorepath
, '.'))
2333 _tcscat_s(maskbuf
, MAX_PATH
, _tcsrchr(ignorepath
, '.'));
2334 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2335 stdstring verb
= stdstring(maskbuf
);
2336 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2337 myVerbsMap
[verb
] = idCmd
;
2338 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2339 myVerbsIDMap
[idCmd
] = verb
;
2340 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2341 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2347 if (itemStates
& ITEMIS_INGIT
)
2349 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE
);
2350 _stprintf_s(ignorepath
, MAX_PATH
, stringtablebuffer
, files_
.size());
2351 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2352 stdstring verb
= stdstring(ignorepath
);
2353 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2354 myVerbsMap
[verb
] = idCmd
;
2355 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2356 myVerbsIDMap
[idCmd
] = verb
;
2357 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnore
;
2358 myIDMap
[idCmd
++] = ShellMenuDeleteIgnore
;
2360 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK
);
2361 _stprintf_s(ignorepath
, MAX_PATH
, stringtablebuffer
, files_
.size());
2362 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2363 verb
= stdstring(ignorepath
);
2364 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2365 myVerbsMap
[verb
] = idCmd
;
2366 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2367 myVerbsIDMap
[idCmd
] = verb
;
2368 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnoreCaseSensitive
;
2369 myIDMap
[idCmd
++] = ShellMenuDeleteIgnoreCaseSensitive
;
2373 MAKESTRING(IDS_MENUIGNOREMULTIPLE
);
2374 _stprintf_s(ignorepath
, MAX_PATH
, stringtablebuffer
, files_
.size());
2375 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2376 stdstring verb
= stdstring(ignorepath
);
2377 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2378 myVerbsMap
[verb
] = idCmd
;
2379 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2380 myVerbsIDMap
[idCmd
] = verb
;
2381 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
2382 myIDMap
[idCmd
++] = ShellMenuIgnore
;
2384 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK
);
2385 _stprintf_s(ignorepath
, MAX_PATH
, stringtablebuffer
, files_
.size());
2386 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2387 verb
= stdstring(ignorepath
);
2388 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2389 myVerbsMap
[verb
] = idCmd
;
2390 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2391 myVerbsIDMap
[idCmd
] = verb
;
2392 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2393 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2398 if (bShowIgnoreMenu
)
2400 MENUITEMINFO menuiteminfo
;
2401 SecureZeroMemory(&menuiteminfo
, sizeof(menuiteminfo
));
2402 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
2403 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
2406 menuiteminfo
.fMask
|= MIIM_BITMAP
;
2407 menuiteminfo
.hbmpItem
= (SysInfo::Instance().IsVistaOrLater()) ? IconToBitmapPARGB32(icon
) : HBMMENU_CALLBACK
;
2409 menuiteminfo
.fType
= MFT_STRING
;
2410 menuiteminfo
.hSubMenu
= ignoresubmenu
;
2411 menuiteminfo
.wID
= idCmd
;
2412 SecureZeroMemory(stringtablebuffer
, sizeof(stringtablebuffer
));
2413 if (itemStates
& ITEMIS_IGNORED
)
2414 GetMenuTextFromResource(ShellMenuUnIgnoreSub
);
2415 else if (itemStates
& ITEMIS_INGIT
)
2416 GetMenuTextFromResource(ShellMenuDeleteIgnoreSub
);
2418 GetMenuTextFromResource(ShellMenuIgnoreSub
);
2419 menuiteminfo
.dwTypeData
= stringtablebuffer
;
2420 menuiteminfo
.cch
= (UINT
)min(_tcslen(menuiteminfo
.dwTypeData
), UINT_MAX
);
2422 InsertMenuItem((topmenu
& MENUIGNORE
) ? hMenu
: subMenu
, (topmenu
& MENUIGNORE
) ? indexMenu
++ : indexSubMenu
++, TRUE
, &menuiteminfo
);
2423 if (itemStates
& ITEMIS_IGNORED
)
2425 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreSub
;
2426 myIDMap
[idCmd
++] = ShellMenuUnIgnoreSub
;
2430 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreSub
;
2431 myIDMap
[idCmd
++] = ShellMenuIgnoreSub
;
2434 return bShowIgnoreMenu
;
2437 HBITMAP
CShellExt::IconToBitmapPARGB32(UINT uIcon
)
2439 std::map
<UINT
, HBITMAP
>::iterator bitmap_it
= bitmaps
.lower_bound(uIcon
);
2440 if (bitmap_it
!= bitmaps
.end() && bitmap_it
->first
== uIcon
)
2441 return bitmap_it
->second
;
2443 HICON hIcon
= (HICON
)LoadImage(g_hResInst
, MAKEINTRESOURCE(uIcon
), IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
2447 if (pfnBeginBufferedPaint
== NULL
|| pfnEndBufferedPaint
== NULL
|| pfnGetBufferedPaintBits
== NULL
)
2451 sizIcon
.cx
= GetSystemMetrics(SM_CXSMICON
);
2452 sizIcon
.cy
= GetSystemMetrics(SM_CYSMICON
);
2455 SetRect(&rcIcon
, 0, 0, sizIcon
.cx
, sizIcon
.cy
);
2456 HBITMAP hBmp
= NULL
;
2458 HDC hdcDest
= CreateCompatibleDC(NULL
);
2461 if (SUCCEEDED(Create32BitHBITMAP(hdcDest
, &sizIcon
, NULL
, &hBmp
)))
2463 HBITMAP hbmpOld
= (HBITMAP
)SelectObject(hdcDest
, hBmp
);
2466 BLENDFUNCTION bfAlpha
= { AC_SRC_OVER
, 0, 255, AC_SRC_ALPHA
};
2467 BP_PAINTPARAMS paintParams
= {0};
2468 paintParams
.cbSize
= sizeof(paintParams
);
2469 paintParams
.dwFlags
= BPPF_ERASE
;
2470 paintParams
.pBlendFunction
= &bfAlpha
;
2473 HPAINTBUFFER hPaintBuffer
= pfnBeginBufferedPaint(hdcDest
, &rcIcon
, BPBF_DIB
, &paintParams
, &hdcBuffer
);
2476 if (DrawIconEx(hdcBuffer
, 0, 0, hIcon
, sizIcon
.cx
, sizIcon
.cy
, 0, NULL
, DI_NORMAL
))
2478 // If icon did not have an alpha channel we need to convert buffer to PARGB
2479 ConvertBufferToPARGB32(hPaintBuffer
, hdcDest
, hIcon
, sizIcon
);
2482 // This will write the buffer contents to the destination bitmap
2483 pfnEndBufferedPaint(hPaintBuffer
, TRUE
);
2486 SelectObject(hdcDest
, hbmpOld
);
2496 bitmaps
.insert(bitmap_it
, std::make_pair(uIcon
, hBmp
));
2500 HRESULT
CShellExt::Create32BitHBITMAP(HDC hdc
, const SIZE
*psize
, __deref_opt_out
void **ppvBits
, __out HBITMAP
* phBmp
)
2505 ZeroMemory(&bmi
, sizeof(bmi
));
2506 bmi
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
2507 bmi
.bmiHeader
.biPlanes
= 1;
2508 bmi
.bmiHeader
.biCompression
= BI_RGB
;
2510 bmi
.bmiHeader
.biWidth
= psize
->cx
;
2511 bmi
.bmiHeader
.biHeight
= psize
->cy
;
2512 bmi
.bmiHeader
.biBitCount
= 32;
2514 HDC hdcUsed
= hdc
? hdc
: GetDC(NULL
);
2517 *phBmp
= CreateDIBSection(hdcUsed
, &bmi
, DIB_RGB_COLORS
, ppvBits
, NULL
, 0);
2520 ReleaseDC(NULL
, hdcUsed
);
2523 return (NULL
== *phBmp
) ? E_OUTOFMEMORY
: S_OK
;
2526 HRESULT
CShellExt::ConvertBufferToPARGB32(HPAINTBUFFER hPaintBuffer
, HDC hdc
, HICON hicon
, SIZE
& sizIcon
)
2530 HRESULT hr
= pfnGetBufferedPaintBits(hPaintBuffer
, &prgbQuad
, &cxRow
);
2533 ARGB
*pargb
= reinterpret_cast<ARGB
*>(prgbQuad
);
2534 if (!HasAlpha(pargb
, sizIcon
, cxRow
))
2537 if (GetIconInfo(hicon
, &info
))
2541 hr
= ConvertToPARGB32(hdc
, pargb
, info
.hbmMask
, sizIcon
, cxRow
);
2544 DeleteObject(info
.hbmColor
);
2545 DeleteObject(info
.hbmMask
);
2553 bool CShellExt::HasAlpha(__in ARGB
*pargb
, SIZE
& sizImage
, int cxRow
)
2555 ULONG cxDelta
= cxRow
- sizImage
.cx
;
2556 for (ULONG y
= sizImage
.cy
; y
; --y
)
2558 for (ULONG x
= sizImage
.cx
; x
; --x
)
2560 if (*pargb
++ & 0xFF000000)
2572 HRESULT
CShellExt::ConvertToPARGB32(HDC hdc
, __inout ARGB
*pargb
, HBITMAP hbmp
, SIZE
& sizImage
, int cxRow
)
2575 ZeroMemory(&bmi
, sizeof(bmi
));
2576 bmi
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
2577 bmi
.bmiHeader
.biPlanes
= 1;
2578 bmi
.bmiHeader
.biCompression
= BI_RGB
;
2580 bmi
.bmiHeader
.biWidth
= sizImage
.cx
;
2581 bmi
.bmiHeader
.biHeight
= sizImage
.cy
;
2582 bmi
.bmiHeader
.biBitCount
= 32;
2584 HRESULT hr
= E_OUTOFMEMORY
;
2585 HANDLE hHeap
= GetProcessHeap();
2586 void *pvBits
= HeapAlloc(hHeap
, 0, bmi
.bmiHeader
.biWidth
* 4 * bmi
.bmiHeader
.biHeight
);
2590 if (GetDIBits(hdc
, hbmp
, 0, bmi
.bmiHeader
.biHeight
, pvBits
, &bmi
, DIB_RGB_COLORS
) == bmi
.bmiHeader
.biHeight
)
2592 ULONG cxDelta
= cxRow
- bmi
.bmiHeader
.biWidth
;
2593 ARGB
*pargbMask
= static_cast<ARGB
*>(pvBits
);
2595 for (ULONG y
= bmi
.bmiHeader
.biHeight
; y
; --y
)
2597 for (ULONG x
= bmi
.bmiHeader
.biWidth
; x
; --x
)
2601 // transparent pixel
2607 *pargb
++ |= 0xFF000000;
2617 HeapFree(hHeap
, 0, pvBits
);
2623 void CShellExt::RunCommand(const tstring
& path
, const tstring
& command
, LPCTSTR errorMessage
)
2625 if (CCreateProcessHelper::CreateProcessDetached(path
.c_str(), const_cast<TCHAR
*>(command
.c_str())))
2627 // process started - exit
2631 MessageBox(NULL
, CFormatMessageWrapper(), errorMessage
, MB_OK
| MB_ICONINFORMATION
);