renamed ITEMIS_SUBMODULE to ITEMIS_SUBMODULECONTAINER
[TortoiseGit.git] / src / TortoiseShell / ContextMenu.cpp
blobb91e3e4a53a749251dbc4b82ee3035e7088fe0f2
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.
20 #include "stdafx.h"
21 #include "ShellExt.h"
22 #include "ItemIDList.h"
23 #include "PreserveChdir.h"
24 #include "UnicodeUtils.h"
25 //#include "GitProperties.h"
26 #include "GitStatus.h"
27 #include "TGitPath.h"
29 #define GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
30 #define GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
32 int g_shellidlist=RegisterClipboardFormat(CFSTR_SHELLIDLIST);
34 extern MenuInfo menuInfo[];
37 STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder,
38 LPDATAOBJECT pDataObj,
39 HKEY /* hRegKey */)
42 ATLTRACE("Shell :: Initialize\n");
43 PreserveChdir preserveChdir;
44 files_.clear();
45 folder_.erase();
46 uuidSource.erase();
47 uuidTarget.erase();
48 itemStates = 0;
49 itemStatesFolder = 0;
50 stdstring statuspath;
51 git_wc_status_kind fetchedstatus = git_wc_status_none;
52 // get selected files/folders
53 if (pDataObj)
55 STGMEDIUM medium;
56 FORMATETC fmte = {(CLIPFORMAT)g_shellidlist,
57 (DVTARGETDEVICE FAR *)NULL,
58 DVASPECT_CONTENT,
59 -1,
60 TYMED_HGLOBAL};
61 HRESULT hres = pDataObj->GetData(&fmte, &medium);
63 if (SUCCEEDED(hres) && medium.hGlobal)
65 if (m_State == FileStateDropHandler)
68 FORMATETC etc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
69 STGMEDIUM stg = { TYMED_HGLOBAL };
70 if ( FAILED( pDataObj->GetData ( &etc, &stg )))
72 ReleaseStgMedium ( &medium );
73 return E_INVALIDARG;
77 HDROP drop = (HDROP)GlobalLock(stg.hGlobal);
78 if ( NULL == drop )
80 ReleaseStgMedium ( &stg );
81 ReleaseStgMedium ( &medium );
82 return E_INVALIDARG;
85 int count = DragQueryFile(drop, (UINT)-1, NULL, 0);
86 if (count == 1)
87 itemStates |= ITEMIS_ONLYONE;
88 for (int i = 0; i < count; i++)
90 // find the path length in chars
91 UINT len = DragQueryFile(drop, i, NULL, 0);
92 if (len == 0)
93 continue;
94 TCHAR * szFileName = new TCHAR[len+1];
95 if (0 == DragQueryFile(drop, i, szFileName, len+1))
97 delete [] szFileName;
98 continue;
100 stdstring str = stdstring(szFileName);
101 delete [] szFileName;
102 if ((str.empty() == false)&&(g_ShellCache.IsContextPathAllowed(szFileName)))
104 if (itemStates & ITEMIS_ONLYONE)
106 CTGitPath strpath;
107 strpath.SetFromWin(str.c_str());
108 itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE : 0;
109 itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE : 0;
111 files_.push_back(str);
112 if (i == 0)
114 //get the Subversion status of the item
115 git_wc_status_kind status = git_wc_status_none;
116 CTGitPath askedpath;
117 askedpath.SetFromWin(str.c_str());
120 GitStatus stat;
121 stat.GetStatus(CTGitPath(str.c_str()), false, false, true);
122 if (stat.status)
124 statuspath = str;
125 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
126 fetchedstatus = status;
127 //if ((stat.status->entry)&&(stat.status->entry->lock_token))
128 // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
129 if ( askedpath.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
131 itemStates |= ITEMIS_FOLDER;
132 if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
133 itemStates |= ITEMIS_FOLDERINSVN;
135 //if ((stat.status->entry)&&(stat.status->entry->present_props))
137 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
138 // itemStates |= ITEMIS_NEEDSLOCK;
140 //if ((stat.status->entry)&&(stat.status->entry->uuid))
141 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
143 else
145 // sometimes, git_client_status() returns with an error.
146 // in that case, we have to check if the working copy is versioned
147 // anyway to show the 'correct' context menu
148 if (askedpath.HasAdminDir())
149 status = git_wc_status_normal;
152 catch ( ... )
154 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
157 // TODO: should we really assume any sub-directory to be versioned
158 // or only if it contains versioned files
159 itemStates |= askedpath.GetAdminDirMask();
161 if ((status == git_wc_status_unversioned) || (status == git_wc_status_ignored) || (status == git_wc_status_none))
162 itemStates &= ~ITEMIS_INSVN;
164 if (status == git_wc_status_ignored)
165 itemStates |= ITEMIS_IGNORED;
166 if (status == git_wc_status_normal)
167 itemStates |= ITEMIS_NORMAL;
168 if (status == git_wc_status_conflicted)
169 itemStates |= ITEMIS_CONFLICTED;
170 if (status == git_wc_status_added)
171 itemStates |= ITEMIS_ADDED;
172 if (status == git_wc_status_deleted)
173 itemStates |= ITEMIS_DELETED;
176 } // for (int i = 0; i < count; i++)
177 GlobalUnlock ( drop );
178 ReleaseStgMedium ( &stg );
180 } // if (m_State == FileStateDropHandler)
181 else
184 //Enumerate PIDLs which the user has selected
185 CIDA* cida = (CIDA*)GlobalLock(medium.hGlobal);
186 ItemIDList parent( GetPIDLFolder (cida));
188 int count = cida->cidl;
189 BOOL statfetched = FALSE;
190 for (int i = 0; i < count; ++i)
192 ItemIDList child (GetPIDLItem (cida, i), &parent);
193 stdstring str = child.toString();
194 if ((str.empty() == false)&&(g_ShellCache.IsContextPathAllowed(str.c_str())))
196 //check if our menu is requested for a subversion admin directory
197 if (g_GitAdminDir.IsAdminDirPath(str.c_str()))
198 continue;
200 files_.push_back(str);
201 CTGitPath strpath;
202 strpath.SetFromWin(str.c_str());
203 itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE : 0;
204 itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE : 0;
205 if (!statfetched)
207 //get the Subversion status of the item
208 git_wc_status_kind status = git_wc_status_none;
209 if ((g_ShellCache.IsSimpleContext())&&(strpath.IsDirectory()))
211 if (strpath.HasAdminDir())
212 status = git_wc_status_normal;
214 else
218 GitStatus stat;
219 if (strpath.HasAdminDir())
220 stat.GetStatus(strpath, false, false, true);
221 statuspath = str;
222 if (stat.status)
224 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
225 fetchedstatus = status;
226 //if ((stat.status->entry)&&(stat.status->entry->lock_token))
227 // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
228 if ( strpath.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
230 itemStates |= ITEMIS_FOLDER;
231 if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
232 itemStates |= ITEMIS_FOLDERINSVN;
234 // TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
235 if (status == git_wc_status_conflicted)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
236 itemStates |= ITEMIS_CONFLICTED;
237 //if ((stat.status->entry)&&(stat.status->entry->present_props))
239 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
240 // itemStates |= ITEMIS_NEEDSLOCK;
242 //if ((stat.status->entry)&&(stat.status->entry->uuid))
243 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
245 else
247 // sometimes, git_client_status() returns with an error.
248 // in that case, we have to check if the working copy is versioned
249 // anyway to show the 'correct' context menu
250 if (strpath.HasAdminDir())
252 status = git_wc_status_normal;
253 fetchedstatus = status;
256 statfetched = TRUE;
258 catch ( ... )
260 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
264 itemStates |= strpath.GetAdminDirMask();
266 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
267 itemStates &= ~ITEMIS_INSVN;
268 if (status == git_wc_status_ignored)
270 itemStates |= ITEMIS_IGNORED;
271 // the item is ignored. Get the svn:ignored properties so we can (maybe) later
272 // offer a 'remove from ignored list' entry
273 // GitProperties props(strpath.GetContainingDirectory(), false);
274 // ignoredprops.empty();
275 // for (int p=0; p<props.GetCount(); ++p)
276 // {
277 // if (props.GetItemName(p).compare(stdstring(_T("svn:ignore")))==0)
278 // {
279 // std::string st = props.GetItemValue(p);
280 // ignoredprops = MultibyteToWide(st.c_str());
281 // // remove all escape chars ('\\')
282 // std::remove(ignoredprops.begin(), ignoredprops.end(), '\\');
283 // break;
284 // }
285 // }
288 if (status == git_wc_status_normal)
289 itemStates |= ITEMIS_NORMAL;
290 if (status == git_wc_status_conflicted)
291 itemStates |= ITEMIS_CONFLICTED;
292 if (status == git_wc_status_added)
293 itemStates |= ITEMIS_ADDED;
294 if (status == git_wc_status_deleted)
295 itemStates |= ITEMIS_DELETED;
298 } // for (int i = 0; i < count; ++i)
299 ItemIDList child (GetPIDLItem (cida, 0), &parent);
300 if (g_ShellCache.HasSVNAdminDir(child.toString().c_str(), FALSE))
301 itemStates |= ITEMIS_INVERSIONEDFOLDER;
302 GlobalUnlock(medium.hGlobal);
304 // if the item is a versioned folder, check if there's a patch file
305 // in the clipboard to be used in "Apply Patch"
306 UINT cFormatDiff = RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
307 if (cFormatDiff)
309 if (IsClipboardFormatAvailable(cFormatDiff))
310 itemStates |= ITEMIS_PATCHINCLIPBOARD;
312 if (IsClipboardFormatAvailable(CF_HDROP))
313 itemStates |= ITEMIS_PATHINCLIPBOARD;
317 ReleaseStgMedium ( &medium );
318 if (medium.pUnkForRelease)
320 IUnknown* relInterface = (IUnknown*)medium.pUnkForRelease;
321 relInterface->Release();
326 // get folder background
327 if (pIDFolder)
330 ItemIDList list(pIDFolder);
331 folder_ = list.toString();
332 git_wc_status_kind status = git_wc_status_none;
333 if (IsClipboardFormatAvailable(CF_HDROP))
334 itemStatesFolder |= ITEMIS_PATHINCLIPBOARD;
336 CTGitPath askedpath;
337 askedpath.SetFromWin(folder_.c_str());
339 if (g_ShellCache.IsContextPathAllowed(folder_.c_str()))
341 if (folder_.compare(statuspath)!=0)
346 GitStatus stat;
347 stat.GetStatus(CTGitPath(folder_.c_str()), false, false, true);
348 if (stat.status)
350 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
351 // if ((stat.status->entry)&&(stat.status->entry->lock_token))
352 // itemStatesFolder |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
353 // if ((stat.status->entry)&&(stat.status->entry->present_props))
354 // {
355 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
356 // itemStatesFolder |= ITEMIS_NEEDSLOCK;
357 // }
358 // if ((stat.status->entry)&&(stat.status->entry->uuid))
359 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
362 else
364 // sometimes, git_client_status() returns with an error.
365 // in that case, we have to check if the working copy is versioned
366 // anyway to show the 'correct' context menu
367 if (askedpath.HasAdminDir())
368 status = git_wc_status_normal;
371 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
372 itemStatesFolder |= askedpath.GetAdminDirMask();
374 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
375 itemStates &= ~ITEMIS_INSVN;
377 if (status == git_wc_status_normal)
378 itemStatesFolder |= ITEMIS_NORMAL;
379 if (status == git_wc_status_conflicted)
380 itemStatesFolder |= ITEMIS_CONFLICTED;
381 if (status == git_wc_status_added)
382 itemStatesFolder |= ITEMIS_ADDED;
383 if (status == git_wc_status_deleted)
384 itemStatesFolder |= ITEMIS_DELETED;
387 catch ( ... )
389 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
392 else
394 status = fetchedstatus;
396 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
397 itemStatesFolder |= askedpath.GetAdminDirMask();
399 if (status == git_wc_status_ignored)
400 itemStatesFolder |= ITEMIS_IGNORED;
401 itemStatesFolder |= ITEMIS_FOLDER;
402 if (files_.size() == 0)
403 itemStates |= ITEMIS_ONLYONE;
404 if (m_State != FileStateDropHandler)
405 itemStates |= itemStatesFolder;
407 else
409 folder_.clear();
410 status = fetchedstatus;
413 if (files_.size() == 2)
414 itemStates |= ITEMIS_TWO;
415 if ((files_.size() == 1)&&(g_ShellCache.IsContextPathAllowed(files_.front().c_str())))
418 itemStates |= ITEMIS_ONLYONE;
419 if (m_State != FileStateDropHandler)
421 if (PathIsDirectory(files_.front().c_str()))
423 folder_ = files_.front();
424 git_wc_status_kind status = git_wc_status_none;
425 CTGitPath askedpath;
426 askedpath.SetFromWin(folder_.c_str());
428 if (folder_.compare(statuspath)!=0)
432 GitStatus stat;
433 stat.GetStatus(CTGitPath(folder_.c_str()), false, false, true);
434 if (stat.status)
436 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
437 // if ((stat.status->entry)&&(stat.status->entry->lock_token))
438 // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
439 // if ((stat.status->entry)&&(stat.status->entry->present_props))
440 // {
441 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
442 // itemStates |= ITEMIS_NEEDSLOCK;
443 // }
444 // if ((stat.status->entry)&&(stat.status->entry->uuid))
445 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
448 catch ( ... )
450 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
453 else
455 status = fetchedstatus;
457 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
458 itemStates |= askedpath.GetAdminDirMask();
460 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
461 itemStates &= ~ITEMIS_INSVN;
463 if (status == git_wc_status_ignored)
464 itemStates |= ITEMIS_IGNORED;
465 itemStates |= ITEMIS_FOLDER;
466 if (status == git_wc_status_added)
467 itemStates |= ITEMIS_ADDED;
468 if (status == git_wc_status_deleted)
469 itemStates |= ITEMIS_DELETED;
476 return NOERROR;
479 void CShellExt::InsertGitMenu(BOOL istop, HMENU menu, UINT pos, UINT_PTR id, UINT stringid, UINT icon, UINT idCmdFirst, GitCommands com, UINT uFlags)
481 TCHAR menutextbuffer[512] = {0};
482 TCHAR verbsbuffer[255] = {0};
483 MAKESTRING(stringid);
485 if (istop)
487 //menu entry for the top context menu, so append an "Git " before
488 //the menu text to indicate where the entry comes from
489 _tcscpy_s(menutextbuffer, 255, _T("Git "));
491 _tcscat_s(menutextbuffer, 255, stringtablebuffer);
492 #if 1
493 // insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
494 // so we have an easy and fast way to check the current branch
495 // (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
496 if (com == ShellMenuCommit)
498 // get branch name
499 CTGitPath path(folder_.empty() ? files_.front().c_str() : folder_.c_str());
500 CString sProjectRoot;
501 CString sBranchName;
503 if (path.HasAdminDir(&sProjectRoot) && !g_Git.GetCurrentBranchFromFile(sProjectRoot, sBranchName))
505 if (sBranchName.GetLength() == 40)
507 // if SHA1 only show 4 first bytes
508 BOOL bIsSha1 = TRUE;
509 for (int i=0; i<40; i++)
510 if ( !iswxdigit(sBranchName[i]) )
512 bIsSha1 = FALSE;
513 break;
515 if (bIsSha1)
516 sBranchName = sBranchName.Left(8) + _T("....");
519 // sanity check
520 if (sBranchName.GetLength() > 64)
521 sBranchName = sBranchName.Left(64) + _T("...");
523 // scan to before "..."
524 LPTSTR s = menutextbuffer + _tcslen(menutextbuffer)-1;
525 if (s > menutextbuffer)
527 while (s > menutextbuffer)
529 if (*s != _T('.'))
531 s++;
532 break;
534 s--;
537 else
539 s = menutextbuffer;
542 // append branch name and end with ...
543 _tcscpy(s, _T(" -> \"") + sBranchName + _T("\"..."));
546 #endif
547 if ((fullver < 0x500)||(fullver == 0x500 && !(uFlags&~(CMF_RESERVED|CMF_EXPLORE|CMF_EXTENDEDVERBS))))
549 // on win2k, the context menu does not work properly if we use
550 // icon bitmaps. At least the menu text is empty in the context menu
551 // for folder backgrounds (seems like a win2k bug).
552 // the workaround is to use the check/unchecked bitmaps, which are drawn
553 // with AND raster op, but it's better than nothing at all
554 InsertMenu(menu, pos, MF_BYPOSITION | MF_STRING , id, menutextbuffer);
555 if (icon)
557 HBITMAP bmp = IconToBitmap(icon);
558 SetMenuItemBitmaps(menu, pos, MF_BYPOSITION, bmp, bmp);
561 else
563 MENUITEMINFO menuiteminfo;
564 SecureZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
565 menuiteminfo.cbSize = sizeof(menuiteminfo);
566 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING;
567 menuiteminfo.fType = MFT_STRING;
568 menuiteminfo.dwTypeData = menutextbuffer;
569 if (icon)
571 menuiteminfo.fMask |= MIIM_BITMAP;
572 menuiteminfo.hbmpItem = (fullver >= 0x600) ? IconToBitmapPARGB32(icon) : HBMMENU_CALLBACK;
574 menuiteminfo.wID = id;
575 InsertMenuItem(menu, pos, TRUE, &menuiteminfo);
578 if (istop)
580 //menu entry for the top context menu, so append an "Git " before
581 //the menu text to indicate where the entry comes from
582 _tcscpy_s(menutextbuffer, 255, _T("Git "));
584 LoadString(g_hResInst, stringid, verbsbuffer, sizeof(verbsbuffer));
585 _tcscat_s(menutextbuffer, 255, verbsbuffer);
586 stdstring verb = stdstring(menutextbuffer);
587 if (verb.find('&') != -1)
589 verb.erase(verb.find('&'),1);
591 myVerbsMap[verb] = id - idCmdFirst;
592 myVerbsMap[verb] = id;
593 myVerbsIDMap[id - idCmdFirst] = verb;
594 myVerbsIDMap[id] = verb;
595 // We store the relative and absolute diameter
596 // (drawitem callback uses absolute, others relative)
597 myIDMap[id - idCmdFirst] = com;
598 myIDMap[id] = com;
599 if (!istop)
600 mySubMenuMap[pos] = com;
603 HBITMAP CShellExt::IconToBitmap(UINT uIcon)
605 std::map<UINT, HBITMAP>::iterator bitmap_it = bitmaps.lower_bound(uIcon);
606 if (bitmap_it != bitmaps.end() && bitmap_it->first == uIcon)
607 return bitmap_it->second;
609 HICON hIcon = (HICON)LoadImage(g_hResInst, MAKEINTRESOURCE(uIcon), IMAGE_ICON, 12, 12, LR_DEFAULTCOLOR);
610 if (!hIcon)
611 return NULL;
613 RECT rect;
615 rect.right = ::GetSystemMetrics(SM_CXMENUCHECK);
616 rect.bottom = ::GetSystemMetrics(SM_CYMENUCHECK);
618 rect.left = rect.top = 0;
620 HWND desktop = ::GetDesktopWindow();
621 if (desktop == NULL)
623 DestroyIcon(hIcon);
624 return NULL;
627 HDC screen_dev = ::GetDC(desktop);
628 if (screen_dev == NULL)
630 DestroyIcon(hIcon);
631 return NULL;
634 // Create a compatible DC
635 HDC dst_hdc = ::CreateCompatibleDC(screen_dev);
636 if (dst_hdc == NULL)
638 DestroyIcon(hIcon);
639 ::ReleaseDC(desktop, screen_dev);
640 return NULL;
643 // Create a new bitmap of icon size
644 HBITMAP bmp = ::CreateCompatibleBitmap(screen_dev, rect.right, rect.bottom);
645 if (bmp == NULL)
647 DestroyIcon(hIcon);
648 ::DeleteDC(dst_hdc);
649 ::ReleaseDC(desktop, screen_dev);
650 return NULL;
653 // Select it into the compatible DC
654 HBITMAP old_dst_bmp = (HBITMAP)::SelectObject(dst_hdc, bmp);
655 if (old_dst_bmp == NULL)
657 DestroyIcon(hIcon);
658 return NULL;
661 // Fill the background of the compatible DC with the white color
662 // that is taken by menu routines as transparent
663 ::SetBkColor(dst_hdc, RGB(255, 255, 255));
664 ::ExtTextOut(dst_hdc, 0, 0, ETO_OPAQUE, &rect, NULL, 0, NULL);
666 // Draw the icon into the compatible DC
667 ::DrawIconEx(dst_hdc, 0, 0, hIcon, rect.right, rect.bottom, 0, NULL, DI_NORMAL);
669 // Restore settings
670 ::SelectObject(dst_hdc, old_dst_bmp);
671 ::DeleteDC(dst_hdc);
672 ::ReleaseDC(desktop, screen_dev);
673 DestroyIcon(hIcon);
674 if (bmp)
675 bitmaps.insert(bitmap_it, std::make_pair(uIcon, bmp));
676 return bmp;
679 bool CShellExt::WriteClipboardPathsToTempFile(stdstring& tempfile)
681 bool bRet = true;
682 tempfile = stdstring();
683 //write all selected files and paths to a temporary file
684 //for TortoiseProc.exe to read out again.
685 DWORD written = 0;
686 DWORD pathlength = GetTempPath(0, NULL);
687 TCHAR * path = new TCHAR[pathlength+1];
688 TCHAR * tempFile = new TCHAR[pathlength + 100];
689 GetTempPath (pathlength+1, path);
690 GetTempFileName (path, _T("git"), 0, tempFile);
691 tempfile = stdstring(tempFile);
693 HANDLE file = ::CreateFile (tempFile,
694 GENERIC_WRITE,
695 FILE_SHARE_READ,
697 CREATE_ALWAYS,
698 FILE_ATTRIBUTE_TEMPORARY,
701 delete [] path;
702 delete [] tempFile;
703 if (file == INVALID_HANDLE_VALUE)
704 return false;
706 if (!IsClipboardFormatAvailable(CF_HDROP))
707 return false;
708 if (!OpenClipboard(NULL))
709 return false;
711 stdstring sClipboardText;
712 HGLOBAL hglb = GetClipboardData(CF_HDROP);
713 HDROP hDrop = (HDROP)GlobalLock(hglb);
714 if(hDrop != NULL)
716 TCHAR szFileName[MAX_PATH];
717 UINT cFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
718 for(UINT i = 0; i < cFiles; ++i)
720 DragQueryFile(hDrop, i, szFileName, sizeof(szFileName));
721 stdstring filename = szFileName;
722 ::WriteFile (file, filename.c_str(), filename.size()*sizeof(TCHAR), &written, 0);
723 ::WriteFile (file, _T("\n"), 2, &written, 0);
725 GlobalUnlock(hDrop);
727 else bRet = false;
728 GlobalUnlock(hglb);
730 CloseClipboard();
731 ::CloseHandle(file);
733 return bRet;
736 stdstring CShellExt::WriteFileListToTempFile()
738 //write all selected files and paths to a temporary file
739 //for TortoiseProc.exe to read out again.
740 DWORD pathlength = GetTempPath(0, NULL);
741 TCHAR * path = new TCHAR[pathlength+1];
742 TCHAR * tempFile = new TCHAR[pathlength + 100];
743 GetTempPath (pathlength+1, path);
744 GetTempFileName (path, _T("git"), 0, tempFile);
745 stdstring retFilePath = stdstring(tempFile);
747 HANDLE file = ::CreateFile (tempFile,
748 GENERIC_WRITE,
749 FILE_SHARE_READ,
751 CREATE_ALWAYS,
752 FILE_ATTRIBUTE_TEMPORARY,
755 delete [] path;
756 delete [] tempFile;
757 if (file == INVALID_HANDLE_VALUE)
758 return stdstring();
760 DWORD written = 0;
761 if (files_.empty())
763 ::WriteFile (file, folder_.c_str(), folder_.size()*sizeof(TCHAR), &written, 0);
764 ::WriteFile (file, _T("\n"), 2, &written, 0);
767 for (std::vector<stdstring>::iterator I = files_.begin(); I != files_.end(); ++I)
769 ::WriteFile (file, I->c_str(), I->size()*sizeof(TCHAR), &written, 0);
770 ::WriteFile (file, _T("\n"), 2, &written, 0);
772 ::CloseHandle(file);
773 return retFilePath;
776 STDMETHODIMP CShellExt::QueryDropContext(UINT uFlags, UINT idCmdFirst, HMENU hMenu, UINT &indexMenu)
778 PreserveChdir preserveChdir;
779 LoadLangDll();
781 if ((uFlags & CMF_DEFAULTONLY)!=0)
782 return NOERROR; //we don't change the default action
784 if ((files_.size() == 0)||(folder_.size() == 0))
785 return NOERROR;
787 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
788 return NOERROR;
790 bool bSourceAndTargetFromSameRepository = (uuidSource.compare(uuidTarget) == 0) || uuidSource.empty() || uuidTarget.empty();
792 //the drop handler only has eight commands, but not all are visible at the same time:
793 //if the source file(s) are under version control then those files can be moved
794 //to the new location or they can be moved with a rename,
795 //if they are unversioned then they can be added to the working copy
796 //if they are versioned, they also can be exported to an unversioned location
797 UINT idCmd = idCmdFirst;
799 // Git move here
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_FOLDERINSVN)&&((itemStates & ITEMIS_INSVN)&&((~itemStates) & ITEMIS_ADDED)))
802 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVEMENU, 0, idCmdFirst, ShellMenuDropMove, uFlags);
804 // Git move and rename here
805 // 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
806 if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINSVN)&&(itemStates & ITEMIS_INSVN)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
807 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVERENAMEMENU, 0, idCmdFirst, ShellMenuDropMoveRename, uFlags);
809 // Git copy here
810 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
811 if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINSVN)&&(itemStates & ITEMIS_INSVN)&&((~itemStates) & ITEMIS_ADDED))
812 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYMENU, 0, idCmdFirst, ShellMenuDropCopy, uFlags);
814 // Git copy and rename here, source and target from same repository
815 // available if source is a single, versioned but not added item, target is versioned or target folder is added
816 if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINSVN)&&(itemStates & ITEMIS_INSVN)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
817 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYRENAMEMENU, 0, idCmdFirst, ShellMenuDropCopyRename, uFlags);
819 // Git add here
820 // available if target is versioned and source is either unversioned or from another repository
821 if ((itemStatesFolder & ITEMIS_FOLDERINSVN)&&(((~itemStates) & ITEMIS_INSVN)||!bSourceAndTargetFromSameRepository))
822 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYADDMENU, 0, idCmdFirst, ShellMenuDropCopyAdd, uFlags);
824 // Git export here
825 // available if source is versioned and a folder
826 if ((itemStates & ITEMIS_INSVN)&&(itemStates & ITEMIS_FOLDER))
827 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
829 // Git export all here
830 // available if source is versioned and a folder
831 if ((itemStates & ITEMIS_INSVN)&&(itemStates & ITEMIS_FOLDER))
832 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
834 // apply patch
835 // available if source is a patchfile
836 if (itemStates & ITEMIS_PATCHFILE)
837 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_MENUAPPLYPATCH, 0, idCmdFirst, ShellMenuApplyPatch, uFlags);
839 // separator
840 if (idCmd != idCmdFirst)
841 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
843 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
846 STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu,
847 UINT indexMenu,
848 UINT idCmdFirst,
849 UINT /*idCmdLast*/,
850 UINT uFlags)
852 ATLTRACE("Shell :: QueryContextMenu\n");
853 PreserveChdir preserveChdir;
855 //first check if our drop handler is called
856 //and then (if true) provide the context menu for the
857 //drop handler
858 if (m_State == FileStateDropHandler)
860 return QueryDropContext(uFlags, idCmdFirst, hMenu, indexMenu);
863 if ((uFlags & CMF_DEFAULTONLY)!=0)
864 return NOERROR; //we don't change the default action
866 if ((files_.size() == 0)&&(folder_.size() == 0))
867 return NOERROR;
869 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
870 return NOERROR;
872 int csidlarray[] =
874 CSIDL_BITBUCKET,
875 CSIDL_CDBURN_AREA,
876 CSIDL_COMMON_FAVORITES,
877 CSIDL_COMMON_STARTMENU,
878 CSIDL_COMPUTERSNEARME,
879 CSIDL_CONNECTIONS,
880 CSIDL_CONTROLS,
881 CSIDL_COOKIES,
882 CSIDL_FAVORITES,
883 CSIDL_FONTS,
884 CSIDL_HISTORY,
885 CSIDL_INTERNET,
886 CSIDL_INTERNET_CACHE,
887 CSIDL_NETHOOD,
888 CSIDL_NETWORK,
889 CSIDL_PRINTERS,
890 CSIDL_PRINTHOOD,
891 CSIDL_RECENT,
892 CSIDL_SENDTO,
893 CSIDL_STARTMENU,
896 if (IsIllegalFolder(folder_, csidlarray))
897 return NOERROR;
899 if (folder_.empty())
901 // folder is empty, but maybe files are selected
902 if (files_.size() == 0)
903 return NOERROR; // nothing selected - we don't have a menu to show
904 // check whether a selected entry is an UID - those are namespace extensions
905 // which we can't handle
906 for (std::vector<stdstring>::const_iterator it = files_.begin(); it != files_.end(); ++it)
908 if (_tcsncmp(it->c_str(), _T("::{"), 3)==0)
909 return NOERROR;
913 if (((uFlags & CMF_EXTENDEDVERBS) == 0) && g_ShellCache.HideMenusForUnversionedItems())
915 if ((itemStates & (ITEMIS_INSVN|ITEMIS_INVERSIONEDFOLDER|ITEMIS_FOLDERINSVN))==0)
916 return S_OK;
919 //check if our menu is requested for a subversion admin directory
920 if (g_GitAdminDir.IsAdminDirPath(folder_.c_str()))
921 return NOERROR;
923 if (uFlags & CMF_EXTENDEDVERBS)
924 itemStates |= ITEMIS_EXTENDED;
926 const BOOL bShortcut = !!(uFlags & CMF_VERBSONLY);
927 if ( bShortcut && (files_.size()==1))
929 // Don't show the context menu for a link if the
930 // destination is not part of a working copy.
931 // It would only show the standard menu items
932 // which are already shown for the lnk-file.
933 CString path = files_.front().c_str();
934 if ( !g_GitAdminDir.HasAdminDir(path) )
936 return NOERROR;
940 //check if we already added our menu entry for a folder.
941 //we check that by iterating through all menu entries and check if
942 //the dwItemData member points to our global ID string. That string is set
943 //by our shell extension when the folder menu is inserted.
944 TCHAR menubuf[MAX_PATH];
945 int count = GetMenuItemCount(hMenu);
946 for (int i=0; i<count; ++i)
948 MENUITEMINFO miif;
949 SecureZeroMemory(&miif, sizeof(MENUITEMINFO));
950 miif.cbSize = sizeof(MENUITEMINFO);
951 miif.fMask = MIIM_DATA;
952 miif.dwTypeData = menubuf;
953 miif.cch = _countof(menubuf);
954 GetMenuItemInfo(hMenu, i, TRUE, &miif);
955 if (miif.dwItemData == (ULONG_PTR)g_MenuIDString)
956 return NOERROR;
959 LoadLangDll();
960 UINT idCmd = idCmdFirst;
962 //create the sub menu
963 HMENU subMenu = CreateMenu();
964 int indexSubMenu = 0;
966 unsigned __int64 topmenu = g_ShellCache.GetMenuLayout();
967 unsigned __int64 menumask = g_ShellCache.GetMenuMask();
968 unsigned __int64 menuex = g_ShellCache.GetMenuExt();
970 int menuIndex = 0;
971 bool bAddSeparator = false;
972 bool bMenuEntryAdded = false;
973 bool bMenuEmpty = true;
974 // insert separator at start
975 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL); idCmd++;
976 bool bShowIcons = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\ShowContextMenuIcons"), TRUE));
977 // ?? TSV disabled icons for win2k and earlier, but they work for win2k and should work for win95 and up
978 /*if (fullver <= 0x500)
979 bShowIcons = false;*/
981 #if 0
982 if (itemStates & (ITEMIS_INSVN|ITEMIS_FOLDERINSVN))
984 // show current branch name (as a "read-only" menu entry)
986 CTGitPath path(folder_.empty() ? files_.front().c_str() : folder_.c_str());
987 CString sProjectRoot;
988 CString sBranchName;
990 if (path.HasAdminDir(&sProjectRoot) && !g_Git.GetCurrentBranchFromFile(sProjectRoot, sBranchName))
992 if (sBranchName.GetLength() == 40)
994 // if SHA1 only show 4 first bytes
995 BOOL bIsSha1 = TRUE;
996 for (int i=0; i<40; i++)
997 if ( !iswxdigit(sBranchName[i]) )
999 bIsSha1 = FALSE;
1000 break;
1002 if (bIsSha1)
1003 sBranchName = sBranchName.Left(8) + _T("....");
1006 sBranchName = _T('"') + sBranchName + _T('"');
1008 const int icon = IDI_COPY;
1009 const int pos = indexMenu++;
1010 const int id = idCmd++;
1012 if ((fullver < 0x500)||(fullver == 0x500 && !(uFlags&~(CMF_RESERVED|CMF_EXPLORE|CMF_EXTENDEDVERBS))))
1014 InsertMenu(hMenu, pos, MF_DISABLED|MF_GRAYED|MF_BYPOSITION|MF_STRING, id, sBranchName);
1015 HBITMAP bmp = IconToBitmap(icon);
1016 SetMenuItemBitmaps(hMenu, pos, MF_BYPOSITION, bmp, bmp);
1018 else
1020 MENUITEMINFO menuiteminfo;
1021 SecureZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
1022 menuiteminfo.cbSize = sizeof(menuiteminfo);
1023 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING | MIIM_STATE;
1024 menuiteminfo.fState = MFS_DISABLED;
1025 menuiteminfo.fType = MFT_STRING;
1026 menuiteminfo.dwTypeData = (LPWSTR)sBranchName.GetString();
1027 if (icon)
1029 menuiteminfo.fMask |= MIIM_BITMAP;
1030 menuiteminfo.hbmpItem = (fullver >= 0x600) ? IconToBitmapPARGB32(icon) : HBMMENU_CALLBACK;
1032 if (menuiteminfo.hbmpItem == HBMMENU_CALLBACK)
1034 // WM_DRAWITEM uses myIDMap to get icon, we use the same icon as create branch
1035 myIDMap[id - idCmdFirst] = ShellMenuBranch;
1036 myIDMap[id] = ShellMenuBranch;
1039 menuiteminfo.wID = id;
1040 InsertMenuItem(hMenu, pos, TRUE, &menuiteminfo);
1044 #endif
1046 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1048 if (menuInfo[menuIndex].command == ShellSeparator)
1050 // we don't add a separator immediately. Because there might not be
1051 // another 'normal' menu entry after we insert a separator.
1052 // we simply set a flag here, indicating that before the next
1053 // 'normal' menu entry, a separator should be added.
1054 if (!bMenuEmpty)
1055 bAddSeparator = true;
1057 else
1059 // check the conditions whether to show the menu entry or not
1060 bool bInsertMenu = false;
1062 if (menuInfo[menuIndex].firstyes && menuInfo[menuIndex].firstno)
1064 if (((menuInfo[menuIndex].firstyes & itemStates) == menuInfo[menuIndex].firstyes)
1066 ((menuInfo[menuIndex].firstno & (~itemStates)) == menuInfo[menuIndex].firstno))
1067 bInsertMenu = true;
1069 else if ((menuInfo[menuIndex].firstyes)&&((menuInfo[menuIndex].firstyes & itemStates) == menuInfo[menuIndex].firstyes))
1070 bInsertMenu = true;
1071 else if ((menuInfo[menuIndex].firstno)&&((menuInfo[menuIndex].firstno & (~itemStates)) == menuInfo[menuIndex].firstno))
1072 bInsertMenu = true;
1074 if (menuInfo[menuIndex].secondyes && menuInfo[menuIndex].secondno)
1076 if (((menuInfo[menuIndex].secondyes & itemStates) == menuInfo[menuIndex].secondyes)
1078 ((menuInfo[menuIndex].secondno & (~itemStates)) == menuInfo[menuIndex].secondno))
1079 bInsertMenu = true;
1081 else if ((menuInfo[menuIndex].secondyes)&&((menuInfo[menuIndex].secondyes & itemStates) == menuInfo[menuIndex].secondyes))
1082 bInsertMenu = true;
1083 else if ((menuInfo[menuIndex].secondno)&&((menuInfo[menuIndex].secondno & (~itemStates)) == menuInfo[menuIndex].secondno))
1084 bInsertMenu = true;
1086 if (menuInfo[menuIndex].thirdyes && menuInfo[menuIndex].thirdno)
1088 if (((menuInfo[menuIndex].thirdyes & itemStates) == menuInfo[menuIndex].thirdyes)
1090 ((menuInfo[menuIndex].thirdno & (~itemStates)) == menuInfo[menuIndex].thirdno))
1091 bInsertMenu = true;
1093 else if ((menuInfo[menuIndex].thirdyes)&&((menuInfo[menuIndex].thirdyes & itemStates) == menuInfo[menuIndex].thirdyes))
1094 bInsertMenu = true;
1095 else if ((menuInfo[menuIndex].thirdno)&&((menuInfo[menuIndex].thirdno & (~itemStates)) == menuInfo[menuIndex].thirdno))
1096 bInsertMenu = true;
1098 if (menuInfo[menuIndex].fourthyes && menuInfo[menuIndex].fourthno)
1100 if (((menuInfo[menuIndex].fourthyes & itemStates) == menuInfo[menuIndex].fourthyes)
1102 ((menuInfo[menuIndex].fourthno & (~itemStates)) == menuInfo[menuIndex].fourthno))
1103 bInsertMenu = true;
1105 else if ((menuInfo[menuIndex].fourthyes)&&((menuInfo[menuIndex].fourthyes & itemStates) == menuInfo[menuIndex].fourthyes))
1106 bInsertMenu = true;
1107 else if ((menuInfo[menuIndex].fourthno)&&((menuInfo[menuIndex].fourthno & (~itemStates)) == menuInfo[menuIndex].fourthno))
1108 bInsertMenu = true;
1110 if (menuInfo[menuIndex].menuID & menuex)
1112 if( !(itemStates & ITEMIS_EXTENDED) )
1114 bInsertMenu = false;
1118 if (menuInfo[menuIndex].menuID & (~menumask))
1120 if (bInsertMenu)
1122 bool bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1123 // insert a separator
1124 if ((bMenuEntryAdded)&&(bAddSeparator)&&(!bIsTop))
1126 bAddSeparator = false;
1127 bMenuEntryAdded = false;
1128 InsertMenu(subMenu, indexSubMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
1129 idCmd++;
1132 // handle special cases (sub menus)
1133 if ((menuInfo[menuIndex].command == ShellMenuIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuUnIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuDeleteIgnoreSub))
1135 if(InsertIgnoreSubmenus(idCmd, idCmdFirst, hMenu, subMenu, indexMenu, indexSubMenu, topmenu, bShowIcons, uFlags))
1137 bMenuEntryAdded = true;
1138 bMenuEmpty = false;
1141 else
1143 bool bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1145 // insert the menu entry
1146 InsertGitMenu( bIsTop,
1147 bIsTop ? hMenu : subMenu,
1148 bIsTop ? indexMenu++ : indexSubMenu++,
1149 idCmd++,
1150 menuInfo[menuIndex].menuTextID,
1151 bShowIcons ? menuInfo[menuIndex].iconID : 0,
1152 idCmdFirst,
1153 menuInfo[menuIndex].command,
1154 uFlags);
1155 if (!bIsTop)
1157 bMenuEntryAdded = true;
1158 bMenuEmpty = false;
1159 bAddSeparator = false;
1165 menuIndex++;
1168 //add sub menu to main context menu
1169 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1170 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1171 MAKESTRING(IDS_MENUSUBMENU);
1172 MENUITEMINFO menuiteminfo;
1173 SecureZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
1174 menuiteminfo.cbSize = sizeof(menuiteminfo);
1175 menuiteminfo.fType = MFT_STRING;
1176 menuiteminfo.dwTypeData = stringtablebuffer;
1178 UINT uIcon = bShowIcons ? IDI_APP : 0;
1179 if (folder_.size())
1181 uIcon = bShowIcons ? IDI_MENUFOLDER : 0;
1182 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFolder;
1183 myIDMap[idCmd] = ShellSubMenuFolder;
1184 menuiteminfo.dwItemData = (ULONG_PTR)g_MenuIDString;
1186 else if (!bShortcut && (files_.size()==1))
1188 uIcon = bShowIcons ? IDI_MENUFILE : 0;
1189 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFile;
1190 myIDMap[idCmd] = ShellSubMenuFile;
1192 else if (bShortcut && (files_.size()==1))
1194 uIcon = bShowIcons ? IDI_MENULINK : 0;
1195 myIDMap[idCmd - idCmdFirst] = ShellSubMenuLink;
1196 myIDMap[idCmd] = ShellSubMenuLink;
1198 else if (files_.size() > 1)
1200 uIcon = bShowIcons ? IDI_MENUMULTIPLE : 0;
1201 myIDMap[idCmd - idCmdFirst] = ShellSubMenuMultiple;
1202 myIDMap[idCmd] = ShellSubMenuMultiple;
1204 else
1206 myIDMap[idCmd - idCmdFirst] = ShellSubMenu;
1207 myIDMap[idCmd] = ShellSubMenu;
1209 HBITMAP bmp = NULL;
1210 if ((fullver < 0x500)||(fullver == 0x500 && !(uFlags&~(CMF_RESERVED|CMF_EXPLORE|CMF_EXTENDEDVERBS))))
1212 menuiteminfo.fMask = MIIM_STRING | MIIM_ID | MIIM_SUBMENU | MIIM_DATA;
1213 if (uIcon)
1215 menuiteminfo.fMask |= MIIM_CHECKMARKS;
1216 bmp = IconToBitmap(uIcon);
1217 menuiteminfo.hbmpChecked = bmp;
1218 menuiteminfo.hbmpUnchecked = bmp;
1221 else
1223 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
1224 if (uIcon)
1226 menuiteminfo.fMask |= MIIM_BITMAP;
1227 menuiteminfo.hbmpItem = (fullver >= 0x600) ? IconToBitmapPARGB32(uIcon) : HBMMENU_CALLBACK;
1230 menuiteminfo.hSubMenu = subMenu;
1231 menuiteminfo.wID = idCmd++;
1232 InsertMenuItem(hMenu, indexMenu++, TRUE, &menuiteminfo);
1234 //separator after
1235 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL); idCmd++;
1237 //return number of menu items added
1238 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
1242 // This is called when you invoke a command on the menu:
1243 STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
1245 PreserveChdir preserveChdir;
1246 HRESULT hr = E_INVALIDARG;
1247 if (lpcmi == NULL)
1248 return hr;
1250 std::string command;
1251 std::string parent;
1252 std::string file;
1254 if ((files_.size() > 0)||(folder_.size() > 0))
1256 UINT idCmd = LOWORD(lpcmi->lpVerb);
1258 if (HIWORD(lpcmi->lpVerb))
1260 stdstring verb = stdstring(MultibyteToWide(lpcmi->lpVerb));
1261 std::map<stdstring, UINT_PTR>::const_iterator verb_it = myVerbsMap.lower_bound(verb);
1262 if (verb_it != myVerbsMap.end() && verb_it->first == verb)
1263 idCmd = verb_it->second;
1264 else
1265 return hr;
1268 // See if we have a handler interface for this id
1269 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
1270 if (id_it != myIDMap.end() && id_it->first == idCmd)
1272 STARTUPINFO startup;
1273 PROCESS_INFORMATION process;
1274 memset(&startup, 0, sizeof(startup));
1275 startup.cb = sizeof(startup);
1276 memset(&process, 0, sizeof(process));
1277 CRegStdString tortoiseProcPath(_T("Software\\TortoiseGit\\ProcPath"), _T("TortoiseProc.exe"), false, HKEY_LOCAL_MACHINE);
1278 CRegStdString tortoiseMergePath(_T("Software\\TortoiseGit\\TMergePath"), _T("TortoiseMerge.exe"), false, HKEY_LOCAL_MACHINE);
1280 //TortoiseProc expects a command line of the form:
1281 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1282 // or
1283 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1285 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1286 //* pathfile is a path to a temporary file which contains a list of file paths
1287 stdstring svnCmd = _T(" /command:");
1288 stdstring tempfile;
1289 switch (id_it->second)
1291 //#region case
1292 case ShellMenuSync:
1293 svnCmd += _T("sync /path:\"");
1294 if (files_.size() > 0)
1295 svnCmd += files_.front();
1296 else
1297 svnCmd += folder_;
1298 svnCmd += _T("\"");
1299 break;
1300 case ShellMenuCheckout:
1301 svnCmd += _T("checkout /path:\"");
1302 if (files_.size() > 0)
1303 svnCmd += files_.front();
1304 else
1305 svnCmd += folder_;
1306 svnCmd += _T("\"");
1307 break;
1308 case ShellMenuUpdate:
1309 tempfile = WriteFileListToTempFile();
1310 svnCmd += _T("update /pathfile:\"");
1311 svnCmd += tempfile;
1312 svnCmd += _T("\"");
1313 svnCmd += _T(" /deletepathfile");
1314 break;
1315 case ShellMenuSubSync:
1316 tempfile = WriteFileListToTempFile();
1317 svnCmd += _T("subsync /pathfile:\"");
1318 svnCmd += tempfile;
1319 svnCmd += _T("\"");
1320 svnCmd += _T(" /deletepathfile");
1321 if(itemStatesFolder&ITEMIS_SUBMODULECONTAINER)
1323 svnCmd += _T(" /bkpath:\"");
1324 svnCmd += folder_;
1325 svnCmd += _T("\"");
1327 break;
1328 case ShellMenuUpdateExt:
1329 tempfile = WriteFileListToTempFile();
1330 svnCmd += _T("subupdate /pathfile:\"");
1331 svnCmd += tempfile;
1332 svnCmd += _T("\"");
1333 svnCmd += _T(" /deletepathfile");
1334 if(itemStatesFolder&ITEMIS_SUBMODULECONTAINER)
1336 svnCmd += _T(" /bkpath:\"");
1337 svnCmd += folder_;
1338 svnCmd += _T("\"");
1340 break;
1341 case ShellMenuCommit:
1342 tempfile = WriteFileListToTempFile();
1343 svnCmd += _T("commit /pathfile:\"");
1344 svnCmd += tempfile;
1345 svnCmd += _T("\"");
1346 svnCmd += _T(" /deletepathfile");
1347 break;
1348 case ShellMenuAdd:
1349 case ShellMenuAddAsReplacement:
1350 tempfile = WriteFileListToTempFile();
1351 svnCmd += _T("add /pathfile:\"");
1352 svnCmd += tempfile;
1353 svnCmd += _T("\"");
1354 svnCmd += _T(" /deletepathfile");
1355 break;
1356 case ShellMenuIgnore:
1357 tempfile = WriteFileListToTempFile();
1358 svnCmd += _T("ignore /pathfile:\"");
1359 svnCmd += tempfile;
1360 svnCmd += _T("\"");
1361 svnCmd += _T(" /deletepathfile");
1362 break;
1363 case ShellMenuIgnoreCaseSensitive:
1364 tempfile = WriteFileListToTempFile();
1365 svnCmd += _T("ignore /pathfile:\"");
1366 svnCmd += tempfile;
1367 svnCmd += _T("\"");
1368 svnCmd += _T(" /deletepathfile");
1369 svnCmd += _T(" /onlymask");
1370 break;
1371 case ShellMenuDeleteIgnore:
1372 tempfile = WriteFileListToTempFile();
1373 svnCmd += _T("ignore /delete /pathfile:\"");
1374 svnCmd += tempfile;
1375 svnCmd += _T("\"");
1376 svnCmd += _T(" /deletepathfile");
1377 break;
1378 case ShellMenuDeleteIgnoreCaseSensitive:
1379 tempfile = WriteFileListToTempFile();
1380 svnCmd += _T("ignore /delete /pathfile:\"");
1381 svnCmd += tempfile;
1382 svnCmd += _T("\"");
1383 svnCmd += _T(" /deletepathfile");
1384 svnCmd += _T(" /onlymask");
1385 break;
1386 case ShellMenuUnIgnore:
1387 tempfile = WriteFileListToTempFile();
1388 svnCmd += _T("unignore /pathfile:\"");
1389 svnCmd += tempfile;
1390 svnCmd += _T("\"");
1391 svnCmd += _T(" /deletepathfile");
1392 break;
1393 case ShellMenuUnIgnoreCaseSensitive:
1394 tempfile = WriteFileListToTempFile();
1395 svnCmd += _T("unignore /pathfile:\"");
1396 svnCmd += tempfile;
1397 svnCmd += _T("\"");
1398 svnCmd += _T(" /deletepathfile");
1399 svnCmd += _T(" /onlymask");
1400 break;
1401 case ShellMenuRevert:
1402 tempfile = WriteFileListToTempFile();
1403 svnCmd += _T("revert /pathfile:\"");
1404 svnCmd += tempfile;
1405 svnCmd += _T("\"");
1406 svnCmd += _T(" /deletepathfile");
1407 break;
1408 case ShellMenuDelUnversioned:
1409 svnCmd += _T("delunversioned /path:\"");
1410 svnCmd += folder_;
1411 svnCmd += _T("\"");
1412 break;
1413 case ShellMenuCleanup:
1414 tempfile = WriteFileListToTempFile();
1415 svnCmd += _T("cleanup /pathfile:\"");
1416 svnCmd += tempfile;
1417 svnCmd += _T("\"");
1418 svnCmd += _T(" /deletepathfile");
1419 break;
1420 case ShellMenuSendMail:
1421 tempfile = WriteFileListToTempFile();
1422 svnCmd += _T("sendmail /pathfile:\"");
1423 svnCmd += tempfile;
1424 svnCmd += _T("\"");
1425 svnCmd += _T(" /deletepathfile");
1426 break;
1427 case ShellMenuResolve:
1428 tempfile = WriteFileListToTempFile();
1429 svnCmd += _T("resolve /pathfile:\"");
1430 svnCmd += tempfile;
1431 svnCmd += _T("\"");
1432 svnCmd += _T(" /deletepathfile");
1433 break;
1434 case ShellMenuSwitch:
1435 svnCmd += _T("switch /path:\"");
1436 if (files_.size() > 0)
1437 svnCmd += files_.front();
1438 else
1439 svnCmd += folder_;
1440 svnCmd += _T("\"");
1441 break;
1442 case ShellMenuImport:
1443 svnCmd += _T("import /path:\"");
1444 if (files_.size() > 0)
1445 svnCmd += files_.front();
1446 else
1447 svnCmd += folder_;
1448 svnCmd += _T("\"");
1449 break;
1450 case ShellMenuExport:
1451 svnCmd += _T("export /path:\"");
1452 if (files_.size() > 0)
1453 svnCmd += files_.front();
1454 else
1455 svnCmd += folder_;
1456 svnCmd += _T("\"");
1457 break;
1458 case ShellMenuAbout:
1459 svnCmd += _T("about");
1460 break;
1461 case ShellMenuCreateRepos:
1462 svnCmd += _T("repocreate /path:\"");
1463 if (files_.size() > 0)
1464 svnCmd += files_.front();
1465 else
1466 svnCmd += folder_;
1467 svnCmd += _T("\"");
1468 break;
1469 case ShellMenuMerge:
1470 svnCmd += _T("merge /path:\"");
1471 if (files_.size() > 0)
1472 svnCmd += files_.front();
1473 else
1474 svnCmd += folder_;
1475 svnCmd += _T("\"");
1476 break;
1477 case ShellMenuMergeAll:
1478 svnCmd += _T("mergeall /path:\"");
1479 if (files_.size() > 0)
1480 svnCmd += files_.front();
1481 else
1482 svnCmd += folder_;
1483 svnCmd += _T("\"");
1484 break;
1485 case ShellMenuCopy:
1486 svnCmd += _T("copy /path:\"");
1487 if (files_.size() > 0)
1488 svnCmd += files_.front();
1489 else
1490 svnCmd += folder_;
1491 svnCmd += _T("\"");
1492 break;
1493 case ShellMenuSettings:
1494 svnCmd += _T("settings /path:\"");
1495 if (files_.size() > 0)
1496 svnCmd += files_.front();
1497 else
1498 svnCmd += folder_;
1499 svnCmd += _T("\"");
1500 break;
1501 case ShellMenuHelp:
1502 svnCmd += _T("help");
1503 break;
1504 case ShellMenuRename:
1505 svnCmd += _T("rename /path:\"");
1506 if (files_.size() > 0)
1507 svnCmd += files_.front();
1508 else
1509 svnCmd += folder_;
1510 svnCmd += _T("\"");
1511 break;
1512 case ShellMenuRemove:
1513 tempfile = WriteFileListToTempFile();
1514 svnCmd += _T("remove /pathfile:\"");
1515 svnCmd += tempfile;
1516 svnCmd += _T("\"");
1517 svnCmd += _T(" /deletepathfile");
1518 break;
1519 case ShellMenuRemoveKeep:
1520 tempfile = WriteFileListToTempFile();
1521 svnCmd += _T("remove /pathfile:\"");
1522 svnCmd += tempfile;
1523 svnCmd += _T("\"");
1524 svnCmd += _T(" /deletepathfile");
1525 svnCmd += _T(" /keep");
1526 break;
1527 case ShellMenuDiff:
1528 svnCmd += _T("diff /path:\"");
1529 if (files_.size() == 1)
1530 svnCmd += files_.front();
1531 else if (files_.size() == 2)
1533 std::vector<stdstring>::iterator I = files_.begin();
1534 svnCmd += *I;
1535 I++;
1536 svnCmd += _T("\" /path2:\"");
1537 svnCmd += *I;
1539 else
1540 svnCmd += folder_;
1541 svnCmd += _T("\"");
1542 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1543 svnCmd += _T(" /alternative");
1544 break;
1545 case ShellMenuPrevDiff:
1546 svnCmd += _T("prevdiff /path:\"");
1547 if (files_.size() == 1)
1548 svnCmd += files_.front();
1549 else
1550 svnCmd += folder_;
1551 svnCmd += _T("\"");
1552 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1553 svnCmd += _T(" /alternative");
1554 break;
1555 case ShellMenuUrlDiff:
1556 svnCmd += _T("urldiff /path:\"");
1557 if (files_.size() == 1)
1558 svnCmd += files_.front();
1559 else
1560 svnCmd += folder_;
1561 svnCmd += _T("\"");
1562 break;
1563 case ShellMenuDiffTwo:
1564 svnCmd += _T("diffcommits /path:\"");
1565 if (files_.size() == 1)
1566 svnCmd += files_.front();
1567 else
1568 svnCmd += folder_;
1569 svnCmd += _T("\"");
1570 break;
1571 case ShellMenuDropCopyAdd:
1572 tempfile = WriteFileListToTempFile();
1573 svnCmd += _T("dropcopyadd /pathfile:\"");
1574 svnCmd += tempfile;
1575 svnCmd += _T("\"");
1576 svnCmd += _T(" /deletepathfile");
1577 svnCmd += _T(" /droptarget:\"");
1578 svnCmd += folder_;
1579 svnCmd += _T("\"";)
1580 break;
1581 case ShellMenuDropCopy:
1582 tempfile = WriteFileListToTempFile();
1583 svnCmd += _T("dropcopy /pathfile:\"");
1584 svnCmd += tempfile;
1585 svnCmd += _T("\"");
1586 svnCmd += _T(" /deletepathfile");
1587 svnCmd += _T(" /droptarget:\"");
1588 svnCmd += folder_;
1589 svnCmd += _T("\"";)
1590 break;
1591 case ShellMenuDropCopyRename:
1592 tempfile = WriteFileListToTempFile();
1593 svnCmd += _T("dropcopy /pathfile:\"");
1594 svnCmd += tempfile;
1595 svnCmd += _T("\"");
1596 svnCmd += _T(" /deletepathfile");
1597 svnCmd += _T(" /droptarget:\"");
1598 svnCmd += folder_;
1599 svnCmd += _T("\" /rename";)
1600 break;
1601 case ShellMenuDropMove:
1602 tempfile = WriteFileListToTempFile();
1603 svnCmd += _T("dropmove /pathfile:\"");
1604 svnCmd += tempfile;
1605 svnCmd += _T("\"");
1606 svnCmd += _T(" /deletepathfile");
1607 svnCmd += _T(" /droptarget:\"");
1608 svnCmd += folder_;
1609 svnCmd += _T("\"");
1610 break;
1611 case ShellMenuDropMoveRename:
1612 tempfile = WriteFileListToTempFile();
1613 svnCmd += _T("dropmove /pathfile:\"");
1614 svnCmd += tempfile;
1615 svnCmd += _T("\"");
1616 svnCmd += _T(" /deletepathfile");
1617 svnCmd += _T(" /droptarget:\"");
1618 svnCmd += folder_;
1619 svnCmd += _T("\" /rename";)
1620 break;
1621 case ShellMenuDropExport:
1622 tempfile = WriteFileListToTempFile();
1623 svnCmd += _T("dropexport /pathfile:\"");
1624 svnCmd += tempfile;
1625 svnCmd += _T("\"");
1626 svnCmd += _T(" /deletepathfile");
1627 svnCmd += _T(" /droptarget:\"");
1628 svnCmd += folder_;
1629 svnCmd += _T("\"");
1630 break;
1631 case ShellMenuDropExportExtended:
1632 tempfile = WriteFileListToTempFile();
1633 svnCmd += _T("dropexport /pathfile:\"");
1634 svnCmd += tempfile;
1635 svnCmd += _T("\"");
1636 svnCmd += _T(" /deletepathfile");
1637 svnCmd += _T(" /droptarget:\"");
1638 svnCmd += folder_;
1639 svnCmd += _T("\"");
1640 svnCmd += _T(" /extended");
1641 break;
1642 case ShellMenuLog:
1643 svnCmd += _T("log /path:\"");
1644 if (files_.size() > 0)
1645 svnCmd += files_.front();
1646 else
1647 svnCmd += folder_;
1648 svnCmd += _T("\"");
1649 break;
1650 case ShellMenuConflictEditor:
1651 svnCmd += _T("conflicteditor /path:\"");
1652 if (files_.size() > 0)
1653 svnCmd += files_.front();
1654 else
1655 svnCmd += folder_;
1656 svnCmd += _T("\"");
1657 break;
1658 case ShellMenuRelocate:
1659 svnCmd += _T("relocate /path:\"");
1660 if (files_.size() > 0)
1661 svnCmd += files_.front();
1662 else
1663 svnCmd += folder_;
1664 svnCmd += _T("\"");
1665 break;
1666 case ShellMenuGitSVNRebase:
1667 svnCmd += _T("svnrebase /path:\"");
1668 if (files_.size() > 0)
1669 svnCmd += files_.front();
1670 else
1671 svnCmd += folder_;
1672 svnCmd += _T("\"");
1673 break;
1674 case ShellMenuGitSVNDCommit:
1675 svnCmd += _T("svndcommit /path:\"");
1676 if (files_.size() > 0)
1677 svnCmd += files_.front();
1678 else
1679 svnCmd += folder_;
1680 svnCmd += _T("\"");
1681 break;
1682 case ShellMenuGitSVNIgnore:
1683 svnCmd += _T("svnignore /path:\"");
1684 if (files_.size() > 0)
1685 svnCmd += files_.front();
1686 else
1687 svnCmd += folder_;
1688 svnCmd += _T("\"");
1689 break;
1690 case ShellMenuRebase:
1691 svnCmd += _T("rebase /path:\"");
1692 if (files_.size() > 0)
1693 svnCmd += files_.front();
1694 else
1695 svnCmd += folder_;
1696 svnCmd += _T("\"");
1697 break;
1698 case ShellMenuShowChanged:
1699 if (files_.size() > 1)
1701 tempfile = WriteFileListToTempFile();
1702 svnCmd += _T("repostatus /pathfile:\"");
1703 svnCmd += tempfile;
1704 svnCmd += _T("\"");
1705 svnCmd += _T(" /deletepathfile");
1707 else
1709 svnCmd += _T("repostatus /path:\"");
1710 if (files_.size() > 0)
1711 svnCmd += files_.front();
1712 else
1713 svnCmd += folder_;
1714 svnCmd += _T("\"");
1716 break;
1717 case ShellMenuRefBrowse:
1718 svnCmd += _T("refbrowse /path:\"");
1719 if (files_.size() > 0)
1720 svnCmd += files_.front();
1721 else
1722 svnCmd += folder_;
1723 svnCmd += _T("\"");
1724 break;
1725 case ShellMenuRefLog:
1726 svnCmd += _T("reflog /path:\"");
1727 if (files_.size() > 0)
1728 svnCmd += files_.front();
1729 else
1730 svnCmd += folder_;
1731 svnCmd += _T("\"");
1732 break;
1734 case ShellMenuStashSave:
1735 svnCmd += _T("stashsave /path:\"");
1736 if (files_.size() > 0)
1737 svnCmd += files_.front();
1738 else
1739 svnCmd += folder_;
1740 svnCmd += _T("\"");
1741 break;
1743 case ShellMenuStashApply:
1744 svnCmd += _T("stashapply /path:\"");
1745 if (files_.size() > 0)
1746 svnCmd += files_.front();
1747 else
1748 svnCmd += folder_;
1749 svnCmd += _T("\"");
1750 break;
1752 case ShellMenuStashPop:
1753 svnCmd += _T("stashpop /path:\"");
1754 if (files_.size() > 0)
1755 svnCmd += files_.front();
1756 else
1757 svnCmd += folder_;
1758 svnCmd += _T("\"");
1759 break;
1762 case ShellMenuStashList:
1763 svnCmd += _T("reflog /path:\"");
1764 if (files_.size() > 0)
1765 svnCmd += files_.front();
1766 else
1767 svnCmd += folder_;
1768 svnCmd += _T("\" /ref:refs/stash");
1769 break;
1771 case ShellMenuSubAdd:
1772 svnCmd += _T("subadd /path:\"");
1773 if (files_.size() > 0)
1774 svnCmd += files_.front();
1775 else
1776 svnCmd += folder_;
1777 svnCmd += _T("\"");
1778 break;
1780 case ShellMenuBlame:
1781 svnCmd += _T("blame /path:\"");
1782 if (files_.size() > 0)
1783 svnCmd += files_.front();
1784 else
1785 svnCmd += folder_;
1786 svnCmd += _T("\"");
1787 break;
1788 case ShellMenuCreatePatch:
1789 tempfile = WriteFileListToTempFile();
1790 svnCmd += _T("createpatch /pathfile:\"");
1791 svnCmd += tempfile;
1792 svnCmd += _T("\"");
1793 svnCmd += _T(" /deletepathfile");
1794 break;
1795 case ShellMenuApplyPatch:
1796 if ((itemStates & ITEMIS_PATCHINCLIPBOARD) && ((~itemStates) & ITEMIS_PATCHFILE))
1798 // if there's a patch file in the clipboard, we save it
1799 // to a temporary file and tell TortoiseMerge to use that one
1800 UINT cFormat = RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
1801 if ((cFormat)&&(OpenClipboard(NULL)))
1803 HGLOBAL hglb = GetClipboardData(cFormat);
1804 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
1806 DWORD len = GetTempPath(0, NULL);
1807 TCHAR * path = new TCHAR[len+1];
1808 TCHAR * tempF = new TCHAR[len+100];
1809 GetTempPath (len+1, path);
1810 GetTempFileName (path, TEXT("git"), 0, tempF);
1811 std::wstring sTempFile = std::wstring(tempF);
1812 delete [] path;
1813 delete [] tempF;
1815 FILE * outFile;
1816 size_t patchlen = strlen(lpstr);
1817 _tfopen_s(&outFile, sTempFile.c_str(), _T("wb"));
1818 if(outFile)
1820 size_t size = fwrite(lpstr, sizeof(char), patchlen, outFile);
1821 if (size == patchlen)
1823 itemStates |= ITEMIS_PATCHFILE;
1824 files_.clear();
1825 files_.push_back(sTempFile);
1827 fclose(outFile);
1829 GlobalUnlock(hglb);
1830 CloseClipboard();
1833 if (itemStates & ITEMIS_PATCHFILE)
1835 svnCmd = _T(" /diff:\"");
1836 if (files_.size() > 0)
1838 svnCmd += files_.front();
1839 if (itemStatesFolder & ITEMIS_FOLDERINSVN)
1841 svnCmd += _T("\" /patchpath:\"");
1842 svnCmd += folder_;
1845 else
1846 svnCmd += folder_;
1847 if (itemStates & ITEMIS_INVERSIONEDFOLDER)
1848 svnCmd += _T("\" /wc");
1849 else
1850 svnCmd += _T("\"");
1852 else
1854 svnCmd = _T(" /patchpath:\"");
1855 if (files_.size() > 0)
1856 svnCmd += files_.front();
1857 else
1858 svnCmd += folder_;
1859 svnCmd += _T("\"");
1861 myIDMap.clear();
1862 myVerbsIDMap.clear();
1863 myVerbsMap.clear();
1864 if (CreateProcess(((stdstring)tortoiseMergePath).c_str(), const_cast<TCHAR*>(svnCmd.c_str()), NULL, NULL, FALSE, 0, 0, 0, &startup, &process)==0)
1866 LPVOID lpMsgBuf;
1867 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
1868 FORMAT_MESSAGE_FROM_SYSTEM |
1869 FORMAT_MESSAGE_IGNORE_INSERTS,
1870 NULL,
1871 GetLastError(),
1872 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
1873 (LPTSTR) &lpMsgBuf,
1875 NULL
1877 MessageBox( NULL, (LPCTSTR)lpMsgBuf, _T("TortoiseMerge launch failed"), MB_OK | MB_ICONINFORMATION );
1878 LocalFree( lpMsgBuf );
1880 CloseHandle(process.hThread);
1881 CloseHandle(process.hProcess);
1882 return NOERROR;
1883 break;
1884 case ShellMenuRevisionGraph:
1885 svnCmd += _T("revisiongraph /path:\"");
1886 if (files_.size() > 0)
1887 svnCmd += files_.front();
1888 else
1889 svnCmd += folder_;
1890 svnCmd += _T("\"");
1891 break;
1892 case ShellMenuProperties:
1893 tempfile = WriteFileListToTempFile();
1894 svnCmd += _T("properties /pathfile:\"");
1895 svnCmd += tempfile;
1896 svnCmd += _T("\"");
1897 svnCmd += _T(" /deletepathfile");
1898 break;
1899 case ShellMenuClipPaste:
1900 if (WriteClipboardPathsToTempFile(tempfile))
1902 bool bCopy = true;
1903 UINT cPrefDropFormat = RegisterClipboardFormat(_T("Preferred DropEffect"));
1904 if (cPrefDropFormat)
1906 if (OpenClipboard(lpcmi->hwnd))
1908 HGLOBAL hglb = GetClipboardData(cPrefDropFormat);
1909 if (hglb)
1911 DWORD* effect = (DWORD*) GlobalLock(hglb);
1912 if (*effect == DROPEFFECT_MOVE)
1913 bCopy = false;
1914 GlobalUnlock(hglb);
1916 CloseClipboard();
1920 if (bCopy)
1921 svnCmd += _T("pastecopy /pathfile:\"");
1922 else
1923 svnCmd += _T("pastemove /pathfile:\"");
1924 svnCmd += tempfile;
1925 svnCmd += _T("\"");
1926 svnCmd += _T(" /deletepathfile");
1927 svnCmd += _T(" /droptarget:\"");
1928 svnCmd += folder_;
1929 svnCmd += _T("\"");
1931 else return NOERROR;
1932 break;
1933 case ShellMenuClone:
1934 svnCmd += _T("clone /path:\"");
1935 if (files_.size() > 0)
1936 svnCmd += files_.front();
1937 else
1938 svnCmd += folder_;
1939 svnCmd += _T("\"");
1940 break;
1941 case ShellMenuPull:
1942 svnCmd += _T("pull /path:\"");
1943 if (files_.size() > 0)
1944 svnCmd += files_.front();
1945 else
1946 svnCmd += folder_;
1947 svnCmd += _T("\"");
1948 break;
1949 case ShellMenuPush:
1950 svnCmd += _T("push /path:\"");
1951 if (files_.size() > 0)
1952 svnCmd += files_.front();
1953 else
1954 svnCmd += folder_;
1955 svnCmd += _T("\"");
1956 break;
1957 case ShellMenuBranch:
1958 svnCmd += _T("branch /path:\"");
1959 if (files_.size() > 0)
1960 svnCmd += files_.front();
1961 else
1962 svnCmd += folder_;
1963 svnCmd += _T("\"");
1964 break;
1966 case ShellMenuTag:
1967 svnCmd += _T("tag /path:\"");
1968 if (files_.size() > 0)
1969 svnCmd += files_.front();
1970 else
1971 svnCmd += folder_;
1972 svnCmd += _T("\"");
1973 break;
1975 case ShellMenuFormatPatch:
1976 svnCmd += _T("formatpatch /path:\"");
1977 if (files_.size() > 0)
1978 svnCmd += files_.front();
1979 else
1980 svnCmd += folder_;
1981 svnCmd += _T("\"");
1982 break;
1984 case ShellMenuImportPatch:
1985 tempfile = WriteFileListToTempFile();
1986 svnCmd += _T("importpatch /pathfile:\"");
1987 svnCmd += tempfile;
1988 svnCmd += _T("\"");
1989 svnCmd += _T(" /deletepathfile");
1990 break;
1992 case ShellMenuCherryPick:
1993 svnCmd += _T("cherrypick /path:\"");
1994 if (files_.size() > 0)
1995 svnCmd += files_.front();
1996 else
1997 svnCmd += folder_;
1998 svnCmd += _T("\"");
1999 break;
2000 case ShellMenuFetch:
2001 svnCmd += _T("fetch /path:\"");
2002 if (files_.size() > 0)
2003 svnCmd += files_.front();
2004 else
2005 svnCmd += folder_;
2006 svnCmd += _T("\"");
2007 break;
2009 default:
2010 break;
2011 //#endregion
2012 } // switch (id_it->second)
2013 svnCmd += _T(" /hwnd:");
2014 TCHAR buf[30];
2015 _stprintf_s(buf, 30, _T("%d"), lpcmi->hwnd);
2016 svnCmd += buf;
2017 myIDMap.clear();
2018 myVerbsIDMap.clear();
2019 myVerbsMap.clear();
2020 if (CreateProcess(((stdstring)tortoiseProcPath).c_str(), const_cast<TCHAR*>(svnCmd.c_str()), NULL, NULL, FALSE, 0, 0, 0, &startup, &process)==0)
2022 LPVOID lpMsgBuf;
2023 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
2024 FORMAT_MESSAGE_FROM_SYSTEM |
2025 FORMAT_MESSAGE_IGNORE_INSERTS,
2026 NULL,
2027 GetLastError(),
2028 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
2029 (LPTSTR) &lpMsgBuf,
2031 NULL
2033 MessageBox( NULL, (LPCTSTR)lpMsgBuf, _T("TortoiseProc Launch failed"), MB_OK | MB_ICONINFORMATION );
2034 LocalFree( lpMsgBuf );
2036 CloseHandle(process.hThread);
2037 CloseHandle(process.hProcess);
2038 hr = NOERROR;
2039 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
2040 } // if ((files_.size() > 0)||(folder_.size() > 0))
2041 return hr;
2045 // This is for the status bar and things like that:
2046 STDMETHODIMP CShellExt::GetCommandString(UINT_PTR idCmd,
2047 UINT uFlags,
2048 UINT FAR * /*reserved*/,
2049 LPSTR pszName,
2050 UINT cchMax)
2052 PreserveChdir preserveChdir;
2053 //do we know the id?
2054 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
2055 if (id_it == myIDMap.end() || id_it->first != idCmd)
2057 return E_INVALIDARG; //no, we don't
2060 LoadLangDll();
2061 HRESULT hr = E_INVALIDARG;
2063 MAKESTRING(IDS_MENUDESCDEFAULT);
2064 int menuIndex = 0;
2065 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
2067 if (menuInfo[menuIndex].command == (GitCommands)id_it->second)
2069 MAKESTRING(menuInfo[menuIndex].menuDescID);
2070 break;
2072 menuIndex++;
2075 const TCHAR * desc = stringtablebuffer;
2076 switch(uFlags)
2078 case GCS_HELPTEXTA:
2080 std::string help = WideToMultibyte(desc);
2081 lstrcpynA(pszName, help.c_str(), cchMax);
2082 hr = S_OK;
2083 break;
2085 case GCS_HELPTEXTW:
2087 wide_string help = desc;
2088 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax);
2089 hr = S_OK;
2090 break;
2092 case GCS_VERBA:
2094 std::map<UINT_PTR, stdstring>::const_iterator verb_id_it = myVerbsIDMap.lower_bound(idCmd);
2095 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
2097 std::string help = WideToMultibyte(verb_id_it->second);
2098 lstrcpynA(pszName, help.c_str(), cchMax);
2099 hr = S_OK;
2102 break;
2103 case GCS_VERBW:
2105 std::map<UINT_PTR, stdstring>::const_iterator verb_id_it = myVerbsIDMap.lower_bound(idCmd);
2106 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
2108 wide_string help = verb_id_it->second;
2109 ATLTRACE("verb : %ws\n", help.c_str());
2110 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax);
2111 hr = S_OK;
2114 break;
2116 return hr;
2119 STDMETHODIMP CShellExt::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
2121 LRESULT res;
2122 return HandleMenuMsg2(uMsg, wParam, lParam, &res);
2125 STDMETHODIMP CShellExt::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pResult)
2127 PreserveChdir preserveChdir;
2129 LRESULT res;
2130 if (pResult == NULL)
2131 pResult = &res;
2132 *pResult = FALSE;
2134 LoadLangDll();
2135 switch (uMsg)
2137 case WM_MEASUREITEM:
2139 MEASUREITEMSTRUCT* lpmis = (MEASUREITEMSTRUCT*)lParam;
2140 if (lpmis==NULL||lpmis->CtlType!=ODT_MENU)
2141 break;
2142 lpmis->itemWidth += 2;
2143 if (lpmis->itemHeight < 16)
2144 lpmis->itemHeight = 16;
2145 *pResult = TRUE;
2147 break;
2148 case WM_DRAWITEM:
2150 LPCTSTR resource;
2151 DRAWITEMSTRUCT* lpdis = (DRAWITEMSTRUCT*)lParam;
2152 if ((lpdis==NULL)||(lpdis->CtlType != ODT_MENU))
2153 return S_OK; //not for a menu
2154 resource = GetMenuTextFromResource(myIDMap[lpdis->itemID]);
2155 if (resource == NULL)
2156 return S_OK;
2157 HICON hIcon = (HICON)LoadImage(g_hResInst, resource, IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
2158 if (hIcon == NULL)
2159 return S_OK;
2160 DrawIconEx(lpdis->hDC,
2161 lpdis->rcItem.left < 16 ? lpdis->rcItem.left : lpdis->rcItem.left - 16,
2162 lpdis->rcItem.top + (lpdis->rcItem.bottom - lpdis->rcItem.top - 16) / 2,
2163 hIcon, 16, 16,
2164 0, NULL, DI_NORMAL);
2165 DestroyIcon(hIcon);
2166 *pResult = TRUE;
2168 break;
2169 case WM_MENUCHAR:
2171 LPCTSTR resource;
2172 TCHAR *szItem;
2173 if (HIWORD(wParam) != MF_POPUP)
2174 return NOERROR;
2175 int nChar = LOWORD(wParam);
2176 if (_istascii((wint_t)nChar) && _istupper((wint_t)nChar))
2177 nChar = tolower(nChar);
2178 // we have the char the user pressed, now search that char in all our
2179 // menu items
2180 std::vector<int> accmenus;
2181 for (std::map<UINT_PTR, UINT_PTR>::iterator It = mySubMenuMap.begin(); It != mySubMenuMap.end(); ++It)
2183 resource = GetMenuTextFromResource(mySubMenuMap[It->first]);
2184 if (resource == NULL)
2185 continue;
2186 szItem = stringtablebuffer;
2187 TCHAR * amp = _tcschr(szItem, '&');
2188 if (amp == NULL)
2189 continue;
2190 amp++;
2191 int ampChar = LOWORD(*amp);
2192 if (_istascii((wint_t)ampChar) && _istupper((wint_t)ampChar))
2193 ampChar = tolower(ampChar);
2194 if (ampChar == nChar)
2196 // yep, we found a menu which has the pressed key
2197 // as an accelerator. Add that menu to the list to
2198 // process later.
2199 accmenus.push_back(It->first);
2202 if (accmenus.size() == 0)
2204 // no menu with that accelerator key.
2205 *pResult = MAKELONG(0, MNC_IGNORE);
2206 return NOERROR;
2208 if (accmenus.size() == 1)
2210 // Only one menu with that accelerator key. We're lucky!
2211 // So just execute that menu entry.
2212 *pResult = MAKELONG(accmenus[0], MNC_EXECUTE);
2213 return NOERROR;
2215 if (accmenus.size() > 1)
2217 // we have more than one menu item with this accelerator key!
2218 MENUITEMINFO mif;
2219 mif.cbSize = sizeof(MENUITEMINFO);
2220 mif.fMask = MIIM_STATE;
2221 for (std::vector<int>::iterator it = accmenus.begin(); it != accmenus.end(); ++it)
2223 GetMenuItemInfo((HMENU)lParam, *it, TRUE, &mif);
2224 if (mif.fState == MFS_HILITE)
2226 // this is the selected item, so select the next one
2227 ++it;
2228 if (it == accmenus.end())
2229 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
2230 else
2231 *pResult = MAKELONG(*it, MNC_SELECT);
2232 return NOERROR;
2235 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
2238 break;
2239 default:
2240 return NOERROR;
2243 return NOERROR;
2246 LPCTSTR CShellExt::GetMenuTextFromResource(int id)
2248 TCHAR textbuf[255];
2249 LPCTSTR resource = NULL;
2250 unsigned __int64 layout = g_ShellCache.GetMenuLayout();
2251 space = 6;
2253 int menuIndex = 0;
2254 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
2256 if (menuInfo[menuIndex].command == id)
2258 MAKESTRING(menuInfo[menuIndex].menuTextID);
2259 resource = MAKEINTRESOURCE(menuInfo[menuIndex].iconID);
2260 switch (id)
2262 case ShellSubMenuMultiple:
2263 case ShellSubMenuLink:
2264 case ShellSubMenuFolder:
2265 case ShellSubMenuFile:
2266 case ShellSubMenu:
2267 space = 0;
2268 break;
2269 default:
2270 space = layout & menuInfo[menuIndex].menuID ? 0 : 6;
2271 if (layout & (menuInfo[menuIndex].menuID))
2273 _tcscpy_s(textbuf, 255, _T("Git "));
2274 _tcscat_s(textbuf, 255, stringtablebuffer);
2275 _tcscpy_s(stringtablebuffer, 255, textbuf);
2277 break;
2279 return resource;
2281 menuIndex++;
2283 return NULL;
2286 bool CShellExt::IsIllegalFolder(std::wstring folder, int * cslidarray)
2288 int i=0;
2289 TCHAR buf[MAX_PATH]; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
2290 LPITEMIDLIST pidl = NULL;
2291 while (cslidarray[i])
2293 ++i;
2294 pidl = NULL;
2295 if (SHGetFolderLocation(NULL, cslidarray[i-1], NULL, 0, &pidl)!=S_OK)
2296 continue;
2297 if (!SHGetPathFromIDList(pidl, buf))
2299 // not a file system path, definitely illegal for our use
2300 CoTaskMemFree(pidl);
2301 continue;
2303 CoTaskMemFree(pidl);
2304 if (_tcslen(buf)==0)
2305 continue;
2306 if (_tcscmp(buf, folder.c_str())==0)
2307 return true;
2309 return false;
2312 bool CShellExt::InsertIgnoreSubmenus(UINT &idCmd, UINT idCmdFirst, HMENU hMenu, HMENU subMenu, UINT &indexMenu, int &indexSubMenu, unsigned __int64 topmenu, bool bShowIcons, UINT uFlags)
2314 HMENU ignoresubmenu = NULL;
2315 int indexignoresub = 0;
2316 bool bShowIgnoreMenu = false;
2317 TCHAR maskbuf[MAX_PATH]; // MAX_PATH is ok, since this only holds a filename
2318 TCHAR ignorepath[MAX_PATH]; // MAX_PATH is ok, since this only holds a filename
2319 if (files_.size() == 0 || (files_.size() == 1 && folder_.length() != 0 && g_GitAdminDir.GetGitTopDir(folder_.c_str()) == folder_.c_str()))
2320 return false;
2321 UINT icon = bShowIcons ? IDI_IGNORE : 0;
2323 std::vector<stdstring>::iterator I = files_.begin();
2324 if (_tcsrchr(I->c_str(), '\\'))
2325 _tcscpy_s(ignorepath, MAX_PATH, _tcsrchr(I->c_str(), '\\')+1);
2326 else
2327 _tcscpy_s(ignorepath, MAX_PATH, I->c_str());
2328 if ((itemStates & ITEMIS_IGNORED)&&(ignoredprops.size() > 0))
2330 // check if the item name is ignored or the mask
2331 size_t p = 0;
2332 while ( (p=ignoredprops.find( ignorepath,p )) != -1 )
2334 if ( (p==0 || ignoredprops[p-1]==TCHAR('\n'))
2335 && (p+_tcslen(ignorepath)==ignoredprops.length() || ignoredprops[p+_tcslen(ignorepath)+1]==TCHAR('\n')) )
2337 break;
2339 p++;
2341 if (p!=-1)
2343 ignoresubmenu = CreateMenu();
2344 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2345 stdstring verb = stdstring(ignorepath);
2346 myVerbsMap[verb] = idCmd - idCmdFirst;
2347 myVerbsMap[verb] = idCmd;
2348 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2349 myVerbsIDMap[idCmd] = verb;
2350 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnore;
2351 myIDMap[idCmd++] = ShellMenuUnIgnore;
2352 bShowIgnoreMenu = true;
2354 _tcscpy_s(maskbuf, MAX_PATH, _T("*"));
2355 if (_tcsrchr(ignorepath, '.'))
2357 _tcscat_s(maskbuf, MAX_PATH, _tcsrchr(ignorepath, '.'));
2358 p = ignoredprops.find(maskbuf);
2359 if ((p!=-1) &&
2360 ((ignoredprops.compare(maskbuf)==0) || (ignoredprops.find('\n', p)==p+_tcslen(maskbuf)+1) || (ignoredprops.rfind('\n', p)==p-1)))
2362 if (ignoresubmenu==NULL)
2363 ignoresubmenu = CreateMenu();
2365 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2366 stdstring verb = stdstring(maskbuf);
2367 myVerbsMap[verb] = idCmd - idCmdFirst;
2368 myVerbsMap[verb] = idCmd;
2369 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2370 myVerbsIDMap[idCmd] = verb;
2371 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreCaseSensitive;
2372 myIDMap[idCmd++] = ShellMenuUnIgnoreCaseSensitive;
2373 bShowIgnoreMenu = true;
2377 else if ((itemStates & ITEMIS_IGNORED) == 0)
2379 bShowIgnoreMenu = true;
2380 ignoresubmenu = CreateMenu();
2381 if (itemStates & ITEMIS_ONLYONE)
2383 if (itemStates & ITEMIS_INSVN)
2385 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2386 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
2387 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
2389 _tcscpy_s(maskbuf, MAX_PATH, _T("*"));
2390 if (_tcsrchr(ignorepath, '.'))
2392 _tcscat_s(maskbuf, MAX_PATH, _tcsrchr(ignorepath, '.'));
2393 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2394 stdstring verb = stdstring(maskbuf);
2395 myVerbsMap[verb] = idCmd - idCmdFirst;
2396 myVerbsMap[verb] = idCmd;
2397 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2398 myVerbsIDMap[idCmd] = verb;
2399 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
2400 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
2403 else
2405 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2406 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
2407 myIDMap[idCmd++] = ShellMenuIgnore;
2409 _tcscpy_s(maskbuf, MAX_PATH, _T("*"));
2410 if (_tcsrchr(ignorepath, '.'))
2412 _tcscat_s(maskbuf, MAX_PATH, _tcsrchr(ignorepath, '.'));
2413 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2414 stdstring verb = stdstring(maskbuf);
2415 myVerbsMap[verb] = idCmd - idCmdFirst;
2416 myVerbsMap[verb] = idCmd;
2417 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2418 myVerbsIDMap[idCmd] = verb;
2419 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2420 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2424 else
2426 if (itemStates & ITEMIS_INSVN)
2428 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE);
2429 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2430 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2431 stdstring verb = stdstring(ignorepath);
2432 myVerbsMap[verb] = idCmd - idCmdFirst;
2433 myVerbsMap[verb] = idCmd;
2434 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2435 myVerbsIDMap[idCmd] = verb;
2436 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
2437 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
2439 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK);
2440 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2441 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2442 verb = stdstring(ignorepath);
2443 myVerbsMap[verb] = idCmd - idCmdFirst;
2444 myVerbsMap[verb] = idCmd;
2445 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2446 myVerbsIDMap[idCmd] = verb;
2447 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
2448 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
2450 else
2452 MAKESTRING(IDS_MENUIGNOREMULTIPLE);
2453 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2454 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2455 stdstring verb = stdstring(ignorepath);
2456 myVerbsMap[verb] = idCmd - idCmdFirst;
2457 myVerbsMap[verb] = idCmd;
2458 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2459 myVerbsIDMap[idCmd] = verb;
2460 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
2461 myIDMap[idCmd++] = ShellMenuIgnore;
2463 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK);
2464 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2465 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2466 verb = stdstring(ignorepath);
2467 myVerbsMap[verb] = idCmd - idCmdFirst;
2468 myVerbsMap[verb] = idCmd;
2469 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2470 myVerbsIDMap[idCmd] = verb;
2471 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2472 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2477 if (bShowIgnoreMenu)
2479 MENUITEMINFO menuiteminfo;
2480 SecureZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
2481 menuiteminfo.cbSize = sizeof(menuiteminfo);
2482 if (fullver < 0x500 || (fullver == 0x500 && !(uFlags&~(CMF_RESERVED|CMF_EXPLORE|CMF_EXTENDEDVERBS))))
2484 menuiteminfo.fMask = MIIM_STRING | MIIM_ID | MIIM_SUBMENU | MIIM_DATA;
2485 if (icon)
2487 HBITMAP bmp = IconToBitmap(icon);
2488 menuiteminfo.fMask |= MIIM_CHECKMARKS;
2489 menuiteminfo.hbmpChecked = bmp;
2490 menuiteminfo.hbmpUnchecked = bmp;
2493 else
2495 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
2496 if (icon)
2498 menuiteminfo.fMask |= MIIM_BITMAP;
2499 menuiteminfo.hbmpItem = (fullver >= 0x600) ? IconToBitmapPARGB32(icon) : HBMMENU_CALLBACK;
2502 menuiteminfo.fType = MFT_STRING;
2503 menuiteminfo.hSubMenu = ignoresubmenu;
2504 menuiteminfo.wID = idCmd;
2505 SecureZeroMemory(stringtablebuffer, sizeof(stringtablebuffer));
2506 if (itemStates & ITEMIS_IGNORED)
2507 GetMenuTextFromResource(ShellMenuUnIgnoreSub);
2508 else if (itemStates & ITEMIS_INSVN)
2509 GetMenuTextFromResource(ShellMenuDeleteIgnoreSub);
2510 else
2511 GetMenuTextFromResource(ShellMenuIgnoreSub);
2512 menuiteminfo.dwTypeData = stringtablebuffer;
2513 menuiteminfo.cch = (UINT)min(_tcslen(menuiteminfo.dwTypeData), UINT_MAX);
2515 InsertMenuItem((topmenu & MENUIGNORE) ? hMenu : subMenu, (topmenu & MENUIGNORE) ? indexMenu++ : indexSubMenu++, TRUE, &menuiteminfo);
2516 if (itemStates & ITEMIS_IGNORED)
2518 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreSub;
2519 myIDMap[idCmd++] = ShellMenuUnIgnoreSub;
2521 else
2523 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreSub;
2524 myIDMap[idCmd++] = ShellMenuIgnoreSub;
2527 return bShowIgnoreMenu;
2530 HBITMAP CShellExt::IconToBitmapPARGB32(UINT uIcon)
2532 std::map<UINT, HBITMAP>::iterator bitmap_it = bitmaps.lower_bound(uIcon);
2533 if (bitmap_it != bitmaps.end() && bitmap_it->first == uIcon)
2534 return bitmap_it->second;
2536 HICON hIcon = (HICON)LoadImage(g_hResInst, MAKEINTRESOURCE(uIcon), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
2537 if (!hIcon)
2538 return NULL;
2540 if (pfnBeginBufferedPaint == NULL || pfnEndBufferedPaint == NULL || pfnGetBufferedPaintBits == NULL)
2541 return NULL;
2543 SIZE sizIcon;
2544 sizIcon.cx = GetSystemMetrics(SM_CXSMICON);
2545 sizIcon.cy = GetSystemMetrics(SM_CYSMICON);
2547 RECT rcIcon;
2548 SetRect(&rcIcon, 0, 0, sizIcon.cx, sizIcon.cy);
2549 HBITMAP hBmp = NULL;
2551 HDC hdcDest = CreateCompatibleDC(NULL);
2552 if (hdcDest)
2554 if (SUCCEEDED(Create32BitHBITMAP(hdcDest, &sizIcon, NULL, &hBmp)))
2556 HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcDest, hBmp);
2557 if (hbmpOld)
2559 BLENDFUNCTION bfAlpha = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
2560 BP_PAINTPARAMS paintParams = {0};
2561 paintParams.cbSize = sizeof(paintParams);
2562 paintParams.dwFlags = BPPF_ERASE;
2563 paintParams.pBlendFunction = &bfAlpha;
2565 HDC hdcBuffer;
2566 HPAINTBUFFER hPaintBuffer = pfnBeginBufferedPaint(hdcDest, &rcIcon, BPBF_DIB, &paintParams, &hdcBuffer);
2567 if (hPaintBuffer)
2569 if (DrawIconEx(hdcBuffer, 0, 0, hIcon, sizIcon.cx, sizIcon.cy, 0, NULL, DI_NORMAL))
2571 // If icon did not have an alpha channel we need to convert buffer to PARGB
2572 ConvertBufferToPARGB32(hPaintBuffer, hdcDest, hIcon, sizIcon);
2575 // This will write the buffer contents to the destination bitmap
2576 pfnEndBufferedPaint(hPaintBuffer, TRUE);
2579 SelectObject(hdcDest, hbmpOld);
2583 DeleteDC(hdcDest);
2586 DestroyIcon(hIcon);
2588 if(hBmp)
2589 bitmaps.insert(bitmap_it, std::make_pair(uIcon, hBmp));
2590 return hBmp;
2593 HRESULT CShellExt::Create32BitHBITMAP(HDC hdc, const SIZE *psize, __deref_opt_out void **ppvBits, __out HBITMAP* phBmp)
2595 *phBmp = NULL;
2597 BITMAPINFO bmi;
2598 ZeroMemory(&bmi, sizeof(bmi));
2599 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
2600 bmi.bmiHeader.biPlanes = 1;
2601 bmi.bmiHeader.biCompression = BI_RGB;
2603 bmi.bmiHeader.biWidth = psize->cx;
2604 bmi.bmiHeader.biHeight = psize->cy;
2605 bmi.bmiHeader.biBitCount = 32;
2607 HDC hdcUsed = hdc ? hdc : GetDC(NULL);
2608 if (hdcUsed)
2610 *phBmp = CreateDIBSection(hdcUsed, &bmi, DIB_RGB_COLORS, ppvBits, NULL, 0);
2611 if (hdc != hdcUsed)
2613 ReleaseDC(NULL, hdcUsed);
2616 return (NULL == *phBmp) ? E_OUTOFMEMORY : S_OK;
2619 HRESULT CShellExt::ConvertBufferToPARGB32(HPAINTBUFFER hPaintBuffer, HDC hdc, HICON hicon, SIZE& sizIcon)
2621 RGBQUAD *prgbQuad;
2622 int cxRow;
2623 HRESULT hr = pfnGetBufferedPaintBits(hPaintBuffer, &prgbQuad, &cxRow);
2624 if (SUCCEEDED(hr))
2626 ARGB *pargb = reinterpret_cast<ARGB *>(prgbQuad);
2627 if (!HasAlpha(pargb, sizIcon, cxRow))
2629 ICONINFO info;
2630 if (GetIconInfo(hicon, &info))
2632 if (info.hbmMask)
2634 hr = ConvertToPARGB32(hdc, pargb, info.hbmMask, sizIcon, cxRow);
2637 DeleteObject(info.hbmColor);
2638 DeleteObject(info.hbmMask);
2643 return hr;
2646 bool CShellExt::HasAlpha(__in ARGB *pargb, SIZE& sizImage, int cxRow)
2648 ULONG cxDelta = cxRow - sizImage.cx;
2649 for (ULONG y = sizImage.cy; y; --y)
2651 for (ULONG x = sizImage.cx; x; --x)
2653 if (*pargb++ & 0xFF000000)
2655 return true;
2659 pargb += cxDelta;
2662 return false;
2665 HRESULT CShellExt::ConvertToPARGB32(HDC hdc, __inout ARGB *pargb, HBITMAP hbmp, SIZE& sizImage, int cxRow)
2667 BITMAPINFO bmi;
2668 ZeroMemory(&bmi, sizeof(bmi));
2669 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
2670 bmi.bmiHeader.biPlanes = 1;
2671 bmi.bmiHeader.biCompression = BI_RGB;
2673 bmi.bmiHeader.biWidth = sizImage.cx;
2674 bmi.bmiHeader.biHeight = sizImage.cy;
2675 bmi.bmiHeader.biBitCount = 32;
2677 HRESULT hr = E_OUTOFMEMORY;
2678 HANDLE hHeap = GetProcessHeap();
2679 void *pvBits = HeapAlloc(hHeap, 0, bmi.bmiHeader.biWidth * 4 * bmi.bmiHeader.biHeight);
2680 if (pvBits)
2682 hr = E_UNEXPECTED;
2683 if (GetDIBits(hdc, hbmp, 0, bmi.bmiHeader.biHeight, pvBits, &bmi, DIB_RGB_COLORS) == bmi.bmiHeader.biHeight)
2685 ULONG cxDelta = cxRow - bmi.bmiHeader.biWidth;
2686 ARGB *pargbMask = static_cast<ARGB *>(pvBits);
2688 for (ULONG y = bmi.bmiHeader.biHeight; y; --y)
2690 for (ULONG x = bmi.bmiHeader.biWidth; x; --x)
2692 if (*pargbMask++)
2694 // transparent pixel
2695 *pargb++ = 0;
2697 else
2699 // opaque pixel
2700 *pargb++ |= 0xFF000000;
2704 pargb += cxDelta;
2707 hr = S_OK;
2710 HeapFree(hHeap, 0, pvBits);
2713 return hr;