1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "ItemIDList.h"
22 #include "PreserveChdir.h"
23 #include "UnicodeUtils.h"
24 //#include "GitProperties.h"
25 #include "GitStatus.h"
28 #define GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
29 #define GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
31 int g_shellidlist
=RegisterClipboardFormat(CFSTR_SHELLIDLIST
);
33 extern MenuInfo menuInfo
[];
36 STDMETHODIMP
CShellExt::Initialize(LPCITEMIDLIST pIDFolder
,
37 LPDATAOBJECT pDataObj
,
41 ATLTRACE("Shell :: Initialize\n");
42 PreserveChdir preserveChdir
;
50 git_wc_status_kind fetchedstatus
= git_wc_status_none
;
51 // get selected files/folders
55 FORMATETC fmte
= {(CLIPFORMAT
)g_shellidlist
,
56 (DVTARGETDEVICE FAR
*)NULL
,
60 HRESULT hres
= pDataObj
->GetData(&fmte
, &medium
);
62 if (SUCCEEDED(hres
) && medium
.hGlobal
)
64 if (m_State
== FileStateDropHandler
)
67 FORMATETC etc
= { CF_HDROP
, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
};
68 STGMEDIUM stg
= { TYMED_HGLOBAL
};
69 if ( FAILED( pDataObj
->GetData ( &etc
, &stg
)))
71 ReleaseStgMedium ( &medium
);
76 HDROP drop
= (HDROP
)GlobalLock(stg
.hGlobal
);
79 ReleaseStgMedium ( &stg
);
80 ReleaseStgMedium ( &medium
);
84 int count
= DragQueryFile(drop
, (UINT
)-1, NULL
, 0);
86 itemStates
|= ITEMIS_ONLYONE
;
87 for (int i
= 0; i
< count
; i
++)
89 // find the path length in chars
90 UINT len
= DragQueryFile(drop
, i
, NULL
, 0);
93 TCHAR
* szFileName
= new TCHAR
[len
+1];
94 if (0 == DragQueryFile(drop
, i
, szFileName
, len
+1))
99 stdstring str
= stdstring(szFileName
);
100 delete [] szFileName
;
101 if ((str
.empty() == false)&&(g_ShellCache
.IsContextPathAllowed(szFileName
)))
103 if (itemStates
& ITEMIS_ONLYONE
)
106 strpath
.SetFromWin(str
.c_str());
107 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE
: 0;
108 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE
: 0;
110 files_
.push_back(str
);
113 //get the Subversion status of the item
114 git_wc_status_kind status
= git_wc_status_none
;
116 askedpath
.SetFromWin(str
.c_str());
120 stat
.GetStatus(CTGitPath(str
.c_str()), false, false, true);
124 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
125 fetchedstatus
= status
;
126 //if ((stat.status->entry)&&(stat.status->entry->lock_token))
127 // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
128 if ( askedpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
130 itemStates
|= ITEMIS_FOLDER
;
131 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
132 itemStates
|= ITEMIS_FOLDERINSVN
;
134 //if ((stat.status->entry)&&(stat.status->entry->present_props))
136 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
137 // itemStates |= ITEMIS_NEEDSLOCK;
139 //if ((stat.status->entry)&&(stat.status->entry->uuid))
140 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
144 // sometimes, git_client_status() returns with an error.
145 // in that case, we have to check if the working copy is versioned
146 // anyway to show the 'correct' context menu
147 if (askedpath
.HasAdminDir())
148 status
= git_wc_status_normal
;
153 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
156 // TODO: should we really assume any sub-directory to be versioned
157 // or only if it contains versioned files
158 itemStates
|= askedpath
.GetAdminDirMask();
160 if ((status
== git_wc_status_unversioned
) || (status
== git_wc_status_ignored
) || (status
== git_wc_status_none
))
161 itemStates
&= ~ITEMIS_INSVN
;
163 if (status
== git_wc_status_ignored
)
164 itemStates
|= ITEMIS_IGNORED
;
165 if (status
== git_wc_status_normal
)
166 itemStates
|= ITEMIS_NORMAL
;
167 if (status
== git_wc_status_conflicted
)
168 itemStates
|= ITEMIS_CONFLICTED
;
169 if (status
== git_wc_status_added
)
170 itemStates
|= ITEMIS_ADDED
;
171 if (status
== git_wc_status_deleted
)
172 itemStates
|= ITEMIS_DELETED
;
175 } // for (int i = 0; i < count; i++)
176 GlobalUnlock ( drop
);
177 ReleaseStgMedium ( &stg
);
179 } // if (m_State == FileStateDropHandler)
183 //Enumerate PIDLs which the user has selected
184 CIDA
* cida
= (CIDA
*)GlobalLock(medium
.hGlobal
);
185 ItemIDList
parent( GetPIDLFolder (cida
));
187 int count
= cida
->cidl
;
188 BOOL statfetched
= FALSE
;
189 for (int i
= 0; i
< count
; ++i
)
191 ItemIDList
child (GetPIDLItem (cida
, i
), &parent
);
192 stdstring str
= child
.toString();
193 if ((str
.empty() == false)&&(g_ShellCache
.IsContextPathAllowed(str
.c_str())))
195 //check if our menu is requested for a subversion admin directory
196 if (g_GitAdminDir
.IsAdminDirPath(str
.c_str()))
199 files_
.push_back(str
);
201 strpath
.SetFromWin(str
.c_str());
202 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE
: 0;
203 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE
: 0;
206 //get the Subversion status of the item
207 git_wc_status_kind status
= git_wc_status_none
;
208 if ((g_ShellCache
.IsSimpleContext())&&(strpath
.IsDirectory()))
210 if (strpath
.HasAdminDir())
211 status
= git_wc_status_normal
;
218 if (strpath
.HasAdminDir())
219 stat
.GetStatus(strpath
, false, false, true);
223 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
224 fetchedstatus
= status
;
225 //if ((stat.status->entry)&&(stat.status->entry->lock_token))
226 // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
227 if ( strpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
229 itemStates
|= ITEMIS_FOLDER
;
230 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
231 itemStates
|= ITEMIS_FOLDERINSVN
;
233 // TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
234 if (status
== git_wc_status_conflicted
)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
235 itemStates
|= ITEMIS_CONFLICTED
;
236 //if ((stat.status->entry)&&(stat.status->entry->present_props))
238 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
239 // itemStates |= ITEMIS_NEEDSLOCK;
241 //if ((stat.status->entry)&&(stat.status->entry->uuid))
242 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
246 // sometimes, git_client_status() returns with an error.
247 // in that case, we have to check if the working copy is versioned
248 // anyway to show the 'correct' context menu
249 if (strpath
.HasAdminDir())
251 status
= git_wc_status_normal
;
252 fetchedstatus
= status
;
259 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
263 itemStates
|= strpath
.GetAdminDirMask();
265 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
266 itemStates
&= ~ITEMIS_INSVN
;
267 if (status
== git_wc_status_ignored
)
269 itemStates
|= ITEMIS_IGNORED
;
270 // the item is ignored. Get the svn:ignored properties so we can (maybe) later
271 // offer a 'remove from ignored list' entry
272 // GitProperties props(strpath.GetContainingDirectory(), false);
273 // ignoredprops.empty();
274 // for (int p=0; p<props.GetCount(); ++p)
276 // if (props.GetItemName(p).compare(stdstring(_T("svn:ignore")))==0)
278 // std::string st = props.GetItemValue(p);
279 // ignoredprops = MultibyteToWide(st.c_str());
280 // // remove all escape chars ('\\')
281 // std::remove(ignoredprops.begin(), ignoredprops.end(), '\\');
287 if (status
== git_wc_status_normal
)
288 itemStates
|= ITEMIS_NORMAL
;
289 if (status
== git_wc_status_conflicted
)
290 itemStates
|= ITEMIS_CONFLICTED
;
291 if (status
== git_wc_status_added
)
292 itemStates
|= ITEMIS_ADDED
;
293 if (status
== git_wc_status_deleted
)
294 itemStates
|= ITEMIS_DELETED
;
297 } // for (int i = 0; i < count; ++i)
298 ItemIDList
child (GetPIDLItem (cida
, 0), &parent
);
299 if (g_ShellCache
.HasSVNAdminDir(child
.toString().c_str(), FALSE
))
300 itemStates
|= ITEMIS_INVERSIONEDFOLDER
;
301 GlobalUnlock(medium
.hGlobal
);
303 // if the item is a versioned folder, check if there's a patch file
304 // in the clipboard to be used in "Apply Patch"
305 UINT cFormatDiff
= RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
308 if (IsClipboardFormatAvailable(cFormatDiff
))
309 itemStates
|= ITEMIS_PATCHINCLIPBOARD
;
311 if (IsClipboardFormatAvailable(CF_HDROP
))
312 itemStates
|= ITEMIS_PATHINCLIPBOARD
;
316 ReleaseStgMedium ( &medium
);
317 if (medium
.pUnkForRelease
)
319 IUnknown
* relInterface
= (IUnknown
*)medium
.pUnkForRelease
;
320 relInterface
->Release();
325 // get folder background
329 ItemIDList
list(pIDFolder
);
330 folder_
= list
.toString();
331 git_wc_status_kind status
= git_wc_status_none
;
332 if (IsClipboardFormatAvailable(CF_HDROP
))
333 itemStatesFolder
|= ITEMIS_PATHINCLIPBOARD
;
336 askedpath
.SetFromWin(folder_
.c_str());
338 if (g_ShellCache
.IsContextPathAllowed(folder_
.c_str()))
340 if (folder_
.compare(statuspath
)!=0)
346 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
349 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
350 // if ((stat.status->entry)&&(stat.status->entry->lock_token))
351 // itemStatesFolder |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
352 // if ((stat.status->entry)&&(stat.status->entry->present_props))
354 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
355 // itemStatesFolder |= ITEMIS_NEEDSLOCK;
357 // if ((stat.status->entry)&&(stat.status->entry->uuid))
358 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
363 // sometimes, git_client_status() returns with an error.
364 // in that case, we have to check if the working copy is versioned
365 // anyway to show the 'correct' context menu
366 if (askedpath
.HasAdminDir())
367 status
= git_wc_status_normal
;
370 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
371 itemStatesFolder
|= askedpath
.GetAdminDirMask();
373 if (status
== git_wc_status_normal
)
374 itemStatesFolder
|= ITEMIS_NORMAL
;
375 if (status
== git_wc_status_conflicted
)
376 itemStatesFolder
|= ITEMIS_CONFLICTED
;
377 if (status
== git_wc_status_added
)
378 itemStatesFolder
|= ITEMIS_ADDED
;
379 if (status
== git_wc_status_deleted
)
380 itemStatesFolder
|= ITEMIS_DELETED
;
385 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
390 status
= fetchedstatus
;
392 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
393 itemStatesFolder
|= askedpath
.GetAdminDirMask();
395 if (status
== git_wc_status_ignored
)
396 itemStatesFolder
|= ITEMIS_IGNORED
;
397 itemStatesFolder
|= ITEMIS_FOLDER
;
398 if (files_
.size() == 0)
399 itemStates
|= ITEMIS_ONLYONE
;
400 if (m_State
!= FileStateDropHandler
)
401 itemStates
|= itemStatesFolder
;
405 status
= fetchedstatus
;
408 if (files_
.size() == 2)
409 itemStates
|= ITEMIS_TWO
;
410 if ((files_
.size() == 1)&&(g_ShellCache
.IsContextPathAllowed(files_
.front().c_str())))
413 itemStates
|= ITEMIS_ONLYONE
;
414 if (m_State
!= FileStateDropHandler
)
416 if (PathIsDirectory(files_
.front().c_str()))
418 folder_
= files_
.front();
419 git_wc_status_kind status
= git_wc_status_none
;
421 askedpath
.SetFromWin(folder_
.c_str());
423 if (folder_
.compare(statuspath
)!=0)
428 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
431 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
432 // if ((stat.status->entry)&&(stat.status->entry->lock_token))
433 // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
434 // if ((stat.status->entry)&&(stat.status->entry->present_props))
436 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
437 // itemStates |= ITEMIS_NEEDSLOCK;
439 // if ((stat.status->entry)&&(stat.status->entry->uuid))
440 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
445 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
450 status
= fetchedstatus
;
452 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
453 itemStates
|= askedpath
.GetAdminDirMask();
455 if (status
== git_wc_status_ignored
)
456 itemStates
|= ITEMIS_IGNORED
;
457 itemStates
|= ITEMIS_FOLDER
;
458 if (status
== git_wc_status_added
)
459 itemStates
|= ITEMIS_ADDED
;
460 if (status
== git_wc_status_deleted
)
461 itemStates
|= ITEMIS_DELETED
;
471 void CShellExt::InsertGitMenu(BOOL istop
, HMENU menu
, UINT pos
, UINT_PTR id
, UINT stringid
, UINT icon
, UINT idCmdFirst
, GitCommands com
, UINT uFlags
)
473 TCHAR menutextbuffer
[512] = {0};
474 TCHAR verbsbuffer
[255] = {0};
475 MAKESTRING(stringid
);
479 //menu entry for the top context menu, so append an "Git " before
480 //the menu text to indicate where the entry comes from
481 _tcscpy_s(menutextbuffer
, 255, _T("Git "));
483 _tcscat_s(menutextbuffer
, 255, stringtablebuffer
);
485 // insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
486 // so we have an easy and fast way to check the current branch
487 // (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
488 if (com
== ShellMenuCommit
)
491 CTGitPath
path(folder_
.empty() ? files_
.front().c_str() : folder_
.c_str());
492 CString sProjectRoot
;
495 if (path
.HasAdminDir(&sProjectRoot
) && !g_Git
.GetCurrentBranchFromFile(sProjectRoot
, sBranchName
))
497 if (sBranchName
.GetLength() == 40)
499 // if SHA1 only show 4 first bytes
501 for (int i
=0; i
<40; i
++)
502 if ( !iswxdigit(sBranchName
[i
]) )
508 sBranchName
= sBranchName
.Left(8) + _T("....");
512 if (sBranchName
.GetLength() > 64)
513 sBranchName
= sBranchName
.Left(64) + _T("...");
515 // scan to before "..."
516 LPTSTR s
= menutextbuffer
+ _tcslen(menutextbuffer
)-1;
517 if (s
> menutextbuffer
)
519 while (s
> menutextbuffer
)
534 // append branch name and end with ...
535 _tcscpy(s
, _T(" -> \"") + sBranchName
+ _T("\"..."));
539 if ((fullver
< 0x500)||(fullver
== 0x500 && !(uFlags
&~(CMF_RESERVED
|CMF_EXPLORE
|CMF_EXTENDEDVERBS
))))
541 // on win2k, the context menu does not work properly if we use
542 // icon bitmaps. At least the menu text is empty in the context menu
543 // for folder backgrounds (seems like a win2k bug).
544 // the workaround is to use the check/unchecked bitmaps, which are drawn
545 // with AND raster op, but it's better than nothing at all
546 InsertMenu(menu
, pos
, MF_BYPOSITION
| MF_STRING
, id
, menutextbuffer
);
549 HBITMAP bmp
= IconToBitmap(icon
);
550 SetMenuItemBitmaps(menu
, pos
, MF_BYPOSITION
, bmp
, bmp
);
555 MENUITEMINFO menuiteminfo
;
556 SecureZeroMemory(&menuiteminfo
, sizeof(menuiteminfo
));
557 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
558 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_STRING
;
559 menuiteminfo
.fType
= MFT_STRING
;
560 menuiteminfo
.dwTypeData
= menutextbuffer
;
563 menuiteminfo
.fMask
|= MIIM_BITMAP
;
564 menuiteminfo
.hbmpItem
= (fullver
>= 0x600) ? IconToBitmapPARGB32(icon
) : HBMMENU_CALLBACK
;
566 menuiteminfo
.wID
= id
;
567 InsertMenuItem(menu
, pos
, TRUE
, &menuiteminfo
);
572 //menu entry for the top context menu, so append an "Git " before
573 //the menu text to indicate where the entry comes from
574 _tcscpy_s(menutextbuffer
, 255, _T("Git "));
576 LoadString(g_hResInst
, stringid
, verbsbuffer
, sizeof(verbsbuffer
));
577 _tcscat_s(menutextbuffer
, 255, verbsbuffer
);
578 stdstring verb
= stdstring(menutextbuffer
);
579 if (verb
.find('&') != -1)
581 verb
.erase(verb
.find('&'),1);
583 myVerbsMap
[verb
] = id
- idCmdFirst
;
584 myVerbsMap
[verb
] = id
;
585 myVerbsIDMap
[id
- idCmdFirst
] = verb
;
586 myVerbsIDMap
[id
] = verb
;
587 // We store the relative and absolute diameter
588 // (drawitem callback uses absolute, others relative)
589 myIDMap
[id
- idCmdFirst
] = com
;
592 mySubMenuMap
[pos
] = com
;
595 HBITMAP
CShellExt::IconToBitmap(UINT uIcon
)
597 std::map
<UINT
, HBITMAP
>::iterator bitmap_it
= bitmaps
.lower_bound(uIcon
);
598 if (bitmap_it
!= bitmaps
.end() && bitmap_it
->first
== uIcon
)
599 return bitmap_it
->second
;
601 HICON hIcon
= (HICON
)LoadImage(g_hResInst
, MAKEINTRESOURCE(uIcon
), IMAGE_ICON
, 12, 12, LR_DEFAULTCOLOR
);
607 rect
.right
= ::GetSystemMetrics(SM_CXMENUCHECK
);
608 rect
.bottom
= ::GetSystemMetrics(SM_CYMENUCHECK
);
610 rect
.left
= rect
.top
= 0;
612 HWND desktop
= ::GetDesktopWindow();
619 HDC screen_dev
= ::GetDC(desktop
);
620 if (screen_dev
== NULL
)
626 // Create a compatible DC
627 HDC dst_hdc
= ::CreateCompatibleDC(screen_dev
);
631 ::ReleaseDC(desktop
, screen_dev
);
635 // Create a new bitmap of icon size
636 HBITMAP bmp
= ::CreateCompatibleBitmap(screen_dev
, rect
.right
, rect
.bottom
);
641 ::ReleaseDC(desktop
, screen_dev
);
645 // Select it into the compatible DC
646 HBITMAP old_dst_bmp
= (HBITMAP
)::SelectObject(dst_hdc
, bmp
);
647 if (old_dst_bmp
== NULL
)
653 // Fill the background of the compatible DC with the white color
654 // that is taken by menu routines as transparent
655 ::SetBkColor(dst_hdc
, RGB(255, 255, 255));
656 ::ExtTextOut(dst_hdc
, 0, 0, ETO_OPAQUE
, &rect
, NULL
, 0, NULL
);
658 // Draw the icon into the compatible DC
659 ::DrawIconEx(dst_hdc
, 0, 0, hIcon
, rect
.right
, rect
.bottom
, 0, NULL
, DI_NORMAL
);
662 ::SelectObject(dst_hdc
, old_dst_bmp
);
664 ::ReleaseDC(desktop
, screen_dev
);
667 bitmaps
.insert(bitmap_it
, std::make_pair(uIcon
, bmp
));
671 bool CShellExt::WriteClipboardPathsToTempFile(stdstring
& tempfile
)
674 tempfile
= stdstring();
675 //write all selected files and paths to a temporary file
676 //for TortoiseProc.exe to read out again.
678 DWORD pathlength
= GetTempPath(0, NULL
);
679 TCHAR
* path
= new TCHAR
[pathlength
+1];
680 TCHAR
* tempFile
= new TCHAR
[pathlength
+ 100];
681 GetTempPath (pathlength
+1, path
);
682 GetTempFileName (path
, _T("git"), 0, tempFile
);
683 tempfile
= stdstring(tempFile
);
685 HANDLE file
= ::CreateFile (tempFile
,
690 FILE_ATTRIBUTE_TEMPORARY
,
695 if (file
== INVALID_HANDLE_VALUE
)
698 if (!IsClipboardFormatAvailable(CF_HDROP
))
700 if (!OpenClipboard(NULL
))
703 stdstring sClipboardText
;
704 HGLOBAL hglb
= GetClipboardData(CF_HDROP
);
705 HDROP hDrop
= (HDROP
)GlobalLock(hglb
);
708 TCHAR szFileName
[MAX_PATH
];
709 UINT cFiles
= DragQueryFile(hDrop
, 0xFFFFFFFF, NULL
, 0);
710 for(UINT i
= 0; i
< cFiles
; ++i
)
712 DragQueryFile(hDrop
, i
, szFileName
, sizeof(szFileName
));
713 stdstring filename
= szFileName
;
714 ::WriteFile (file
, filename
.c_str(), filename
.size()*sizeof(TCHAR
), &written
, 0);
715 ::WriteFile (file
, _T("\n"), 2, &written
, 0);
728 stdstring
CShellExt::WriteFileListToTempFile()
730 //write all selected files and paths to a temporary file
731 //for TortoiseProc.exe to read out again.
732 DWORD pathlength
= GetTempPath(0, NULL
);
733 TCHAR
* path
= new TCHAR
[pathlength
+1];
734 TCHAR
* tempFile
= new TCHAR
[pathlength
+ 100];
735 GetTempPath (pathlength
+1, path
);
736 GetTempFileName (path
, _T("git"), 0, tempFile
);
737 stdstring retFilePath
= stdstring(tempFile
);
739 HANDLE file
= ::CreateFile (tempFile
,
744 FILE_ATTRIBUTE_TEMPORARY
,
749 if (file
== INVALID_HANDLE_VALUE
)
755 ::WriteFile (file
, folder_
.c_str(), folder_
.size()*sizeof(TCHAR
), &written
, 0);
756 ::WriteFile (file
, _T("\n"), 2, &written
, 0);
759 for (std::vector
<stdstring
>::iterator I
= files_
.begin(); I
!= files_
.end(); ++I
)
761 ::WriteFile (file
, I
->c_str(), I
->size()*sizeof(TCHAR
), &written
, 0);
762 ::WriteFile (file
, _T("\n"), 2, &written
, 0);
768 STDMETHODIMP
CShellExt::QueryDropContext(UINT uFlags
, UINT idCmdFirst
, HMENU hMenu
, UINT
&indexMenu
)
770 PreserveChdir preserveChdir
;
773 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
774 return NOERROR
; //we don't change the default action
776 if ((files_
.size() == 0)||(folder_
.size() == 0))
779 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
782 bool bSourceAndTargetFromSameRepository
= (uuidSource
.compare(uuidTarget
) == 0) || uuidSource
.empty() || uuidTarget
.empty();
784 //the drop handler only has eight commands, but not all are visible at the same time:
785 //if the source file(s) are under version control then those files can be moved
786 //to the new location or they can be moved with a rename,
787 //if they are unversioned then they can be added to the working copy
788 //if they are versioned, they also can be exported to an unversioned location
789 UINT idCmd
= idCmdFirst
;
792 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
793 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINSVN
)&&((itemStates
& ITEMIS_INSVN
)&&((~itemStates
) & ITEMIS_ADDED
)))
794 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVEMENU
, 0, idCmdFirst
, ShellMenuDropMove
, uFlags
);
796 // Git move and rename here
797 // 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
798 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINSVN
)&&(itemStates
& ITEMIS_INSVN
)&&(itemStates
& ITEMIS_ONLYONE
)&&((~itemStates
) & ITEMIS_ADDED
))
799 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVERENAMEMENU
, 0, idCmdFirst
, ShellMenuDropMoveRename
, uFlags
);
802 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
803 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINSVN
)&&(itemStates
& ITEMIS_INSVN
)&&((~itemStates
) & ITEMIS_ADDED
))
804 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYMENU
, 0, idCmdFirst
, ShellMenuDropCopy
, uFlags
);
806 // Git copy and rename here, source and target from same repository
807 // available if source is a single, versioned but not added item, target is versioned or target folder is added
808 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINSVN
)&&(itemStates
& ITEMIS_INSVN
)&&(itemStates
& ITEMIS_ONLYONE
)&&((~itemStates
) & ITEMIS_ADDED
))
809 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYRENAMEMENU
, 0, idCmdFirst
, ShellMenuDropCopyRename
, uFlags
);
812 // available if target is versioned and source is either unversioned or from another repository
813 if ((itemStatesFolder
& ITEMIS_FOLDERINSVN
)&&(((~itemStates
) & ITEMIS_INSVN
)||!bSourceAndTargetFromSameRepository
))
814 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYADDMENU
, 0, idCmdFirst
, ShellMenuDropCopyAdd
, uFlags
);
817 // available if source is versioned and a folder
818 if ((itemStates
& ITEMIS_INSVN
)&&(itemStates
& ITEMIS_FOLDER
))
819 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPEXPORTMENU
, 0, idCmdFirst
, ShellMenuDropExport
, uFlags
);
821 // Git export all here
822 // available if source is versioned and a folder
823 if ((itemStates
& ITEMIS_INSVN
)&&(itemStates
& ITEMIS_FOLDER
))
824 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPEXPORTEXTENDEDMENU
, 0, idCmdFirst
, ShellMenuDropExportExtended
, uFlags
);
827 // available if source is a patchfile
828 if (itemStates
& ITEMIS_PATCHFILE
)
829 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_MENUAPPLYPATCH
, 0, idCmdFirst
, ShellMenuApplyPatch
, uFlags
);
832 if (idCmd
!= idCmdFirst
)
833 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 NOERROR
; //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 NOERROR
; // 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 //check if our menu is requested for a subversion admin directory
906 if (g_GitAdminDir
.IsAdminDirPath(folder_
.c_str()))
909 if (uFlags
& CMF_EXTENDEDVERBS
)
910 itemStates
|= ITEMIS_EXTENDED
;
912 const BOOL bShortcut
= !!(uFlags
& CMF_VERBSONLY
);
913 if ( bShortcut
&& (files_
.size()==1))
915 // Don't show the context menu for a link if the
916 // destination is not part of a working copy.
917 // It would only show the standard menu items
918 // which are already shown for the lnk-file.
919 CString path
= files_
.front().c_str();
920 if ( !g_GitAdminDir
.HasAdminDir(path
) )
926 //check if we already added our menu entry for a folder.
927 //we check that by iterating through all menu entries and check if
928 //the dwItemData member points to our global ID string. That string is set
929 //by our shell extension when the folder menu is inserted.
930 TCHAR menubuf
[MAX_PATH
];
931 int count
= GetMenuItemCount(hMenu
);
932 for (int i
=0; i
<count
; ++i
)
935 SecureZeroMemory(&miif
, sizeof(MENUITEMINFO
));
936 miif
.cbSize
= sizeof(MENUITEMINFO
);
937 miif
.fMask
= MIIM_DATA
;
938 miif
.dwTypeData
= menubuf
;
939 miif
.cch
= sizeof(menubuf
)/sizeof(TCHAR
);
940 GetMenuItemInfo(hMenu
, i
, TRUE
, &miif
);
941 if (miif
.dwItemData
== (ULONG_PTR
)g_MenuIDString
)
946 UINT idCmd
= idCmdFirst
;
948 //create the sub menu
949 HMENU subMenu
= CreateMenu();
950 int indexSubMenu
= 0;
952 unsigned __int64 topmenu
= g_ShellCache
.GetMenuLayout();
953 unsigned __int64 menumask
= g_ShellCache
.GetMenuMask();
954 unsigned __int64 menuex
= g_ShellCache
.GetMenuExt();
957 bool bAddSeparator
= false;
958 bool bMenuEntryAdded
= false;
959 bool bMenuEmpty
= true;
960 // insert separator at start
961 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, NULL
); idCmd
++;
962 bool bShowIcons
= !!DWORD(CRegStdWORD(_T("Software\\TortoiseGit\\ShowContextMenuIcons"), TRUE
));
963 // ?? TSV disabled icons for win2k and earlier, but they work for win2k and should work for win95 and up
964 /*if (fullver <= 0x500)
965 bShowIcons = false;*/
968 if (itemStates
& (ITEMIS_INSVN
|ITEMIS_FOLDERINSVN
))
970 // show current branch name (as a "read-only" menu entry)
972 CTGitPath
path(folder_
.empty() ? files_
.front().c_str() : folder_
.c_str());
973 CString sProjectRoot
;
976 if (path
.HasAdminDir(&sProjectRoot
) && !g_Git
.GetCurrentBranchFromFile(sProjectRoot
, sBranchName
))
978 if (sBranchName
.GetLength() == 40)
980 // if SHA1 only show 4 first bytes
982 for (int i
=0; i
<40; i
++)
983 if ( !iswxdigit(sBranchName
[i
]) )
989 sBranchName
= sBranchName
.Left(8) + _T("....");
992 sBranchName
= _T('"') + sBranchName
+ _T('"');
994 const int icon
= IDI_COPY
;
995 const int pos
= indexMenu
++;
996 const int id
= idCmd
++;
998 if ((fullver
< 0x500)||(fullver
== 0x500 && !(uFlags
&~(CMF_RESERVED
|CMF_EXPLORE
|CMF_EXTENDEDVERBS
))))
1000 InsertMenu(hMenu
, pos
, MF_DISABLED
|MF_GRAYED
|MF_BYPOSITION
|MF_STRING
, id
, sBranchName
);
1001 HBITMAP bmp
= IconToBitmap(icon
);
1002 SetMenuItemBitmaps(hMenu
, pos
, MF_BYPOSITION
, bmp
, bmp
);
1006 MENUITEMINFO menuiteminfo
;
1007 SecureZeroMemory(&menuiteminfo
, sizeof(menuiteminfo
));
1008 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
1009 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_STRING
| MIIM_STATE
;
1010 menuiteminfo
.fState
= MFS_DISABLED
;
1011 menuiteminfo
.fType
= MFT_STRING
;
1012 menuiteminfo
.dwTypeData
= (LPWSTR
)sBranchName
.GetString();
1015 menuiteminfo
.fMask
|= MIIM_BITMAP
;
1016 menuiteminfo
.hbmpItem
= (fullver
>= 0x600) ? IconToBitmapPARGB32(icon
) : HBMMENU_CALLBACK
;
1018 if (menuiteminfo
.hbmpItem
== HBMMENU_CALLBACK
)
1020 // WM_DRAWITEM uses myIDMap to get icon, we use the same icon as create branch
1021 myIDMap
[id
- idCmdFirst
] = ShellMenuBranch
;
1022 myIDMap
[id
] = ShellMenuBranch
;
1025 menuiteminfo
.wID
= id
;
1026 InsertMenuItem(hMenu
, pos
, TRUE
, &menuiteminfo
);
1032 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1034 if (menuInfo
[menuIndex
].command
== ShellSeparator
)
1036 // we don't add a separator immediately. Because there might not be
1037 // another 'normal' menu entry after we insert a separator.
1038 // we simply set a flag here, indicating that before the next
1039 // 'normal' menu entry, a separator should be added.
1041 bAddSeparator
= true;
1045 // check the conditions whether to show the menu entry or not
1046 bool bInsertMenu
= false;
1048 if (menuInfo
[menuIndex
].firstyes
&& menuInfo
[menuIndex
].firstno
)
1050 if (((menuInfo
[menuIndex
].firstyes
& itemStates
) == menuInfo
[menuIndex
].firstyes
)
1052 ((menuInfo
[menuIndex
].firstno
& (~itemStates
)) == menuInfo
[menuIndex
].firstno
))
1055 else if ((menuInfo
[menuIndex
].firstyes
)&&((menuInfo
[menuIndex
].firstyes
& itemStates
) == menuInfo
[menuIndex
].firstyes
))
1057 else if ((menuInfo
[menuIndex
].firstno
)&&((menuInfo
[menuIndex
].firstno
& (~itemStates
)) == menuInfo
[menuIndex
].firstno
))
1060 if (menuInfo
[menuIndex
].secondyes
&& menuInfo
[menuIndex
].secondno
)
1062 if (((menuInfo
[menuIndex
].secondyes
& itemStates
) == menuInfo
[menuIndex
].secondyes
)
1064 ((menuInfo
[menuIndex
].secondno
& (~itemStates
)) == menuInfo
[menuIndex
].secondno
))
1067 else if ((menuInfo
[menuIndex
].secondyes
)&&((menuInfo
[menuIndex
].secondyes
& itemStates
) == menuInfo
[menuIndex
].secondyes
))
1069 else if ((menuInfo
[menuIndex
].secondno
)&&((menuInfo
[menuIndex
].secondno
& (~itemStates
)) == menuInfo
[menuIndex
].secondno
))
1072 if (menuInfo
[menuIndex
].thirdyes
&& menuInfo
[menuIndex
].thirdno
)
1074 if (((menuInfo
[menuIndex
].thirdyes
& itemStates
) == menuInfo
[menuIndex
].thirdyes
)
1076 ((menuInfo
[menuIndex
].thirdno
& (~itemStates
)) == menuInfo
[menuIndex
].thirdno
))
1079 else if ((menuInfo
[menuIndex
].thirdyes
)&&((menuInfo
[menuIndex
].thirdyes
& itemStates
) == menuInfo
[menuIndex
].thirdyes
))
1081 else if ((menuInfo
[menuIndex
].thirdno
)&&((menuInfo
[menuIndex
].thirdno
& (~itemStates
)) == menuInfo
[menuIndex
].thirdno
))
1084 if (menuInfo
[menuIndex
].fourthyes
&& menuInfo
[menuIndex
].fourthno
)
1086 if (((menuInfo
[menuIndex
].fourthyes
& itemStates
) == menuInfo
[menuIndex
].fourthyes
)
1088 ((menuInfo
[menuIndex
].fourthno
& (~itemStates
)) == menuInfo
[menuIndex
].fourthno
))
1091 else if ((menuInfo
[menuIndex
].fourthyes
)&&((menuInfo
[menuIndex
].fourthyes
& itemStates
) == menuInfo
[menuIndex
].fourthyes
))
1093 else if ((menuInfo
[menuIndex
].fourthno
)&&((menuInfo
[menuIndex
].fourthno
& (~itemStates
)) == menuInfo
[menuIndex
].fourthno
))
1096 if (menuInfo
[menuIndex
].menuID
& menuex
)
1098 if( !(itemStates
& ITEMIS_EXTENDED
) )
1100 bInsertMenu
= false;
1104 if (menuInfo
[menuIndex
].menuID
& (~menumask
))
1108 bool bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1109 // insert a separator
1110 if ((bMenuEntryAdded
)&&(bAddSeparator
)&&(!bIsTop
))
1112 bAddSeparator
= false;
1113 bMenuEntryAdded
= false;
1114 InsertMenu(subMenu
, indexSubMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, NULL
);
1118 // handle special cases (sub menus)
1119 if ((menuInfo
[menuIndex
].command
== ShellMenuIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuUnIgnoreSub
))
1121 InsertIgnoreSubmenus(idCmd
, idCmdFirst
, hMenu
, subMenu
, indexMenu
, indexSubMenu
, topmenu
, bShowIcons
, uFlags
);
1122 bMenuEntryAdded
= true;
1127 bool bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1129 // insert the menu entry
1130 InsertGitMenu( bIsTop
,
1131 bIsTop
? hMenu
: subMenu
,
1132 bIsTop
? indexMenu
++ : indexSubMenu
++,
1134 menuInfo
[menuIndex
].menuTextID
,
1135 bShowIcons
? menuInfo
[menuIndex
].iconID
: 0,
1137 menuInfo
[menuIndex
].command
,
1141 bMenuEntryAdded
= true;
1151 //add sub menu to main context menu
1152 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1153 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1154 MAKESTRING(IDS_MENUSUBMENU
);
1155 MENUITEMINFO menuiteminfo
;
1156 SecureZeroMemory(&menuiteminfo
, sizeof(menuiteminfo
));
1157 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
1158 menuiteminfo
.fType
= MFT_STRING
;
1159 menuiteminfo
.dwTypeData
= stringtablebuffer
;
1161 UINT uIcon
= bShowIcons
? IDI_APP
: 0;
1164 uIcon
= bShowIcons
? IDI_MENUFOLDER
: 0;
1165 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFolder
;
1166 myIDMap
[idCmd
] = ShellSubMenuFolder
;
1167 menuiteminfo
.dwItemData
= (ULONG_PTR
)g_MenuIDString
;
1169 else if (!bShortcut
&& (files_
.size()==1))
1171 uIcon
= bShowIcons
? IDI_MENUFILE
: 0;
1172 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFile
;
1173 myIDMap
[idCmd
] = ShellSubMenuFile
;
1175 else if (bShortcut
&& (files_
.size()==1))
1177 uIcon
= bShowIcons
? IDI_MENULINK
: 0;
1178 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuLink
;
1179 myIDMap
[idCmd
] = ShellSubMenuLink
;
1181 else if (files_
.size() > 1)
1183 uIcon
= bShowIcons
? IDI_MENUMULTIPLE
: 0;
1184 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuMultiple
;
1185 myIDMap
[idCmd
] = ShellSubMenuMultiple
;
1189 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenu
;
1190 myIDMap
[idCmd
] = ShellSubMenu
;
1193 if ((fullver
< 0x500)||(fullver
== 0x500 && !(uFlags
&~(CMF_RESERVED
|CMF_EXPLORE
|CMF_EXTENDEDVERBS
))))
1195 menuiteminfo
.fMask
= MIIM_STRING
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
;
1198 menuiteminfo
.fMask
|= MIIM_CHECKMARKS
;
1199 bmp
= IconToBitmap(uIcon
);
1200 menuiteminfo
.hbmpChecked
= bmp
;
1201 menuiteminfo
.hbmpUnchecked
= bmp
;
1206 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
1209 menuiteminfo
.fMask
|= MIIM_BITMAP
;
1210 menuiteminfo
.hbmpItem
= (fullver
>= 0x600) ? IconToBitmapPARGB32(uIcon
) : HBMMENU_CALLBACK
;
1213 menuiteminfo
.hSubMenu
= subMenu
;
1214 menuiteminfo
.wID
= idCmd
++;
1215 InsertMenuItem(hMenu
, indexMenu
++, TRUE
, &menuiteminfo
);
1218 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, NULL
); idCmd
++;
1220 //return number of menu items added
1221 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
1225 // This is called when you invoke a command on the menu:
1226 STDMETHODIMP
CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi
)
1228 PreserveChdir preserveChdir
;
1229 HRESULT hr
= E_INVALIDARG
;
1233 std::string command
;
1237 if ((files_
.size() > 0)||(folder_
.size() > 0))
1239 UINT idCmd
= LOWORD(lpcmi
->lpVerb
);
1241 if (HIWORD(lpcmi
->lpVerb
))
1243 stdstring verb
= stdstring(MultibyteToWide(lpcmi
->lpVerb
));
1244 std::map
<stdstring
, UINT_PTR
>::const_iterator verb_it
= myVerbsMap
.lower_bound(verb
);
1245 if (verb_it
!= myVerbsMap
.end() && verb_it
->first
== verb
)
1246 idCmd
= verb_it
->second
;
1251 // See if we have a handler interface for this id
1252 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1253 if (id_it
!= myIDMap
.end() && id_it
->first
== idCmd
)
1255 STARTUPINFO startup
;
1256 PROCESS_INFORMATION process
;
1257 memset(&startup
, 0, sizeof(startup
));
1258 startup
.cb
= sizeof(startup
);
1259 memset(&process
, 0, sizeof(process
));
1260 CRegStdString
tortoiseProcPath(_T("Software\\TortoiseGit\\ProcPath"), _T("TortoiseProc.exe"), false, HKEY_LOCAL_MACHINE
);
1261 CRegStdString
tortoiseMergePath(_T("Software\\TortoiseGit\\TMergePath"), _T("TortoiseMerge.exe"), false, HKEY_LOCAL_MACHINE
);
1263 //TortoiseProc expects a command line of the form:
1264 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1266 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1268 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1269 //* pathfile is a path to a temporary file which contains a list of file paths
1270 stdstring svnCmd
= _T(" /command:");
1272 switch (id_it
->second
)
1276 svnCmd
+= _T("sync /path:\"");
1280 case ShellMenuCheckout
:
1281 svnCmd
+= _T("checkout /path:\"");
1285 case ShellMenuUpdate
:
1286 tempfile
= WriteFileListToTempFile();
1287 svnCmd
+= _T("update /pathfile:\"");
1290 svnCmd
+= _T(" /deletepathfile");
1292 case ShellMenuSubSync
:
1293 tempfile
= WriteFileListToTempFile();
1294 svnCmd
+= _T("subsync /pathfile:\"");
1297 svnCmd
+= _T(" /deletepathfile");
1298 if(itemStatesFolder
&ITEMIS_SUBMODULE
)
1300 svnCmd
+= _T(" /bkpath:\"");
1305 case ShellMenuUpdateExt
:
1306 tempfile
= WriteFileListToTempFile();
1307 svnCmd
+= _T("subupdate /pathfile:\"");
1310 svnCmd
+= _T(" /deletepathfile");
1311 if(itemStatesFolder
&ITEMIS_SUBMODULE
)
1313 svnCmd
+= _T(" /bkpath:\"");
1318 case ShellMenuCommit
:
1319 tempfile
= WriteFileListToTempFile();
1320 svnCmd
+= _T("commit /pathfile:\"");
1323 svnCmd
+= _T(" /deletepathfile");
1326 case ShellMenuAddAsReplacement
:
1327 tempfile
= WriteFileListToTempFile();
1328 svnCmd
+= _T("add /pathfile:\"");
1331 svnCmd
+= _T(" /deletepathfile");
1333 case ShellMenuIgnore
:
1334 tempfile
= WriteFileListToTempFile();
1335 svnCmd
+= _T("ignore /pathfile:\"");
1338 svnCmd
+= _T(" /deletepathfile");
1340 case ShellMenuIgnoreCaseSensitive
:
1341 tempfile
= WriteFileListToTempFile();
1342 svnCmd
+= _T("ignore /pathfile:\"");
1345 svnCmd
+= _T(" /deletepathfile");
1346 svnCmd
+= _T(" /onlymask");
1348 case ShellMenuUnIgnore
:
1349 tempfile
= WriteFileListToTempFile();
1350 svnCmd
+= _T("unignore /pathfile:\"");
1353 svnCmd
+= _T(" /deletepathfile");
1355 case ShellMenuUnIgnoreCaseSensitive
:
1356 tempfile
= WriteFileListToTempFile();
1357 svnCmd
+= _T("unignore /pathfile:\"");
1360 svnCmd
+= _T(" /deletepathfile");
1361 svnCmd
+= _T(" /onlymask");
1363 case ShellMenuRevert
:
1364 tempfile
= WriteFileListToTempFile();
1365 svnCmd
+= _T("revert /pathfile:\"");
1368 svnCmd
+= _T(" /deletepathfile");
1370 case ShellMenuDelUnversioned
:
1371 svnCmd
+= _T("delunversioned /path:\"");
1375 case ShellMenuCleanup
:
1376 tempfile
= WriteFileListToTempFile();
1377 svnCmd
+= _T("cleanup /pathfile:\"");
1380 svnCmd
+= _T(" /deletepathfile");
1382 case ShellMenuSendMail
:
1383 tempfile
= WriteFileListToTempFile();
1384 svnCmd
+= _T("sendmail /pathfile:\"");
1387 svnCmd
+= _T(" /deletepathfile");
1389 case ShellMenuResolve
:
1390 tempfile
= WriteFileListToTempFile();
1391 svnCmd
+= _T("resolve /pathfile:\"");
1394 svnCmd
+= _T(" /deletepathfile");
1396 case ShellMenuSwitch
:
1397 svnCmd
+= _T("switch /path:\"");
1398 if (files_
.size() > 0)
1399 svnCmd
+= files_
.front();
1404 case ShellMenuImport
:
1405 svnCmd
+= _T("import /path:\"");
1409 case ShellMenuExport
:
1410 svnCmd
+= _T("export /path:\"");
1414 case ShellMenuAbout
:
1415 svnCmd
+= _T("about");
1417 case ShellMenuCreateRepos
:
1418 svnCmd
+= _T("repocreate /path:\"");
1422 case ShellMenuMerge
:
1423 svnCmd
+= _T("merge /path:\"");
1424 if (files_
.size() > 0)
1425 svnCmd
+= files_
.front();
1430 case ShellMenuMergeAll
:
1431 svnCmd
+= _T("mergeall /path:\"");
1432 if (files_
.size() > 0)
1433 svnCmd
+= files_
.front();
1439 svnCmd
+= _T("copy /path:\"");
1440 if (files_
.size() > 0)
1441 svnCmd
+= files_
.front();
1446 case ShellMenuSettings
:
1447 svnCmd
+= _T("settings /path:\"");
1448 if (files_
.size() > 0)
1449 svnCmd
+= files_
.front();
1455 svnCmd
+= _T("help");
1457 case ShellMenuRename
:
1458 svnCmd
+= _T("rename /path:\"");
1459 if (files_
.size() > 0)
1460 svnCmd
+= files_
.front();
1465 case ShellMenuRemove
:
1466 tempfile
= WriteFileListToTempFile();
1467 svnCmd
+= _T("remove /pathfile:\"");
1470 svnCmd
+= _T(" /deletepathfile");
1472 case ShellMenuRemoveKeep
:
1473 tempfile
= WriteFileListToTempFile();
1474 svnCmd
+= _T("remove /pathfile:\"");
1477 svnCmd
+= _T(" /deletepathfile");
1478 svnCmd
+= _T(" /keep");
1481 svnCmd
+= _T("diff /path:\"");
1482 if (files_
.size() == 1)
1483 svnCmd
+= files_
.front();
1484 else if (files_
.size() == 2)
1486 std::vector
<stdstring
>::iterator I
= files_
.begin();
1489 svnCmd
+= _T("\" /path2:\"");
1495 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1496 svnCmd
+= _T(" /alternative");
1498 case ShellMenuPrevDiff
:
1499 svnCmd
+= _T("prevdiff /path:\"");
1500 if (files_
.size() == 1)
1501 svnCmd
+= files_
.front();
1505 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1506 svnCmd
+= _T(" /alternative");
1508 case ShellMenuUrlDiff
:
1509 svnCmd
+= _T("urldiff /path:\"");
1510 if (files_
.size() == 1)
1511 svnCmd
+= files_
.front();
1516 case ShellMenuDiffTwo
:
1517 svnCmd
+= _T("diffcommits /path:\"");
1518 if (files_
.size() == 1)
1519 svnCmd
+= files_
.front();
1524 case ShellMenuDropCopyAdd
:
1525 tempfile
= WriteFileListToTempFile();
1526 svnCmd
+= _T("dropcopyadd /pathfile:\"");
1529 svnCmd
+= _T(" /deletepathfile");
1530 svnCmd
+= _T(" /droptarget:\"");
1534 case ShellMenuDropCopy
:
1535 tempfile
= WriteFileListToTempFile();
1536 svnCmd
+= _T("dropcopy /pathfile:\"");
1539 svnCmd
+= _T(" /deletepathfile");
1540 svnCmd
+= _T(" /droptarget:\"");
1544 case ShellMenuDropCopyRename
:
1545 tempfile
= WriteFileListToTempFile();
1546 svnCmd
+= _T("dropcopy /pathfile:\"");
1549 svnCmd
+= _T(" /deletepathfile");
1550 svnCmd
+= _T(" /droptarget:\"");
1552 svnCmd
+= _T("\" /rename";)
1554 case ShellMenuDropMove
:
1555 tempfile
= WriteFileListToTempFile();
1556 svnCmd
+= _T("dropmove /pathfile:\"");
1559 svnCmd
+= _T(" /deletepathfile");
1560 svnCmd
+= _T(" /droptarget:\"");
1564 case ShellMenuDropMoveRename
:
1565 tempfile
= WriteFileListToTempFile();
1566 svnCmd
+= _T("dropmove /pathfile:\"");
1569 svnCmd
+= _T(" /deletepathfile");
1570 svnCmd
+= _T(" /droptarget:\"");
1572 svnCmd
+= _T("\" /rename";)
1574 case ShellMenuDropExport
:
1575 tempfile
= WriteFileListToTempFile();
1576 svnCmd
+= _T("dropexport /pathfile:\"");
1579 svnCmd
+= _T(" /deletepathfile");
1580 svnCmd
+= _T(" /droptarget:\"");
1584 case ShellMenuDropExportExtended
:
1585 tempfile
= WriteFileListToTempFile();
1586 svnCmd
+= _T("dropexport /pathfile:\"");
1589 svnCmd
+= _T(" /deletepathfile");
1590 svnCmd
+= _T(" /droptarget:\"");
1593 svnCmd
+= _T(" /extended");
1596 svnCmd
+= _T("log /path:\"");
1597 if (files_
.size() > 0)
1598 svnCmd
+= files_
.front();
1603 case ShellMenuConflictEditor
:
1604 svnCmd
+= _T("conflicteditor /path:\"");
1605 if (files_
.size() > 0)
1606 svnCmd
+= files_
.front();
1611 case ShellMenuRelocate
:
1612 svnCmd
+= _T("relocate /path:\"");
1613 if (files_
.size() > 0)
1614 svnCmd
+= files_
.front();
1619 case ShellMenuGitSVNRebase
:
1620 svnCmd
+= _T("svnrebase /path:\"");
1621 if (files_
.size() > 0)
1622 svnCmd
+= files_
.front();
1627 case ShellMenuGitSVNDCommit
:
1628 svnCmd
+= _T("svndcommit /path:\"");
1629 if (files_
.size() > 0)
1630 svnCmd
+= files_
.front();
1635 case ShellMenuGitSVNIgnore
:
1636 svnCmd
+= _T("svnignore /path:\"");
1637 if (files_
.size() > 0)
1638 svnCmd
+= files_
.front();
1643 case ShellMenuRebase
:
1644 svnCmd
+= _T("rebase /path:\"");
1645 if (files_
.size() > 0)
1646 svnCmd
+= files_
.front();
1651 case ShellMenuShowChanged
:
1652 if (files_
.size() > 1)
1654 tempfile
= WriteFileListToTempFile();
1655 svnCmd
+= _T("repostatus /pathfile:\"");
1658 svnCmd
+= _T(" /deletepathfile");
1662 svnCmd
+= _T("repostatus /path:\"");
1663 if (files_
.size() > 0)
1664 svnCmd
+= files_
.front();
1670 case ShellMenuRefBrowse
:
1671 svnCmd
+= _T("refbrowse /path:\"");
1672 if (files_
.size() > 0)
1673 svnCmd
+= files_
.front();
1678 case ShellMenuRefLog
:
1679 svnCmd
+= _T("reflog /path:\"");
1680 if (files_
.size() > 0)
1681 svnCmd
+= files_
.front();
1687 case ShellMenuStashSave
:
1688 svnCmd
+= _T("stashsave /path:\"");
1689 if (files_
.size() > 0)
1690 svnCmd
+= files_
.front();
1696 case ShellMenuStashApply
:
1697 svnCmd
+= _T("stashapply /path:\"");
1698 if (files_
.size() > 0)
1699 svnCmd
+= files_
.front();
1705 case ShellMenuStashPop
:
1706 svnCmd
+= _T("stashpop /path:\"");
1707 if (files_
.size() > 0)
1708 svnCmd
+= files_
.front();
1715 case ShellMenuStashList
:
1716 svnCmd
+= _T("reflog /path:\"");
1717 if (files_
.size() > 0)
1718 svnCmd
+= files_
.front();
1721 svnCmd
+= _T("\" /ref:refs/stash");
1724 case ShellMenuSubAdd
:
1725 svnCmd
+= _T("subadd /path:\"");
1726 if (files_
.size() > 0)
1727 svnCmd
+= files_
.front();
1733 case ShellMenuBlame
:
1734 svnCmd
+= _T("blame /path:\"");
1735 if (files_
.size() > 0)
1736 svnCmd
+= files_
.front();
1741 case ShellMenuCreatePatch
:
1742 tempfile
= WriteFileListToTempFile();
1743 svnCmd
+= _T("createpatch /pathfile:\"");
1746 svnCmd
+= _T(" /deletepathfile");
1748 case ShellMenuApplyPatch
:
1749 if ((itemStates
& ITEMIS_PATCHINCLIPBOARD
) && ((~itemStates
) & ITEMIS_PATCHFILE
))
1751 // if there's a patch file in the clipboard, we save it
1752 // to a temporary file and tell TortoiseMerge to use that one
1753 UINT cFormat
= RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
1754 if ((cFormat
)&&(OpenClipboard(NULL
)))
1756 HGLOBAL hglb
= GetClipboardData(cFormat
);
1757 LPCSTR lpstr
= (LPCSTR
)GlobalLock(hglb
);
1759 DWORD len
= GetTempPath(0, NULL
);
1760 TCHAR
* path
= new TCHAR
[len
+1];
1761 TCHAR
* tempF
= new TCHAR
[len
+100];
1762 GetTempPath (len
+1, path
);
1763 GetTempFileName (path
, TEXT("git"), 0, tempF
);
1764 std::wstring sTempFile
= std::wstring(tempF
);
1769 size_t patchlen
= strlen(lpstr
);
1770 _tfopen_s(&outFile
, sTempFile
.c_str(), _T("wb"));
1773 size_t size
= fwrite(lpstr
, sizeof(char), patchlen
, outFile
);
1774 if (size
== patchlen
)
1776 itemStates
|= ITEMIS_PATCHFILE
;
1778 files_
.push_back(sTempFile
);
1786 if (itemStates
& ITEMIS_PATCHFILE
)
1788 svnCmd
= _T(" /diff:\"");
1789 if (files_
.size() > 0)
1791 svnCmd
+= files_
.front();
1792 if (itemStatesFolder
& ITEMIS_FOLDERINSVN
)
1794 svnCmd
+= _T("\" /patchpath:\"");
1800 if (itemStates
& ITEMIS_INVERSIONEDFOLDER
)
1801 svnCmd
+= _T("\" /wc");
1807 svnCmd
= _T(" /patchpath:\"");
1808 if (files_
.size() > 0)
1809 svnCmd
+= files_
.front();
1815 myVerbsIDMap
.clear();
1817 if (CreateProcess(((stdstring
)tortoiseMergePath
).c_str(), const_cast<TCHAR
*>(svnCmd
.c_str()), NULL
, NULL
, FALSE
, 0, 0, 0, &startup
, &process
)==0)
1820 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
|
1821 FORMAT_MESSAGE_FROM_SYSTEM
|
1822 FORMAT_MESSAGE_IGNORE_INSERTS
,
1825 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), // Default language
1830 MessageBox( NULL
, (LPCTSTR
)lpMsgBuf
, _T("TortoiseMerge launch failed"), MB_OK
| MB_ICONINFORMATION
);
1831 LocalFree( lpMsgBuf
);
1833 CloseHandle(process
.hThread
);
1834 CloseHandle(process
.hProcess
);
1837 case ShellMenuRevisionGraph
:
1838 svnCmd
+= _T("revisiongraph /path:\"");
1839 if (files_
.size() > 0)
1840 svnCmd
+= files_
.front();
1845 case ShellMenuProperties
:
1846 tempfile
= WriteFileListToTempFile();
1847 svnCmd
+= _T("properties /pathfile:\"");
1850 svnCmd
+= _T(" /deletepathfile");
1852 case ShellMenuClipPaste
:
1853 if (WriteClipboardPathsToTempFile(tempfile
))
1856 UINT cPrefDropFormat
= RegisterClipboardFormat(_T("Preferred DropEffect"));
1857 if (cPrefDropFormat
)
1859 if (OpenClipboard(lpcmi
->hwnd
))
1861 HGLOBAL hglb
= GetClipboardData(cPrefDropFormat
);
1864 DWORD
* effect
= (DWORD
*) GlobalLock(hglb
);
1865 if (*effect
== DROPEFFECT_MOVE
)
1874 svnCmd
+= _T("pastecopy /pathfile:\"");
1876 svnCmd
+= _T("pastemove /pathfile:\"");
1879 svnCmd
+= _T(" /deletepathfile");
1880 svnCmd
+= _T(" /droptarget:\"");
1884 else return NOERROR
;
1886 case ShellMenuClone
:
1887 svnCmd
+= _T("clone /path:\"");
1892 svnCmd
+= _T("pull /path:\"");
1893 if (files_
.size() > 0)
1894 svnCmd
+= files_
.front();
1900 svnCmd
+= _T("push /path:\"");
1901 if (files_
.size() > 0)
1902 svnCmd
+= files_
.front();
1907 case ShellMenuBranch
:
1908 svnCmd
+= _T("branch /path:\"");
1909 if (files_
.size() > 0)
1910 svnCmd
+= files_
.front();
1917 svnCmd
+= _T("tag /path:\"");
1918 if (files_
.size() > 0)
1919 svnCmd
+= files_
.front();
1925 case ShellMenuFormatPatch
:
1926 svnCmd
+= _T("formatpatch /path:\"");
1927 if (files_
.size() > 0)
1928 svnCmd
+= files_
.front();
1934 case ShellMenuImportPatch
:
1935 tempfile
= WriteFileListToTempFile();
1936 svnCmd
+= _T("importpatch /pathfile:\"");
1939 svnCmd
+= _T(" /deletepathfile");
1942 case ShellMenuCherryPick
:
1943 svnCmd
+= _T("cherrypick /path:\"");
1944 if (files_
.size() > 0)
1945 svnCmd
+= files_
.front();
1950 case ShellMenuFetch
:
1951 svnCmd
+= _T("fetch /path:\"");
1952 if (files_
.size() > 0)
1953 svnCmd
+= files_
.front();
1962 } // switch (id_it->second)
1963 svnCmd
+= _T(" /hwnd:");
1965 _stprintf_s(buf
, 30, _T("%d"), lpcmi
->hwnd
);
1968 myVerbsIDMap
.clear();
1970 if (CreateProcess(((stdstring
)tortoiseProcPath
).c_str(), const_cast<TCHAR
*>(svnCmd
.c_str()), NULL
, NULL
, FALSE
, 0, 0, 0, &startup
, &process
)==0)
1973 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
|
1974 FORMAT_MESSAGE_FROM_SYSTEM
|
1975 FORMAT_MESSAGE_IGNORE_INSERTS
,
1978 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), // Default language
1983 MessageBox( NULL
, (LPCTSTR
)lpMsgBuf
, _T("TortoiseProc Launch failed"), MB_OK
| MB_ICONINFORMATION
);
1984 LocalFree( lpMsgBuf
);
1986 CloseHandle(process
.hThread
);
1987 CloseHandle(process
.hProcess
);
1989 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1990 } // if ((files_.size() > 0)||(folder_.size() > 0))
1995 // This is for the status bar and things like that:
1996 STDMETHODIMP
CShellExt::GetCommandString(UINT_PTR idCmd
,
1998 UINT FAR
* /*reserved*/,
2002 PreserveChdir preserveChdir
;
2003 //do we know the id?
2004 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
2005 if (id_it
== myIDMap
.end() || id_it
->first
!= idCmd
)
2007 return E_INVALIDARG
; //no, we don't
2011 HRESULT hr
= E_INVALIDARG
;
2013 MAKESTRING(IDS_MENUDESCDEFAULT
);
2015 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
2017 if (menuInfo
[menuIndex
].command
== (GitCommands
)id_it
->second
)
2019 MAKESTRING(menuInfo
[menuIndex
].menuDescID
);
2025 const TCHAR
* desc
= stringtablebuffer
;
2030 std::string help
= WideToMultibyte(desc
);
2031 lstrcpynA(pszName
, help
.c_str(), cchMax
);
2037 wide_string help
= desc
;
2038 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
);
2044 std::map
<UINT_PTR
, stdstring
>::const_iterator verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
2045 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
2047 std::string help
= WideToMultibyte(verb_id_it
->second
);
2048 lstrcpynA(pszName
, help
.c_str(), cchMax
);
2055 std::map
<UINT_PTR
, stdstring
>::const_iterator verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
2056 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
2058 wide_string help
= verb_id_it
->second
;
2059 ATLTRACE("verb : %ws\n", help
.c_str());
2060 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
);
2069 STDMETHODIMP
CShellExt::HandleMenuMsg(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
2072 return HandleMenuMsg2(uMsg
, wParam
, lParam
, &res
);
2075 STDMETHODIMP
CShellExt::HandleMenuMsg2(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*pResult
)
2077 PreserveChdir preserveChdir
;
2080 if (pResult
== NULL
)
2087 case WM_MEASUREITEM
:
2089 MEASUREITEMSTRUCT
* lpmis
= (MEASUREITEMSTRUCT
*)lParam
;
2090 if (lpmis
==NULL
||lpmis
->CtlType
!=ODT_MENU
)
2092 lpmis
->itemWidth
+= 2;
2093 if (lpmis
->itemHeight
< 16)
2094 lpmis
->itemHeight
= 16;
2101 DRAWITEMSTRUCT
* lpdis
= (DRAWITEMSTRUCT
*)lParam
;
2102 if ((lpdis
==NULL
)||(lpdis
->CtlType
!= ODT_MENU
))
2103 return S_OK
; //not for a menu
2104 resource
= GetMenuTextFromResource(myIDMap
[lpdis
->itemID
]);
2105 if (resource
== NULL
)
2107 HICON hIcon
= (HICON
)LoadImage(g_hResInst
, resource
, IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
2110 DrawIconEx(lpdis
->hDC
,
2111 lpdis
->rcItem
.left
- 16,
2112 lpdis
->rcItem
.top
+ (lpdis
->rcItem
.bottom
- lpdis
->rcItem
.top
- 16) / 2,
2114 0, NULL
, DI_NORMAL
);
2123 if (HIWORD(wParam
) != MF_POPUP
)
2125 int nChar
= LOWORD(wParam
);
2126 if (_istascii((wint_t)nChar
) && _istupper((wint_t)nChar
))
2127 nChar
= tolower(nChar
);
2128 // we have the char the user pressed, now search that char in all our
2130 std::vector
<int> accmenus
;
2131 for (std::map
<UINT_PTR
, UINT_PTR
>::iterator It
= mySubMenuMap
.begin(); It
!= mySubMenuMap
.end(); ++It
)
2133 resource
= GetMenuTextFromResource(mySubMenuMap
[It
->first
]);
2134 if (resource
== NULL
)
2136 szItem
= stringtablebuffer
;
2137 TCHAR
* amp
= _tcschr(szItem
, '&');
2141 int ampChar
= LOWORD(*amp
);
2142 if (_istascii((wint_t)ampChar
) && _istupper((wint_t)ampChar
))
2143 ampChar
= tolower(ampChar
);
2144 if (ampChar
== nChar
)
2146 // yep, we found a menu which has the pressed key
2147 // as an accelerator. Add that menu to the list to
2149 accmenus
.push_back(It
->first
);
2152 if (accmenus
.size() == 0)
2154 // no menu with that accelerator key.
2155 *pResult
= MAKELONG(0, MNC_IGNORE
);
2158 if (accmenus
.size() == 1)
2160 // Only one menu with that accelerator key. We're lucky!
2161 // So just execute that menu entry.
2162 *pResult
= MAKELONG(accmenus
[0], MNC_EXECUTE
);
2165 if (accmenus
.size() > 1)
2167 // we have more than one menu item with this accelerator key!
2169 mif
.cbSize
= sizeof(MENUITEMINFO
);
2170 mif
.fMask
= MIIM_STATE
;
2171 for (std::vector
<int>::iterator it
= accmenus
.begin(); it
!= accmenus
.end(); ++it
)
2173 GetMenuItemInfo((HMENU
)lParam
, *it
, TRUE
, &mif
);
2174 if (mif
.fState
== MFS_HILITE
)
2176 // this is the selected item, so select the next one
2178 if (it
== accmenus
.end())
2179 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
2181 *pResult
= MAKELONG(*it
, MNC_SELECT
);
2185 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
2196 LPCTSTR
CShellExt::GetMenuTextFromResource(int id
)
2199 LPCTSTR resource
= NULL
;
2200 unsigned __int64 layout
= g_ShellCache
.GetMenuLayout();
2204 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
2206 if (menuInfo
[menuIndex
].command
== id
)
2208 MAKESTRING(menuInfo
[menuIndex
].menuTextID
);
2209 resource
= MAKEINTRESOURCE(menuInfo
[menuIndex
].iconID
);
2212 case ShellSubMenuMultiple
:
2213 case ShellSubMenuLink
:
2214 case ShellSubMenuFolder
:
2215 case ShellSubMenuFile
:
2220 space
= layout
& menuInfo
[menuIndex
].menuID
? 0 : 6;
2221 if (layout
& (menuInfo
[menuIndex
].menuID
))
2223 _tcscpy_s(textbuf
, 255, _T("Git "));
2224 _tcscat_s(textbuf
, 255, stringtablebuffer
);
2225 _tcscpy_s(stringtablebuffer
, 255, textbuf
);
2236 bool CShellExt::IsIllegalFolder(std::wstring folder
, int * cslidarray
)
2239 TCHAR buf
[MAX_PATH
]; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
2240 LPITEMIDLIST pidl
= NULL
;
2241 while (cslidarray
[i
])
2245 if (SHGetFolderLocation(NULL
, cslidarray
[i
-1], NULL
, 0, &pidl
)!=S_OK
)
2247 if (!SHGetPathFromIDList(pidl
, buf
))
2249 // not a file system path, definitely illegal for our use
2250 CoTaskMemFree(pidl
);
2253 CoTaskMemFree(pidl
);
2254 if (_tcslen(buf
)==0)
2256 if (_tcscmp(buf
, folder
.c_str())==0)
2262 void CShellExt::InsertIgnoreSubmenus(UINT
&idCmd
, UINT idCmdFirst
, HMENU hMenu
, HMENU subMenu
, UINT
&indexMenu
, int &indexSubMenu
, unsigned __int64 topmenu
, bool bShowIcons
, UINT uFlags
)
2264 HMENU ignoresubmenu
= NULL
;
2265 int indexignoresub
= 0;
2266 bool bShowIgnoreMenu
= false;
2267 TCHAR maskbuf
[MAX_PATH
]; // MAX_PATH is ok, since this only holds a filename
2268 TCHAR ignorepath
[MAX_PATH
]; // MAX_PATH is ok, since this only holds a filename
2269 if (files_
.size() == 0)
2271 UINT icon
= bShowIcons
? IDI_IGNORE
: 0;
2273 std::vector
<stdstring
>::iterator I
= files_
.begin();
2274 if (_tcsrchr(I
->c_str(), '\\'))
2275 _tcscpy_s(ignorepath
, MAX_PATH
, _tcsrchr(I
->c_str(), '\\')+1);
2277 _tcscpy_s(ignorepath
, MAX_PATH
, I
->c_str());
2278 if ((itemStates
& ITEMIS_IGNORED
)&&(ignoredprops
.size() > 0))
2280 // check if the item name is ignored or the mask
2282 while ( (p
=ignoredprops
.find( ignorepath
,p
)) != -1 )
2284 if ( (p
==0 || ignoredprops
[p
-1]==TCHAR('\n'))
2285 && (p
+_tcslen(ignorepath
)==ignoredprops
.length() || ignoredprops
[p
+_tcslen(ignorepath
)+1]==TCHAR('\n')) )
2293 ignoresubmenu
= CreateMenu();
2294 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2295 stdstring verb
= stdstring(ignorepath
);
2296 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2297 myVerbsMap
[verb
] = idCmd
;
2298 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2299 myVerbsIDMap
[idCmd
] = verb
;
2300 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnore
;
2301 myIDMap
[idCmd
++] = ShellMenuUnIgnore
;
2302 bShowIgnoreMenu
= true;
2304 _tcscpy_s(maskbuf
, MAX_PATH
, _T("*"));
2305 if (_tcsrchr(ignorepath
, '.'))
2307 _tcscat_s(maskbuf
, MAX_PATH
, _tcsrchr(ignorepath
, '.'));
2308 p
= ignoredprops
.find(maskbuf
);
2310 ((ignoredprops
.compare(maskbuf
)==0) || (ignoredprops
.find('\n', p
)==p
+_tcslen(maskbuf
)+1) || (ignoredprops
.rfind('\n', p
)==p
-1)))
2312 if (ignoresubmenu
==NULL
)
2313 ignoresubmenu
= CreateMenu();
2315 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2316 stdstring verb
= stdstring(maskbuf
);
2317 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2318 myVerbsMap
[verb
] = idCmd
;
2319 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2320 myVerbsIDMap
[idCmd
] = verb
;
2321 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreCaseSensitive
;
2322 myIDMap
[idCmd
++] = ShellMenuUnIgnoreCaseSensitive
;
2323 bShowIgnoreMenu
= true;
2327 else if ((itemStates
& ITEMIS_IGNORED
) == 0)
2329 bShowIgnoreMenu
= true;
2330 ignoresubmenu
= CreateMenu();
2331 if (itemStates
& ITEMIS_ONLYONE
)
2333 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2334 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
2335 myIDMap
[idCmd
++] = ShellMenuIgnore
;
2337 _tcscpy_s(maskbuf
, MAX_PATH
, _T("*"));
2338 if (_tcsrchr(ignorepath
, '.'))
2340 _tcscat_s(maskbuf
, MAX_PATH
, _tcsrchr(ignorepath
, '.'));
2341 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2342 stdstring verb
= stdstring(maskbuf
);
2343 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2344 myVerbsMap
[verb
] = idCmd
;
2345 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2346 myVerbsIDMap
[idCmd
] = verb
;
2347 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2348 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2353 MAKESTRING(IDS_MENUIGNOREMULTIPLE
);
2354 _stprintf_s(ignorepath
, MAX_PATH
, stringtablebuffer
, files_
.size());
2355 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2356 stdstring verb
= stdstring(ignorepath
);
2357 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2358 myVerbsMap
[verb
] = idCmd
;
2359 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2360 myVerbsIDMap
[idCmd
] = verb
;
2361 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
2362 myIDMap
[idCmd
++] = ShellMenuIgnore
;
2364 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK
);
2365 _stprintf_s(ignorepath
, MAX_PATH
, stringtablebuffer
, files_
.size());
2366 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2367 verb
= stdstring(ignorepath
);
2368 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2369 myVerbsMap
[verb
] = idCmd
;
2370 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2371 myVerbsIDMap
[idCmd
] = verb
;
2372 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2373 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2377 if (bShowIgnoreMenu
)
2379 MENUITEMINFO menuiteminfo
;
2380 SecureZeroMemory(&menuiteminfo
, sizeof(menuiteminfo
));
2381 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
2382 if (fullver
< 0x500 || (fullver
== 0x500 && !(uFlags
&~(CMF_RESERVED
|CMF_EXPLORE
|CMF_EXTENDEDVERBS
))))
2384 menuiteminfo
.fMask
= MIIM_STRING
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
;
2387 HBITMAP bmp
= IconToBitmap(icon
);
2388 menuiteminfo
.fMask
|= MIIM_CHECKMARKS
;
2389 menuiteminfo
.hbmpChecked
= bmp
;
2390 menuiteminfo
.hbmpUnchecked
= bmp
;
2395 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
2398 menuiteminfo
.fMask
|= MIIM_BITMAP
;
2399 menuiteminfo
.hbmpItem
= (fullver
>= 0x600) ? IconToBitmapPARGB32(icon
) : HBMMENU_CALLBACK
;
2402 menuiteminfo
.fType
= MFT_STRING
;
2403 menuiteminfo
.hSubMenu
= ignoresubmenu
;
2404 menuiteminfo
.wID
= idCmd
;
2405 SecureZeroMemory(stringtablebuffer
, sizeof(stringtablebuffer
));
2406 if (itemStates
& ITEMIS_IGNORED
)
2407 GetMenuTextFromResource(ShellMenuUnIgnoreSub
);
2409 GetMenuTextFromResource(ShellMenuIgnoreSub
);
2410 menuiteminfo
.dwTypeData
= stringtablebuffer
;
2411 menuiteminfo
.cch
= (UINT
)min(_tcslen(menuiteminfo
.dwTypeData
), UINT_MAX
);
2413 InsertMenuItem((topmenu
& MENUIGNORE
) ? hMenu
: subMenu
, (topmenu
& MENUIGNORE
) ? indexMenu
++ : indexSubMenu
++, TRUE
, &menuiteminfo
);
2414 if (itemStates
& ITEMIS_IGNORED
)
2416 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreSub
;
2417 myIDMap
[idCmd
++] = ShellMenuUnIgnoreSub
;
2421 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreSub
;
2422 myIDMap
[idCmd
++] = ShellMenuIgnoreSub
;
2427 HBITMAP
CShellExt::IconToBitmapPARGB32(UINT uIcon
)
2429 std::map
<UINT
, HBITMAP
>::iterator bitmap_it
= bitmaps
.lower_bound(uIcon
);
2430 if (bitmap_it
!= bitmaps
.end() && bitmap_it
->first
== uIcon
)
2431 return bitmap_it
->second
;
2433 HICON hIcon
= (HICON
)LoadImage(g_hResInst
, MAKEINTRESOURCE(uIcon
), IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
2437 if (pfnBeginBufferedPaint
== NULL
|| pfnEndBufferedPaint
== NULL
|| pfnGetBufferedPaintBits
== NULL
)
2441 sizIcon
.cx
= GetSystemMetrics(SM_CXSMICON
);
2442 sizIcon
.cy
= GetSystemMetrics(SM_CYSMICON
);
2445 SetRect(&rcIcon
, 0, 0, sizIcon
.cx
, sizIcon
.cy
);
2446 HBITMAP hBmp
= NULL
;
2448 HDC hdcDest
= CreateCompatibleDC(NULL
);
2451 if (SUCCEEDED(Create32BitHBITMAP(hdcDest
, &sizIcon
, NULL
, &hBmp
)))
2453 HBITMAP hbmpOld
= (HBITMAP
)SelectObject(hdcDest
, hBmp
);
2456 BLENDFUNCTION bfAlpha
= { AC_SRC_OVER
, 0, 255, AC_SRC_ALPHA
};
2457 BP_PAINTPARAMS paintParams
= {0};
2458 paintParams
.cbSize
= sizeof(paintParams
);
2459 paintParams
.dwFlags
= BPPF_ERASE
;
2460 paintParams
.pBlendFunction
= &bfAlpha
;
2463 HPAINTBUFFER hPaintBuffer
= pfnBeginBufferedPaint(hdcDest
, &rcIcon
, BPBF_DIB
, &paintParams
, &hdcBuffer
);
2466 if (DrawIconEx(hdcBuffer
, 0, 0, hIcon
, sizIcon
.cx
, sizIcon
.cy
, 0, NULL
, DI_NORMAL
))
2468 // If icon did not have an alpha channel we need to convert buffer to PARGB
2469 ConvertBufferToPARGB32(hPaintBuffer
, hdcDest
, hIcon
, sizIcon
);
2472 // This will write the buffer contents to the destination bitmap
2473 pfnEndBufferedPaint(hPaintBuffer
, TRUE
);
2476 SelectObject(hdcDest
, hbmpOld
);
2486 bitmaps
.insert(bitmap_it
, std::make_pair(uIcon
, hBmp
));
2490 HRESULT
CShellExt::Create32BitHBITMAP(HDC hdc
, const SIZE
*psize
, __deref_opt_out
void **ppvBits
, __out HBITMAP
* phBmp
)
2495 ZeroMemory(&bmi
, sizeof(bmi
));
2496 bmi
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
2497 bmi
.bmiHeader
.biPlanes
= 1;
2498 bmi
.bmiHeader
.biCompression
= BI_RGB
;
2500 bmi
.bmiHeader
.biWidth
= psize
->cx
;
2501 bmi
.bmiHeader
.biHeight
= psize
->cy
;
2502 bmi
.bmiHeader
.biBitCount
= 32;
2504 HDC hdcUsed
= hdc
? hdc
: GetDC(NULL
);
2507 *phBmp
= CreateDIBSection(hdcUsed
, &bmi
, DIB_RGB_COLORS
, ppvBits
, NULL
, 0);
2510 ReleaseDC(NULL
, hdcUsed
);
2513 return (NULL
== *phBmp
) ? E_OUTOFMEMORY
: S_OK
;
2516 HRESULT
CShellExt::ConvertBufferToPARGB32(HPAINTBUFFER hPaintBuffer
, HDC hdc
, HICON hicon
, SIZE
& sizIcon
)
2520 HRESULT hr
= pfnGetBufferedPaintBits(hPaintBuffer
, &prgbQuad
, &cxRow
);
2523 ARGB
*pargb
= reinterpret_cast<ARGB
*>(prgbQuad
);
2524 if (!HasAlpha(pargb
, sizIcon
, cxRow
))
2527 if (GetIconInfo(hicon
, &info
))
2531 hr
= ConvertToPARGB32(hdc
, pargb
, info
.hbmMask
, sizIcon
, cxRow
);
2534 DeleteObject(info
.hbmColor
);
2535 DeleteObject(info
.hbmMask
);
2543 bool CShellExt::HasAlpha(__in ARGB
*pargb
, SIZE
& sizImage
, int cxRow
)
2545 ULONG cxDelta
= cxRow
- sizImage
.cx
;
2546 for (ULONG y
= sizImage
.cy
; y
; --y
)
2548 for (ULONG x
= sizImage
.cx
; x
; --x
)
2550 if (*pargb
++ & 0xFF000000)
2562 HRESULT
CShellExt::ConvertToPARGB32(HDC hdc
, __inout ARGB
*pargb
, HBITMAP hbmp
, SIZE
& sizImage
, int cxRow
)
2565 ZeroMemory(&bmi
, sizeof(bmi
));
2566 bmi
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
2567 bmi
.bmiHeader
.biPlanes
= 1;
2568 bmi
.bmiHeader
.biCompression
= BI_RGB
;
2570 bmi
.bmiHeader
.biWidth
= sizImage
.cx
;
2571 bmi
.bmiHeader
.biHeight
= sizImage
.cy
;
2572 bmi
.bmiHeader
.biBitCount
= 32;
2574 HRESULT hr
= E_OUTOFMEMORY
;
2575 HANDLE hHeap
= GetProcessHeap();
2576 void *pvBits
= HeapAlloc(hHeap
, 0, bmi
.bmiHeader
.biWidth
* 4 * bmi
.bmiHeader
.biHeight
);
2580 if (GetDIBits(hdc
, hbmp
, 0, bmi
.bmiHeader
.biHeight
, pvBits
, &bmi
, DIB_RGB_COLORS
) == bmi
.bmiHeader
.biHeight
)
2582 ULONG cxDelta
= cxRow
- bmi
.bmiHeader
.biWidth
;
2583 ARGB
*pargbMask
= static_cast<ARGB
*>(pvBits
);
2585 for (ULONG y
= bmi
.bmiHeader
.biHeight
; y
; --y
)
2587 for (ULONG x
= bmi
.bmiHeader
.biWidth
; x
; --x
)
2591 // transparent pixel
2597 *pargb
++ |= 0xFF000000;
2607 HeapFree(hHeap
, 0, pvBits
);