Fixed issue #916: Fixed wrong contextmenu icon display position with installed TSVN 1.7
[TortoiseGit.git] / src / TortoiseShell / ContextMenu.cpp
blob9d26b6dc93245d7633d1660b9569c3993f146ad2
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 MENUITEMINFO menuiteminfo;
548 SecureZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
549 menuiteminfo.cbSize = sizeof(menuiteminfo);
550 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING;
551 menuiteminfo.fType = MFT_STRING;
552 menuiteminfo.dwTypeData = menutextbuffer;
553 if (icon)
555 menuiteminfo.fMask |= MIIM_BITMAP;
556 menuiteminfo.hbmpItem = (SysInfo::Instance().IsVistaOrLater()) ? IconToBitmapPARGB32(icon) : HBMMENU_CALLBACK;
558 menuiteminfo.wID = id;
559 InsertMenuItem(menu, pos, TRUE, &menuiteminfo);
561 if (istop)
563 //menu entry for the top context menu, so append an "Git " before
564 //the menu text to indicate where the entry comes from
565 _tcscpy_s(menutextbuffer, 255, _T("Git "));
567 LoadString(g_hResInst, stringid, verbsbuffer, sizeof(verbsbuffer));
568 _tcscat_s(menutextbuffer, 255, verbsbuffer);
569 stdstring verb = stdstring(menutextbuffer);
570 if (verb.find('&') != -1)
572 verb.erase(verb.find('&'),1);
574 myVerbsMap[verb] = id - idCmdFirst;
575 myVerbsMap[verb] = id;
576 myVerbsIDMap[id - idCmdFirst] = verb;
577 myVerbsIDMap[id] = verb;
578 // We store the relative and absolute diameter
579 // (drawitem callback uses absolute, others relative)
580 myIDMap[id - idCmdFirst] = com;
581 myIDMap[id] = com;
582 if (!istop)
583 mySubMenuMap[pos] = com;
586 HBITMAP CShellExt::IconToBitmap(UINT uIcon)
588 std::map<UINT, HBITMAP>::iterator bitmap_it = bitmaps.lower_bound(uIcon);
589 if (bitmap_it != bitmaps.end() && bitmap_it->first == uIcon)
590 return bitmap_it->second;
592 HICON hIcon = (HICON)LoadImage(g_hResInst, MAKEINTRESOURCE(uIcon), IMAGE_ICON, 12, 12, LR_DEFAULTCOLOR);
593 if (!hIcon)
594 return NULL;
596 RECT rect;
598 rect.right = ::GetSystemMetrics(SM_CXMENUCHECK);
599 rect.bottom = ::GetSystemMetrics(SM_CYMENUCHECK);
601 rect.left = rect.top = 0;
603 HWND desktop = ::GetDesktopWindow();
604 if (desktop == NULL)
606 DestroyIcon(hIcon);
607 return NULL;
610 HDC screen_dev = ::GetDC(desktop);
611 if (screen_dev == NULL)
613 DestroyIcon(hIcon);
614 return NULL;
617 // Create a compatible DC
618 HDC dst_hdc = ::CreateCompatibleDC(screen_dev);
619 if (dst_hdc == NULL)
621 DestroyIcon(hIcon);
622 ::ReleaseDC(desktop, screen_dev);
623 return NULL;
626 // Create a new bitmap of icon size
627 HBITMAP bmp = ::CreateCompatibleBitmap(screen_dev, rect.right, rect.bottom);
628 if (bmp == NULL)
630 DestroyIcon(hIcon);
631 ::DeleteDC(dst_hdc);
632 ::ReleaseDC(desktop, screen_dev);
633 return NULL;
636 // Select it into the compatible DC
637 HBITMAP old_dst_bmp = (HBITMAP)::SelectObject(dst_hdc, bmp);
638 if (old_dst_bmp == NULL)
640 DestroyIcon(hIcon);
641 return NULL;
644 // Fill the background of the compatible DC with the white color
645 // that is taken by menu routines as transparent
646 ::SetBkColor(dst_hdc, RGB(255, 255, 255));
647 ::ExtTextOut(dst_hdc, 0, 0, ETO_OPAQUE, &rect, NULL, 0, NULL);
649 // Draw the icon into the compatible DC
650 ::DrawIconEx(dst_hdc, 0, 0, hIcon, rect.right, rect.bottom, 0, NULL, DI_NORMAL);
652 // Restore settings
653 ::SelectObject(dst_hdc, old_dst_bmp);
654 ::DeleteDC(dst_hdc);
655 ::ReleaseDC(desktop, screen_dev);
656 DestroyIcon(hIcon);
657 if (bmp)
658 bitmaps.insert(bitmap_it, std::make_pair(uIcon, bmp));
659 return bmp;
662 bool CShellExt::WriteClipboardPathsToTempFile(stdstring& tempfile)
664 bool bRet = true;
665 tempfile = stdstring();
666 //write all selected files and paths to a temporary file
667 //for TortoiseProc.exe to read out again.
668 DWORD written = 0;
669 DWORD pathlength = GetTempPath(0, NULL);
670 TCHAR * path = new TCHAR[pathlength+1];
671 TCHAR * tempFile = new TCHAR[pathlength + 100];
672 GetTempPath (pathlength+1, path);
673 GetTempFileName (path, _T("git"), 0, tempFile);
674 tempfile = stdstring(tempFile);
676 HANDLE file = ::CreateFile (tempFile,
677 GENERIC_WRITE,
678 FILE_SHARE_READ,
680 CREATE_ALWAYS,
681 FILE_ATTRIBUTE_TEMPORARY,
684 delete [] path;
685 delete [] tempFile;
686 if (file == INVALID_HANDLE_VALUE)
687 return false;
689 if (!IsClipboardFormatAvailable(CF_HDROP))
690 return false;
691 if (!OpenClipboard(NULL))
692 return false;
694 stdstring sClipboardText;
695 HGLOBAL hglb = GetClipboardData(CF_HDROP);
696 HDROP hDrop = (HDROP)GlobalLock(hglb);
697 if(hDrop != NULL)
699 TCHAR szFileName[MAX_PATH];
700 UINT cFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
701 for(UINT i = 0; i < cFiles; ++i)
703 DragQueryFile(hDrop, i, szFileName, sizeof(szFileName));
704 stdstring filename = szFileName;
705 ::WriteFile (file, filename.c_str(), filename.size()*sizeof(TCHAR), &written, 0);
706 ::WriteFile (file, _T("\n"), 2, &written, 0);
708 GlobalUnlock(hDrop);
710 else bRet = false;
711 GlobalUnlock(hglb);
713 CloseClipboard();
714 ::CloseHandle(file);
716 return bRet;
719 stdstring CShellExt::WriteFileListToTempFile()
721 //write all selected files and paths to a temporary file
722 //for TortoiseProc.exe to read out again.
723 DWORD pathlength = GetTempPath(0, NULL);
724 TCHAR * path = new TCHAR[pathlength+1];
725 TCHAR * tempFile = new TCHAR[pathlength + 100];
726 GetTempPath (pathlength+1, path);
727 GetTempFileName (path, _T("git"), 0, tempFile);
728 stdstring retFilePath = stdstring(tempFile);
730 HANDLE file = ::CreateFile (tempFile,
731 GENERIC_WRITE,
732 FILE_SHARE_READ,
734 CREATE_ALWAYS,
735 FILE_ATTRIBUTE_TEMPORARY,
738 delete [] path;
739 delete [] tempFile;
740 if (file == INVALID_HANDLE_VALUE)
741 return stdstring();
743 DWORD written = 0;
744 if (files_.empty())
746 ::WriteFile (file, folder_.c_str(), folder_.size()*sizeof(TCHAR), &written, 0);
747 ::WriteFile (file, _T("\n"), 2, &written, 0);
750 for (std::vector<stdstring>::iterator I = files_.begin(); I != files_.end(); ++I)
752 ::WriteFile (file, I->c_str(), I->size()*sizeof(TCHAR), &written, 0);
753 ::WriteFile (file, _T("\n"), 2, &written, 0);
755 ::CloseHandle(file);
756 return retFilePath;
759 STDMETHODIMP CShellExt::QueryDropContext(UINT uFlags, UINT idCmdFirst, HMENU hMenu, UINT &indexMenu)
761 PreserveChdir preserveChdir;
762 LoadLangDll();
764 if ((uFlags & CMF_DEFAULTONLY)!=0)
765 return NOERROR; //we don't change the default action
767 if ((files_.size() == 0)||(folder_.size() == 0))
768 return NOERROR;
770 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
771 return NOERROR;
773 bool bSourceAndTargetFromSameRepository = (uuidSource.compare(uuidTarget) == 0) || uuidSource.empty() || uuidTarget.empty();
775 //the drop handler only has eight commands, but not all are visible at the same time:
776 //if the source file(s) are under version control then those files can be moved
777 //to the new location or they can be moved with a rename,
778 //if they are unversioned then they can be added to the working copy
779 //if they are versioned, they also can be exported to an unversioned location
780 UINT idCmd = idCmdFirst;
782 // Git move here
783 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
784 if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINSVN)&&((itemStates & ITEMIS_INSVN)&&((~itemStates) & ITEMIS_ADDED)))
785 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVEMENU, 0, idCmdFirst, ShellMenuDropMove, uFlags);
787 // Git move and rename here
788 // 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
789 if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINSVN)&&(itemStates & ITEMIS_INSVN)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
790 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVERENAMEMENU, 0, idCmdFirst, ShellMenuDropMoveRename, uFlags);
792 // Git copy here
793 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
794 if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINSVN)&&(itemStates & ITEMIS_INSVN)&&((~itemStates) & ITEMIS_ADDED))
795 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYMENU, 0, idCmdFirst, ShellMenuDropCopy, uFlags);
797 // Git copy and rename here, source and target from same repository
798 // available if source is a single, versioned but not added item, target is versioned or target folder is added
799 if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINSVN)&&(itemStates & ITEMIS_INSVN)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
800 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYRENAMEMENU, 0, idCmdFirst, ShellMenuDropCopyRename, uFlags);
802 // Git add here
803 // available if target is versioned and source is either unversioned or from another repository
804 if ((itemStatesFolder & ITEMIS_FOLDERINSVN)&&(((~itemStates) & ITEMIS_INSVN)||!bSourceAndTargetFromSameRepository))
805 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYADDMENU, 0, idCmdFirst, ShellMenuDropCopyAdd, uFlags);
807 // Git export here
808 // available if source is versioned and a folder
809 if ((itemStates & ITEMIS_INSVN)&&(itemStates & ITEMIS_FOLDER))
810 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
812 // Git export all here
813 // available if source is versioned and a folder
814 if ((itemStates & ITEMIS_INSVN)&&(itemStates & ITEMIS_FOLDER))
815 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
817 // apply patch
818 // available if source is a patchfile
819 if (itemStates & ITEMIS_PATCHFILE)
820 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_MENUAPPLYPATCH, 0, idCmdFirst, ShellMenuApplyPatch, uFlags);
822 // separator
823 if (idCmd != idCmdFirst)
824 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
826 TweakMenu(hMenu);
828 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
831 STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu,
832 UINT indexMenu,
833 UINT idCmdFirst,
834 UINT /*idCmdLast*/,
835 UINT uFlags)
837 ATLTRACE("Shell :: QueryContextMenu\n");
838 PreserveChdir preserveChdir;
840 //first check if our drop handler is called
841 //and then (if true) provide the context menu for the
842 //drop handler
843 if (m_State == FileStateDropHandler)
845 return QueryDropContext(uFlags, idCmdFirst, hMenu, indexMenu);
848 if ((uFlags & CMF_DEFAULTONLY)!=0)
849 return NOERROR; //we don't change the default action
851 if ((files_.size() == 0)&&(folder_.size() == 0))
852 return NOERROR;
854 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
855 return NOERROR;
857 int csidlarray[] =
859 CSIDL_BITBUCKET,
860 CSIDL_CDBURN_AREA,
861 CSIDL_COMMON_FAVORITES,
862 CSIDL_COMMON_STARTMENU,
863 CSIDL_COMPUTERSNEARME,
864 CSIDL_CONNECTIONS,
865 CSIDL_CONTROLS,
866 CSIDL_COOKIES,
867 CSIDL_FAVORITES,
868 CSIDL_FONTS,
869 CSIDL_HISTORY,
870 CSIDL_INTERNET,
871 CSIDL_INTERNET_CACHE,
872 CSIDL_NETHOOD,
873 CSIDL_NETWORK,
874 CSIDL_PRINTERS,
875 CSIDL_PRINTHOOD,
876 CSIDL_RECENT,
877 CSIDL_SENDTO,
878 CSIDL_STARTMENU,
881 if (IsIllegalFolder(folder_, csidlarray))
882 return NOERROR;
884 if (folder_.empty())
886 // folder is empty, but maybe files are selected
887 if (files_.size() == 0)
888 return NOERROR; // nothing selected - we don't have a menu to show
889 // check whether a selected entry is an UID - those are namespace extensions
890 // which we can't handle
891 for (std::vector<stdstring>::const_iterator it = files_.begin(); it != files_.end(); ++it)
893 if (_tcsncmp(it->c_str(), _T("::{"), 3)==0)
894 return NOERROR;
898 if (((uFlags & CMF_EXTENDEDVERBS) == 0) && g_ShellCache.HideMenusForUnversionedItems())
900 if ((itemStates & (ITEMIS_INSVN|ITEMIS_INVERSIONEDFOLDER|ITEMIS_FOLDERINSVN))==0)
901 return S_OK;
904 //check if our menu is requested for a subversion admin directory
905 if (g_GitAdminDir.IsAdminDirPath(folder_.c_str()))
906 return NOERROR;
908 if (uFlags & CMF_EXTENDEDVERBS)
909 itemStates |= ITEMIS_EXTENDED;
911 const BOOL bShortcut = !!(uFlags & CMF_VERBSONLY);
912 if ( bShortcut && (files_.size()==1))
914 // Don't show the context menu for a link if the
915 // destination is not part of a working copy.
916 // It would only show the standard menu items
917 // which are already shown for the lnk-file.
918 CString path = files_.front().c_str();
919 if ( !g_GitAdminDir.HasAdminDir(path) )
921 return NOERROR;
925 //check if we already added our menu entry for a folder.
926 //we check that by iterating through all menu entries and check if
927 //the dwItemData member points to our global ID string. That string is set
928 //by our shell extension when the folder menu is inserted.
929 TCHAR menubuf[MAX_PATH];
930 int count = GetMenuItemCount(hMenu);
931 for (int i=0; i<count; ++i)
933 MENUITEMINFO miif;
934 SecureZeroMemory(&miif, sizeof(MENUITEMINFO));
935 miif.cbSize = sizeof(MENUITEMINFO);
936 miif.fMask = MIIM_DATA;
937 miif.dwTypeData = menubuf;
938 miif.cch = _countof(menubuf);
939 GetMenuItemInfo(hMenu, i, TRUE, &miif);
940 if (miif.dwItemData == (ULONG_PTR)g_MenuIDString)
941 return NOERROR;
944 LoadLangDll();
945 UINT idCmd = idCmdFirst;
947 //create the sub menu
948 HMENU subMenu = CreateMenu();
949 int indexSubMenu = 0;
951 unsigned __int64 topmenu = g_ShellCache.GetMenuLayout();
952 unsigned __int64 menumask = g_ShellCache.GetMenuMask();
953 unsigned __int64 menuex = g_ShellCache.GetMenuExt();
955 int menuIndex = 0;
956 bool bAddSeparator = false;
957 bool bMenuEntryAdded = false;
958 bool bMenuEmpty = true;
959 // insert separator at start
960 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL); idCmd++;
961 bool bShowIcons = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\ShowContextMenuIcons"), TRUE));
963 #if 0
964 if (itemStates & (ITEMIS_INSVN|ITEMIS_FOLDERINSVN))
966 // show current branch name (as a "read-only" menu entry)
968 CTGitPath path(folder_.empty() ? files_.front().c_str() : folder_.c_str());
969 CString sProjectRoot;
970 CString sBranchName;
972 if (path.HasAdminDir(&sProjectRoot) && !g_Git.GetCurrentBranchFromFile(sProjectRoot, sBranchName))
974 if (sBranchName.GetLength() == 40)
976 // if SHA1 only show 4 first bytes
977 BOOL bIsSha1 = TRUE;
978 for (int i=0; i<40; i++)
979 if ( !iswxdigit(sBranchName[i]) )
981 bIsSha1 = FALSE;
982 break;
984 if (bIsSha1)
985 sBranchName = sBranchName.Left(8) + _T("....");
988 sBranchName = _T('"') + sBranchName + _T('"');
990 const int icon = IDI_COPY;
991 const int pos = indexMenu++;
992 const int id = idCmd++;
994 MENUITEMINFO menuiteminfo;
995 SecureZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
996 menuiteminfo.cbSize = sizeof(menuiteminfo);
997 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING | MIIM_STATE;
998 menuiteminfo.fState = MFS_DISABLED;
999 menuiteminfo.fType = MFT_STRING;
1000 menuiteminfo.dwTypeData = (LPWSTR)sBranchName.GetString();
1001 if (icon)
1003 menuiteminfo.fMask |= MIIM_BITMAP;
1004 menuiteminfo.hbmpItem = (SysInfo::Instance().IsVistaOrLater()) ? IconToBitmapPARGB32(icon) : HBMMENU_CALLBACK;
1006 if (menuiteminfo.hbmpItem == HBMMENU_CALLBACK)
1008 // WM_DRAWITEM uses myIDMap to get icon, we use the same icon as create branch
1009 myIDMap[id - idCmdFirst] = ShellMenuBranch;
1010 myIDMap[id] = ShellMenuBranch;
1013 menuiteminfo.wID = id;
1014 InsertMenuItem(hMenu, pos, TRUE, &menuiteminfo);
1017 #endif
1019 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1021 if (menuInfo[menuIndex].command == ShellSeparator)
1023 // we don't add a separator immediately. Because there might not be
1024 // another 'normal' menu entry after we insert a separator.
1025 // we simply set a flag here, indicating that before the next
1026 // 'normal' menu entry, a separator should be added.
1027 if (!bMenuEmpty)
1028 bAddSeparator = true;
1030 else
1032 // check the conditions whether to show the menu entry or not
1033 bool bInsertMenu = false;
1035 if (menuInfo[menuIndex].firstyes && menuInfo[menuIndex].firstno)
1037 if (((menuInfo[menuIndex].firstyes & itemStates) == menuInfo[menuIndex].firstyes)
1039 ((menuInfo[menuIndex].firstno & (~itemStates)) == menuInfo[menuIndex].firstno))
1040 bInsertMenu = true;
1042 else if ((menuInfo[menuIndex].firstyes)&&((menuInfo[menuIndex].firstyes & itemStates) == menuInfo[menuIndex].firstyes))
1043 bInsertMenu = true;
1044 else if ((menuInfo[menuIndex].firstno)&&((menuInfo[menuIndex].firstno & (~itemStates)) == menuInfo[menuIndex].firstno))
1045 bInsertMenu = true;
1047 if (menuInfo[menuIndex].secondyes && menuInfo[menuIndex].secondno)
1049 if (((menuInfo[menuIndex].secondyes & itemStates) == menuInfo[menuIndex].secondyes)
1051 ((menuInfo[menuIndex].secondno & (~itemStates)) == menuInfo[menuIndex].secondno))
1052 bInsertMenu = true;
1054 else if ((menuInfo[menuIndex].secondyes)&&((menuInfo[menuIndex].secondyes & itemStates) == menuInfo[menuIndex].secondyes))
1055 bInsertMenu = true;
1056 else if ((menuInfo[menuIndex].secondno)&&((menuInfo[menuIndex].secondno & (~itemStates)) == menuInfo[menuIndex].secondno))
1057 bInsertMenu = true;
1059 if (menuInfo[menuIndex].thirdyes && menuInfo[menuIndex].thirdno)
1061 if (((menuInfo[menuIndex].thirdyes & itemStates) == menuInfo[menuIndex].thirdyes)
1063 ((menuInfo[menuIndex].thirdno & (~itemStates)) == menuInfo[menuIndex].thirdno))
1064 bInsertMenu = true;
1066 else if ((menuInfo[menuIndex].thirdyes)&&((menuInfo[menuIndex].thirdyes & itemStates) == menuInfo[menuIndex].thirdyes))
1067 bInsertMenu = true;
1068 else if ((menuInfo[menuIndex].thirdno)&&((menuInfo[menuIndex].thirdno & (~itemStates)) == menuInfo[menuIndex].thirdno))
1069 bInsertMenu = true;
1071 if (menuInfo[menuIndex].fourthyes && menuInfo[menuIndex].fourthno)
1073 if (((menuInfo[menuIndex].fourthyes & itemStates) == menuInfo[menuIndex].fourthyes)
1075 ((menuInfo[menuIndex].fourthno & (~itemStates)) == menuInfo[menuIndex].fourthno))
1076 bInsertMenu = true;
1078 else if ((menuInfo[menuIndex].fourthyes)&&((menuInfo[menuIndex].fourthyes & itemStates) == menuInfo[menuIndex].fourthyes))
1079 bInsertMenu = true;
1080 else if ((menuInfo[menuIndex].fourthno)&&((menuInfo[menuIndex].fourthno & (~itemStates)) == menuInfo[menuIndex].fourthno))
1081 bInsertMenu = true;
1083 if (menuInfo[menuIndex].menuID & menuex)
1085 if( !(itemStates & ITEMIS_EXTENDED) )
1087 bInsertMenu = false;
1091 if (menuInfo[menuIndex].menuID & (~menumask))
1093 if (bInsertMenu)
1095 bool bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1096 // insert a separator
1097 if ((bMenuEntryAdded)&&(bAddSeparator)&&(!bIsTop))
1099 bAddSeparator = false;
1100 bMenuEntryAdded = false;
1101 InsertMenu(subMenu, indexSubMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
1102 idCmd++;
1105 // handle special cases (sub menus)
1106 if ((menuInfo[menuIndex].command == ShellMenuIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuUnIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuDeleteIgnoreSub))
1108 if(InsertIgnoreSubmenus(idCmd, idCmdFirst, hMenu, subMenu, indexMenu, indexSubMenu, topmenu, bShowIcons, uFlags))
1110 bMenuEntryAdded = true;
1111 bMenuEmpty = false;
1114 else
1116 bool bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1118 // insert the menu entry
1119 InsertGitMenu( bIsTop,
1120 bIsTop ? hMenu : subMenu,
1121 bIsTop ? indexMenu++ : indexSubMenu++,
1122 idCmd++,
1123 menuInfo[menuIndex].menuTextID,
1124 bShowIcons ? menuInfo[menuIndex].iconID : 0,
1125 idCmdFirst,
1126 menuInfo[menuIndex].command,
1127 uFlags);
1128 if (!bIsTop)
1130 bMenuEntryAdded = true;
1131 bMenuEmpty = false;
1132 bAddSeparator = false;
1138 menuIndex++;
1141 //add sub menu to main context menu
1142 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1143 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1144 MAKESTRING(IDS_MENUSUBMENU);
1145 MENUITEMINFO menuiteminfo;
1146 SecureZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
1147 menuiteminfo.cbSize = sizeof(menuiteminfo);
1148 menuiteminfo.fType = MFT_STRING;
1149 menuiteminfo.dwTypeData = stringtablebuffer;
1151 UINT uIcon = bShowIcons ? IDI_APP : 0;
1152 if (folder_.size())
1154 uIcon = bShowIcons ? IDI_MENUFOLDER : 0;
1155 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFolder;
1156 myIDMap[idCmd] = ShellSubMenuFolder;
1157 menuiteminfo.dwItemData = (ULONG_PTR)g_MenuIDString;
1159 else if (!bShortcut && (files_.size()==1))
1161 uIcon = bShowIcons ? IDI_MENUFILE : 0;
1162 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFile;
1163 myIDMap[idCmd] = ShellSubMenuFile;
1165 else if (bShortcut && (files_.size()==1))
1167 uIcon = bShowIcons ? IDI_MENULINK : 0;
1168 myIDMap[idCmd - idCmdFirst] = ShellSubMenuLink;
1169 myIDMap[idCmd] = ShellSubMenuLink;
1171 else if (files_.size() > 1)
1173 uIcon = bShowIcons ? IDI_MENUMULTIPLE : 0;
1174 myIDMap[idCmd - idCmdFirst] = ShellSubMenuMultiple;
1175 myIDMap[idCmd] = ShellSubMenuMultiple;
1177 else
1179 myIDMap[idCmd - idCmdFirst] = ShellSubMenu;
1180 myIDMap[idCmd] = ShellSubMenu;
1182 HBITMAP bmp = NULL;
1183 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
1184 if (uIcon)
1186 menuiteminfo.fMask |= MIIM_BITMAP;
1187 menuiteminfo.hbmpItem = (SysInfo::Instance().IsVistaOrLater()) ? IconToBitmapPARGB32(uIcon) : HBMMENU_CALLBACK;
1189 menuiteminfo.hSubMenu = subMenu;
1190 menuiteminfo.wID = idCmd++;
1191 InsertMenuItem(hMenu, indexMenu++, TRUE, &menuiteminfo);
1193 //separator after
1194 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL); idCmd++;
1196 TweakMenu(hMenu);
1198 //return number of menu items added
1199 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
1202 void CShellExt::TweakMenu(HMENU hMenu)
1204 MENUINFO MenuInfo = {};
1205 MenuInfo.cbSize = sizeof(MenuInfo);
1206 MenuInfo.fMask = MIM_STYLE | MIM_APPLYTOSUBMENUS;
1207 MenuInfo.dwStyle = MNS_CHECKORBMP;
1208 SetMenuInfo(hMenu, &MenuInfo);
1211 // This is called when you invoke a command on the menu:
1212 STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
1214 PreserveChdir preserveChdir;
1215 HRESULT hr = E_INVALIDARG;
1216 if (lpcmi == NULL)
1217 return hr;
1219 std::string command;
1220 std::string parent;
1221 std::string file;
1223 if ((files_.size() > 0)||(folder_.size() > 0))
1225 UINT idCmd = LOWORD(lpcmi->lpVerb);
1227 if (HIWORD(lpcmi->lpVerb))
1229 stdstring verb = stdstring(MultibyteToWide(lpcmi->lpVerb));
1230 std::map<stdstring, UINT_PTR>::const_iterator verb_it = myVerbsMap.lower_bound(verb);
1231 if (verb_it != myVerbsMap.end() && verb_it->first == verb)
1232 idCmd = verb_it->second;
1233 else
1234 return hr;
1237 // See if we have a handler interface for this id
1238 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
1239 if (id_it != myIDMap.end() && id_it->first == idCmd)
1241 STARTUPINFO startup;
1242 PROCESS_INFORMATION process;
1243 memset(&startup, 0, sizeof(startup));
1244 startup.cb = sizeof(startup);
1245 memset(&process, 0, sizeof(process));
1246 CRegStdString tortoiseProcPath(_T("Software\\TortoiseGit\\ProcPath"), _T("TortoiseProc.exe"), false, HKEY_LOCAL_MACHINE);
1247 CRegStdString tortoiseMergePath(_T("Software\\TortoiseGit\\TMergePath"), _T("TortoiseMerge.exe"), false, HKEY_LOCAL_MACHINE);
1249 //TortoiseProc expects a command line of the form:
1250 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1251 // or
1252 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1254 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1255 //* pathfile is a path to a temporary file which contains a list of file paths
1256 stdstring svnCmd = _T(" /command:");
1257 stdstring tempfile;
1258 switch (id_it->second)
1260 //#region case
1261 case ShellMenuSync:
1262 svnCmd += _T("sync /path:\"");
1263 if (files_.size() > 0)
1264 svnCmd += files_.front();
1265 else
1266 svnCmd += folder_;
1267 svnCmd += _T("\"");
1268 break;
1269 case ShellMenuUpdate:
1270 tempfile = WriteFileListToTempFile();
1271 svnCmd += _T("update /pathfile:\"");
1272 svnCmd += tempfile;
1273 svnCmd += _T("\"");
1274 svnCmd += _T(" /deletepathfile");
1275 break;
1276 case ShellMenuSubSync:
1277 tempfile = WriteFileListToTempFile();
1278 svnCmd += _T("subsync /pathfile:\"");
1279 svnCmd += tempfile;
1280 svnCmd += _T("\"");
1281 svnCmd += _T(" /deletepathfile");
1282 if(itemStatesFolder&ITEMIS_SUBMODULECONTAINER)
1284 svnCmd += _T(" /bkpath:\"");
1285 svnCmd += folder_;
1286 svnCmd += _T("\"");
1288 break;
1289 case ShellMenuUpdateExt:
1290 tempfile = WriteFileListToTempFile();
1291 svnCmd += _T("subupdate /pathfile:\"");
1292 svnCmd += tempfile;
1293 svnCmd += _T("\"");
1294 svnCmd += _T(" /deletepathfile");
1295 if(itemStatesFolder&ITEMIS_SUBMODULECONTAINER)
1297 svnCmd += _T(" /bkpath:\"");
1298 svnCmd += folder_;
1299 svnCmd += _T("\"");
1301 break;
1302 case ShellMenuCommit:
1303 tempfile = WriteFileListToTempFile();
1304 svnCmd += _T("commit /pathfile:\"");
1305 svnCmd += tempfile;
1306 svnCmd += _T("\"");
1307 svnCmd += _T(" /deletepathfile");
1308 break;
1309 case ShellMenuAdd:
1310 case ShellMenuAddAsReplacement:
1311 tempfile = WriteFileListToTempFile();
1312 svnCmd += _T("add /pathfile:\"");
1313 svnCmd += tempfile;
1314 svnCmd += _T("\"");
1315 svnCmd += _T(" /deletepathfile");
1316 break;
1317 case ShellMenuIgnore:
1318 tempfile = WriteFileListToTempFile();
1319 svnCmd += _T("ignore /pathfile:\"");
1320 svnCmd += tempfile;
1321 svnCmd += _T("\"");
1322 svnCmd += _T(" /deletepathfile");
1323 break;
1324 case ShellMenuIgnoreCaseSensitive:
1325 tempfile = WriteFileListToTempFile();
1326 svnCmd += _T("ignore /pathfile:\"");
1327 svnCmd += tempfile;
1328 svnCmd += _T("\"");
1329 svnCmd += _T(" /deletepathfile");
1330 svnCmd += _T(" /onlymask");
1331 break;
1332 case ShellMenuDeleteIgnore:
1333 tempfile = WriteFileListToTempFile();
1334 svnCmd += _T("ignore /delete /pathfile:\"");
1335 svnCmd += tempfile;
1336 svnCmd += _T("\"");
1337 svnCmd += _T(" /deletepathfile");
1338 break;
1339 case ShellMenuDeleteIgnoreCaseSensitive:
1340 tempfile = WriteFileListToTempFile();
1341 svnCmd += _T("ignore /delete /pathfile:\"");
1342 svnCmd += tempfile;
1343 svnCmd += _T("\"");
1344 svnCmd += _T(" /deletepathfile");
1345 svnCmd += _T(" /onlymask");
1346 break;
1347 case ShellMenuUnIgnore:
1348 tempfile = WriteFileListToTempFile();
1349 svnCmd += _T("unignore /pathfile:\"");
1350 svnCmd += tempfile;
1351 svnCmd += _T("\"");
1352 svnCmd += _T(" /deletepathfile");
1353 break;
1354 case ShellMenuUnIgnoreCaseSensitive:
1355 tempfile = WriteFileListToTempFile();
1356 svnCmd += _T("unignore /pathfile:\"");
1357 svnCmd += tempfile;
1358 svnCmd += _T("\"");
1359 svnCmd += _T(" /deletepathfile");
1360 svnCmd += _T(" /onlymask");
1361 break;
1362 case ShellMenuRevert:
1363 tempfile = WriteFileListToTempFile();
1364 svnCmd += _T("revert /pathfile:\"");
1365 svnCmd += tempfile;
1366 svnCmd += _T("\"");
1367 svnCmd += _T(" /deletepathfile");
1368 break;
1369 case ShellMenuCleanup:
1370 tempfile = WriteFileListToTempFile();
1371 svnCmd += _T("cleanup /pathfile:\"");
1372 svnCmd += tempfile;
1373 svnCmd += _T("\"");
1374 svnCmd += _T(" /deletepathfile");
1375 break;
1376 case ShellMenuSendMail:
1377 tempfile = WriteFileListToTempFile();
1378 svnCmd += _T("sendmail /pathfile:\"");
1379 svnCmd += tempfile;
1380 svnCmd += _T("\"");
1381 svnCmd += _T(" /deletepathfile");
1382 break;
1383 case ShellMenuResolve:
1384 tempfile = WriteFileListToTempFile();
1385 svnCmd += _T("resolve /pathfile:\"");
1386 svnCmd += tempfile;
1387 svnCmd += _T("\"");
1388 svnCmd += _T(" /deletepathfile");
1389 break;
1390 case ShellMenuSwitch:
1391 svnCmd += _T("switch /path:\"");
1392 if (files_.size() > 0)
1393 svnCmd += files_.front();
1394 else
1395 svnCmd += folder_;
1396 svnCmd += _T("\"");
1397 break;
1398 case ShellMenuExport:
1399 svnCmd += _T("export /path:\"");
1400 if (files_.size() > 0)
1401 svnCmd += files_.front();
1402 else
1403 svnCmd += folder_;
1404 svnCmd += _T("\"");
1405 break;
1406 case ShellMenuAbout:
1407 svnCmd += _T("about");
1408 break;
1409 case ShellMenuCreateRepos:
1410 svnCmd += _T("repocreate /path:\"");
1411 if (files_.size() > 0)
1412 svnCmd += files_.front();
1413 else
1414 svnCmd += folder_;
1415 svnCmd += _T("\"");
1416 break;
1417 case ShellMenuMerge:
1418 svnCmd += _T("merge /path:\"");
1419 if (files_.size() > 0)
1420 svnCmd += files_.front();
1421 else
1422 svnCmd += folder_;
1423 svnCmd += _T("\"");
1424 break;
1425 case ShellMenuCopy:
1426 svnCmd += _T("copy /path:\"");
1427 if (files_.size() > 0)
1428 svnCmd += files_.front();
1429 else
1430 svnCmd += folder_;
1431 svnCmd += _T("\"");
1432 break;
1433 case ShellMenuSettings:
1434 svnCmd += _T("settings /path:\"");
1435 if (files_.size() > 0)
1436 svnCmd += files_.front();
1437 else
1438 svnCmd += folder_;
1439 svnCmd += _T("\"");
1440 break;
1441 case ShellMenuHelp:
1442 svnCmd += _T("help");
1443 break;
1444 case ShellMenuRename:
1445 svnCmd += _T("rename /path:\"");
1446 if (files_.size() > 0)
1447 svnCmd += files_.front();
1448 else
1449 svnCmd += folder_;
1450 svnCmd += _T("\"");
1451 break;
1452 case ShellMenuRemove:
1453 tempfile = WriteFileListToTempFile();
1454 svnCmd += _T("remove /pathfile:\"");
1455 svnCmd += tempfile;
1456 svnCmd += _T("\"");
1457 svnCmd += _T(" /deletepathfile");
1458 break;
1459 case ShellMenuRemoveKeep:
1460 tempfile = WriteFileListToTempFile();
1461 svnCmd += _T("remove /pathfile:\"");
1462 svnCmd += tempfile;
1463 svnCmd += _T("\"");
1464 svnCmd += _T(" /deletepathfile");
1465 svnCmd += _T(" /keep");
1466 break;
1467 case ShellMenuDiff:
1468 svnCmd += _T("diff /path:\"");
1469 if (files_.size() == 1)
1470 svnCmd += files_.front();
1471 else if (files_.size() == 2)
1473 std::vector<stdstring>::iterator I = files_.begin();
1474 svnCmd += *I;
1475 I++;
1476 svnCmd += _T("\" /path2:\"");
1477 svnCmd += *I;
1479 else
1480 svnCmd += folder_;
1481 svnCmd += _T("\"");
1482 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1483 svnCmd += _T(" /alternative");
1484 break;
1485 case ShellMenuPrevDiff:
1486 svnCmd += _T("prevdiff /path:\"");
1487 if (files_.size() == 1)
1488 svnCmd += files_.front();
1489 else
1490 svnCmd += folder_;
1491 svnCmd += _T("\"");
1492 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1493 svnCmd += _T(" /alternative");
1494 break;
1495 case ShellMenuDiffTwo:
1496 svnCmd += _T("diffcommits /path:\"");
1497 if (files_.size() == 1)
1498 svnCmd += files_.front();
1499 else
1500 svnCmd += folder_;
1501 svnCmd += _T("\"");
1502 break;
1503 case ShellMenuDropCopyAdd:
1504 tempfile = WriteFileListToTempFile();
1505 svnCmd += _T("dropcopyadd /pathfile:\"");
1506 svnCmd += tempfile;
1507 svnCmd += _T("\"");
1508 svnCmd += _T(" /deletepathfile");
1509 svnCmd += _T(" /droptarget:\"");
1510 svnCmd += folder_;
1511 svnCmd += _T("\"";)
1512 break;
1513 case ShellMenuDropCopy:
1514 tempfile = WriteFileListToTempFile();
1515 svnCmd += _T("dropcopy /pathfile:\"");
1516 svnCmd += tempfile;
1517 svnCmd += _T("\"");
1518 svnCmd += _T(" /deletepathfile");
1519 svnCmd += _T(" /droptarget:\"");
1520 svnCmd += folder_;
1521 svnCmd += _T("\"";)
1522 break;
1523 case ShellMenuDropCopyRename:
1524 tempfile = WriteFileListToTempFile();
1525 svnCmd += _T("dropcopy /pathfile:\"");
1526 svnCmd += tempfile;
1527 svnCmd += _T("\"");
1528 svnCmd += _T(" /deletepathfile");
1529 svnCmd += _T(" /droptarget:\"");
1530 svnCmd += folder_;
1531 svnCmd += _T("\" /rename";)
1532 break;
1533 case ShellMenuDropMove:
1534 tempfile = WriteFileListToTempFile();
1535 svnCmd += _T("dropmove /pathfile:\"");
1536 svnCmd += tempfile;
1537 svnCmd += _T("\"");
1538 svnCmd += _T(" /deletepathfile");
1539 svnCmd += _T(" /droptarget:\"");
1540 svnCmd += folder_;
1541 svnCmd += _T("\"");
1542 break;
1543 case ShellMenuDropMoveRename:
1544 tempfile = WriteFileListToTempFile();
1545 svnCmd += _T("dropmove /pathfile:\"");
1546 svnCmd += tempfile;
1547 svnCmd += _T("\"");
1548 svnCmd += _T(" /deletepathfile");
1549 svnCmd += _T(" /droptarget:\"");
1550 svnCmd += folder_;
1551 svnCmd += _T("\" /rename";)
1552 break;
1553 case ShellMenuDropExport:
1554 tempfile = WriteFileListToTempFile();
1555 svnCmd += _T("dropexport /pathfile:\"");
1556 svnCmd += tempfile;
1557 svnCmd += _T("\"");
1558 svnCmd += _T(" /deletepathfile");
1559 svnCmd += _T(" /droptarget:\"");
1560 svnCmd += folder_;
1561 svnCmd += _T("\"");
1562 break;
1563 case ShellMenuDropExportExtended:
1564 tempfile = WriteFileListToTempFile();
1565 svnCmd += _T("dropexport /pathfile:\"");
1566 svnCmd += tempfile;
1567 svnCmd += _T("\"");
1568 svnCmd += _T(" /deletepathfile");
1569 svnCmd += _T(" /droptarget:\"");
1570 svnCmd += folder_;
1571 svnCmd += _T("\"");
1572 svnCmd += _T(" /extended");
1573 break;
1574 case ShellMenuLog:
1575 svnCmd += _T("log /path:\"");
1576 if (files_.size() > 0)
1577 svnCmd += files_.front();
1578 else
1579 svnCmd += folder_;
1580 svnCmd += _T("\"");
1581 break;
1582 case ShellMenuConflictEditor:
1583 svnCmd += _T("conflicteditor /path:\"");
1584 if (files_.size() > 0)
1585 svnCmd += files_.front();
1586 else
1587 svnCmd += folder_;
1588 svnCmd += _T("\"");
1589 break;
1590 case ShellMenuGitSVNRebase:
1591 svnCmd += _T("svnrebase /path:\"");
1592 if (files_.size() > 0)
1593 svnCmd += files_.front();
1594 else
1595 svnCmd += folder_;
1596 svnCmd += _T("\"");
1597 break;
1598 case ShellMenuGitSVNDCommit:
1599 svnCmd += _T("svndcommit /path:\"");
1600 if (files_.size() > 0)
1601 svnCmd += files_.front();
1602 else
1603 svnCmd += folder_;
1604 svnCmd += _T("\"");
1605 break;
1606 case ShellMenuGitSVNIgnore:
1607 svnCmd += _T("svnignore /path:\"");
1608 if (files_.size() > 0)
1609 svnCmd += files_.front();
1610 else
1611 svnCmd += folder_;
1612 svnCmd += _T("\"");
1613 break;
1614 case ShellMenuRebase:
1615 svnCmd += _T("rebase /path:\"");
1616 if (files_.size() > 0)
1617 svnCmd += files_.front();
1618 else
1619 svnCmd += folder_;
1620 svnCmd += _T("\"");
1621 break;
1622 case ShellMenuShowChanged:
1623 if (files_.size() > 1)
1625 tempfile = WriteFileListToTempFile();
1626 svnCmd += _T("repostatus /pathfile:\"");
1627 svnCmd += tempfile;
1628 svnCmd += _T("\"");
1629 svnCmd += _T(" /deletepathfile");
1631 else
1633 svnCmd += _T("repostatus /path:\"");
1634 if (files_.size() > 0)
1635 svnCmd += files_.front();
1636 else
1637 svnCmd += folder_;
1638 svnCmd += _T("\"");
1640 break;
1641 case ShellMenuRefBrowse:
1642 svnCmd += _T("refbrowse /path:\"");
1643 if (files_.size() > 0)
1644 svnCmd += files_.front();
1645 else
1646 svnCmd += folder_;
1647 svnCmd += _T("\"");
1648 break;
1649 case ShellMenuRefLog:
1650 svnCmd += _T("reflog /path:\"");
1651 if (files_.size() > 0)
1652 svnCmd += files_.front();
1653 else
1654 svnCmd += folder_;
1655 svnCmd += _T("\"");
1656 break;
1658 case ShellMenuStashSave:
1659 svnCmd += _T("stashsave /path:\"");
1660 if (files_.size() > 0)
1661 svnCmd += files_.front();
1662 else
1663 svnCmd += folder_;
1664 svnCmd += _T("\"");
1665 break;
1667 case ShellMenuStashApply:
1668 svnCmd += _T("stashapply /path:\"");
1669 if (files_.size() > 0)
1670 svnCmd += files_.front();
1671 else
1672 svnCmd += folder_;
1673 svnCmd += _T("\"");
1674 break;
1676 case ShellMenuStashPop:
1677 svnCmd += _T("stashpop /path:\"");
1678 if (files_.size() > 0)
1679 svnCmd += files_.front();
1680 else
1681 svnCmd += folder_;
1682 svnCmd += _T("\"");
1683 break;
1686 case ShellMenuStashList:
1687 svnCmd += _T("reflog /path:\"");
1688 if (files_.size() > 0)
1689 svnCmd += files_.front();
1690 else
1691 svnCmd += folder_;
1692 svnCmd += _T("\" /ref:refs/stash");
1693 break;
1695 case ShellMenuBisectStart:
1696 svnCmd += _T("bisect /path:\"");
1697 if (files_.size() > 0)
1698 svnCmd += files_.front();
1699 else
1700 svnCmd += folder_;
1701 svnCmd += _T("\" /start");
1702 break;
1704 case ShellMenuBisectGood:
1705 svnCmd += _T("bisect /path:\"");
1706 if (files_.size() > 0)
1707 svnCmd += files_.front();
1708 else
1709 svnCmd += folder_;
1710 svnCmd += _T("\" /good");
1711 break;
1714 case ShellMenuBisectBad:
1715 svnCmd += _T("bisect /path:\"");
1716 if (files_.size() > 0)
1717 svnCmd += files_.front();
1718 else
1719 svnCmd += folder_;
1720 svnCmd += _T("\" /bad");
1721 break;
1723 case ShellMenuBisectReset:
1724 svnCmd += _T("bisect /path:\"");
1725 if (files_.size() > 0)
1726 svnCmd += files_.front();
1727 else
1728 svnCmd += folder_;
1729 svnCmd += _T("\" /reset");
1730 break;
1732 case ShellMenuSubAdd:
1733 svnCmd += _T("subadd /path:\"");
1734 if (files_.size() > 0)
1735 svnCmd += files_.front();
1736 else
1737 svnCmd += folder_;
1738 svnCmd += _T("\"");
1739 break;
1741 case ShellMenuBlame:
1742 svnCmd += _T("blame /path:\"");
1743 if (files_.size() > 0)
1744 svnCmd += files_.front();
1745 else
1746 svnCmd += folder_;
1747 svnCmd += _T("\"");
1748 break;
1749 case ShellMenuApplyPatch:
1750 if ((itemStates & ITEMIS_PATCHINCLIPBOARD) && ((~itemStates) & ITEMIS_PATCHFILE))
1752 // if there's a patch file in the clipboard, we save it
1753 // to a temporary file and tell TortoiseMerge to use that one
1754 UINT cFormat = RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
1755 if ((cFormat)&&(OpenClipboard(NULL)))
1757 HGLOBAL hglb = GetClipboardData(cFormat);
1758 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
1760 DWORD len = GetTempPath(0, NULL);
1761 TCHAR * path = new TCHAR[len+1];
1762 TCHAR * tempF = new TCHAR[len+100];
1763 GetTempPath (len+1, path);
1764 GetTempFileName (path, TEXT("git"), 0, tempF);
1765 std::wstring sTempFile = std::wstring(tempF);
1766 delete [] path;
1767 delete [] tempF;
1769 FILE * outFile;
1770 size_t patchlen = strlen(lpstr);
1771 _tfopen_s(&outFile, sTempFile.c_str(), _T("wb"));
1772 if(outFile)
1774 size_t size = fwrite(lpstr, sizeof(char), patchlen, outFile);
1775 if (size == patchlen)
1777 itemStates |= ITEMIS_PATCHFILE;
1778 files_.clear();
1779 files_.push_back(sTempFile);
1781 fclose(outFile);
1783 GlobalUnlock(hglb);
1784 CloseClipboard();
1787 if (itemStates & ITEMIS_PATCHFILE)
1789 svnCmd = _T(" /diff:\"");
1790 if (files_.size() > 0)
1792 svnCmd += files_.front();
1793 if (itemStatesFolder & ITEMIS_FOLDERINSVN)
1795 svnCmd += _T("\" /patchpath:\"");
1796 svnCmd += folder_;
1799 else
1800 svnCmd += folder_;
1801 if (itemStates & ITEMIS_INVERSIONEDFOLDER)
1802 svnCmd += _T("\" /wc");
1803 else
1804 svnCmd += _T("\"");
1806 else
1808 svnCmd = _T(" /patchpath:\"");
1809 if (files_.size() > 0)
1810 svnCmd += files_.front();
1811 else
1812 svnCmd += folder_;
1813 svnCmd += _T("\"");
1815 myIDMap.clear();
1816 myVerbsIDMap.clear();
1817 myVerbsMap.clear();
1818 if (CreateProcess(((stdstring)tortoiseMergePath).c_str(), const_cast<TCHAR*>(svnCmd.c_str()), NULL, NULL, FALSE, 0, 0, 0, &startup, &process)==0)
1820 LPVOID lpMsgBuf;
1821 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
1822 FORMAT_MESSAGE_FROM_SYSTEM |
1823 FORMAT_MESSAGE_IGNORE_INSERTS,
1824 NULL,
1825 GetLastError(),
1826 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
1827 (LPTSTR) &lpMsgBuf,
1829 NULL
1831 MessageBox( NULL, (LPCTSTR)lpMsgBuf, _T("TortoiseMerge launch failed"), MB_OK | MB_ICONINFORMATION );
1832 LocalFree( lpMsgBuf );
1834 CloseHandle(process.hThread);
1835 CloseHandle(process.hProcess);
1836 return NOERROR;
1837 break;
1838 case ShellMenuProperties:
1839 tempfile = WriteFileListToTempFile();
1840 svnCmd += _T("properties /pathfile:\"");
1841 svnCmd += tempfile;
1842 svnCmd += _T("\"");
1843 svnCmd += _T(" /deletepathfile");
1844 break;
1845 case ShellMenuClipPaste:
1846 if (WriteClipboardPathsToTempFile(tempfile))
1848 bool bCopy = true;
1849 UINT cPrefDropFormat = RegisterClipboardFormat(_T("Preferred DropEffect"));
1850 if (cPrefDropFormat)
1852 if (OpenClipboard(lpcmi->hwnd))
1854 HGLOBAL hglb = GetClipboardData(cPrefDropFormat);
1855 if (hglb)
1857 DWORD* effect = (DWORD*) GlobalLock(hglb);
1858 if (*effect == DROPEFFECT_MOVE)
1859 bCopy = false;
1860 GlobalUnlock(hglb);
1862 CloseClipboard();
1866 if (bCopy)
1867 svnCmd += _T("pastecopy /pathfile:\"");
1868 else
1869 svnCmd += _T("pastemove /pathfile:\"");
1870 svnCmd += tempfile;
1871 svnCmd += _T("\"");
1872 svnCmd += _T(" /deletepathfile");
1873 svnCmd += _T(" /droptarget:\"");
1874 svnCmd += folder_;
1875 svnCmd += _T("\"");
1877 else return NOERROR;
1878 break;
1879 case ShellMenuClone:
1880 svnCmd += _T("clone /path:\"");
1881 if (files_.size() > 0)
1882 svnCmd += files_.front();
1883 else
1884 svnCmd += folder_;
1885 svnCmd += _T("\"");
1886 break;
1887 case ShellMenuPull:
1888 svnCmd += _T("pull /path:\"");
1889 if (files_.size() > 0)
1890 svnCmd += files_.front();
1891 else
1892 svnCmd += folder_;
1893 svnCmd += _T("\"");
1894 break;
1895 case ShellMenuPush:
1896 svnCmd += _T("push /path:\"");
1897 if (files_.size() > 0)
1898 svnCmd += files_.front();
1899 else
1900 svnCmd += folder_;
1901 svnCmd += _T("\"");
1902 break;
1903 case ShellMenuBranch:
1904 svnCmd += _T("branch /path:\"");
1905 if (files_.size() > 0)
1906 svnCmd += files_.front();
1907 else
1908 svnCmd += folder_;
1909 svnCmd += _T("\"");
1910 break;
1912 case ShellMenuTag:
1913 svnCmd += _T("tag /path:\"");
1914 if (files_.size() > 0)
1915 svnCmd += files_.front();
1916 else
1917 svnCmd += folder_;
1918 svnCmd += _T("\"");
1919 break;
1921 case ShellMenuFormatPatch:
1922 svnCmd += _T("formatpatch /path:\"");
1923 if (files_.size() > 0)
1924 svnCmd += files_.front();
1925 else
1926 svnCmd += folder_;
1927 svnCmd += _T("\"");
1928 break;
1930 case ShellMenuImportPatch:
1931 tempfile = WriteFileListToTempFile();
1932 svnCmd += _T("importpatch /pathfile:\"");
1933 svnCmd += tempfile;
1934 svnCmd += _T("\"");
1935 svnCmd += _T(" /deletepathfile");
1936 break;
1938 case ShellMenuCherryPick:
1939 svnCmd += _T("cherrypick /path:\"");
1940 if (files_.size() > 0)
1941 svnCmd += files_.front();
1942 else
1943 svnCmd += folder_;
1944 svnCmd += _T("\"");
1945 break;
1946 case ShellMenuFetch:
1947 svnCmd += _T("fetch /path:\"");
1948 if (files_.size() > 0)
1949 svnCmd += files_.front();
1950 else
1951 svnCmd += folder_;
1952 svnCmd += _T("\"");
1953 break;
1955 default:
1956 break;
1957 //#endregion
1958 } // switch (id_it->second)
1959 svnCmd += _T(" /hwnd:");
1960 TCHAR buf[30];
1961 _stprintf_s(buf, 30, _T("%d"), lpcmi->hwnd);
1962 svnCmd += buf;
1963 myIDMap.clear();
1964 myVerbsIDMap.clear();
1965 myVerbsMap.clear();
1966 if (CreateProcess(((stdstring)tortoiseProcPath).c_str(), const_cast<TCHAR*>(svnCmd.c_str()), NULL, NULL, FALSE, 0, 0, 0, &startup, &process)==0)
1968 LPVOID lpMsgBuf;
1969 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
1970 FORMAT_MESSAGE_FROM_SYSTEM |
1971 FORMAT_MESSAGE_IGNORE_INSERTS,
1972 NULL,
1973 GetLastError(),
1974 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
1975 (LPTSTR) &lpMsgBuf,
1977 NULL
1979 MessageBox( NULL, (LPCTSTR)lpMsgBuf, _T("TortoiseProc Launch failed"), MB_OK | MB_ICONINFORMATION );
1980 LocalFree( lpMsgBuf );
1982 CloseHandle(process.hThread);
1983 CloseHandle(process.hProcess);
1984 hr = NOERROR;
1985 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1986 } // if ((files_.size() > 0)||(folder_.size() > 0))
1987 return hr;
1991 // This is for the status bar and things like that:
1992 STDMETHODIMP CShellExt::GetCommandString(UINT_PTR idCmd,
1993 UINT uFlags,
1994 UINT FAR * /*reserved*/,
1995 LPSTR pszName,
1996 UINT cchMax)
1998 PreserveChdir preserveChdir;
1999 //do we know the id?
2000 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
2001 if (id_it == myIDMap.end() || id_it->first != idCmd)
2003 return E_INVALIDARG; //no, we don't
2006 LoadLangDll();
2007 HRESULT hr = E_INVALIDARG;
2009 MAKESTRING(IDS_MENUDESCDEFAULT);
2010 int menuIndex = 0;
2011 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
2013 if (menuInfo[menuIndex].command == (GitCommands)id_it->second)
2015 MAKESTRING(menuInfo[menuIndex].menuDescID);
2016 break;
2018 menuIndex++;
2021 const TCHAR * desc = stringtablebuffer;
2022 switch(uFlags)
2024 case GCS_HELPTEXTA:
2026 std::string help = WideToMultibyte(desc);
2027 lstrcpynA(pszName, help.c_str(), cchMax);
2028 hr = S_OK;
2029 break;
2031 case GCS_HELPTEXTW:
2033 wide_string help = desc;
2034 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax);
2035 hr = S_OK;
2036 break;
2038 case GCS_VERBA:
2040 std::map<UINT_PTR, stdstring>::const_iterator verb_id_it = myVerbsIDMap.lower_bound(idCmd);
2041 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
2043 std::string help = WideToMultibyte(verb_id_it->second);
2044 lstrcpynA(pszName, help.c_str(), cchMax);
2045 hr = S_OK;
2048 break;
2049 case GCS_VERBW:
2051 std::map<UINT_PTR, stdstring>::const_iterator verb_id_it = myVerbsIDMap.lower_bound(idCmd);
2052 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
2054 wide_string help = verb_id_it->second;
2055 ATLTRACE("verb : %ws\n", help.c_str());
2056 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax);
2057 hr = S_OK;
2060 break;
2062 return hr;
2065 STDMETHODIMP CShellExt::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
2067 LRESULT res;
2068 return HandleMenuMsg2(uMsg, wParam, lParam, &res);
2071 STDMETHODIMP CShellExt::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pResult)
2073 PreserveChdir preserveChdir;
2075 LRESULT res;
2076 if (pResult == NULL)
2077 pResult = &res;
2078 *pResult = FALSE;
2080 LoadLangDll();
2081 switch (uMsg)
2083 case WM_MEASUREITEM:
2085 MEASUREITEMSTRUCT* lpmis = (MEASUREITEMSTRUCT*)lParam;
2086 if (lpmis==NULL||lpmis->CtlType!=ODT_MENU)
2087 break;
2088 lpmis->itemWidth = 16;
2089 lpmis->itemHeight = 16;
2090 *pResult = TRUE;
2092 break;
2093 case WM_DRAWITEM:
2095 LPCTSTR resource;
2096 DRAWITEMSTRUCT* lpdis = (DRAWITEMSTRUCT*)lParam;
2097 if ((lpdis==NULL)||(lpdis->CtlType != ODT_MENU))
2098 return S_OK; //not for a menu
2099 resource = GetMenuTextFromResource(myIDMap[lpdis->itemID]);
2100 if (resource == NULL)
2101 return S_OK;
2102 HICON hIcon = (HICON)LoadImage(g_hResInst, resource, IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
2103 if (hIcon == NULL)
2104 return S_OK;
2105 DrawIconEx(lpdis->hDC,
2106 lpdis->rcItem.left,
2107 lpdis->rcItem.top + (lpdis->rcItem.bottom - lpdis->rcItem.top - 16) / 2,
2108 hIcon, 16, 16,
2109 0, NULL, DI_NORMAL);
2110 DestroyIcon(hIcon);
2111 *pResult = TRUE;
2113 break;
2114 case WM_MENUCHAR:
2116 LPCTSTR resource;
2117 TCHAR *szItem;
2118 if (HIWORD(wParam) != MF_POPUP)
2119 return NOERROR;
2120 int nChar = LOWORD(wParam);
2121 if (_istascii((wint_t)nChar) && _istupper((wint_t)nChar))
2122 nChar = tolower(nChar);
2123 // we have the char the user pressed, now search that char in all our
2124 // menu items
2125 std::vector<int> accmenus;
2126 for (std::map<UINT_PTR, UINT_PTR>::iterator It = mySubMenuMap.begin(); It != mySubMenuMap.end(); ++It)
2128 resource = GetMenuTextFromResource(mySubMenuMap[It->first]);
2129 if (resource == NULL)
2130 continue;
2131 szItem = stringtablebuffer;
2132 TCHAR * amp = _tcschr(szItem, '&');
2133 if (amp == NULL)
2134 continue;
2135 amp++;
2136 int ampChar = LOWORD(*amp);
2137 if (_istascii((wint_t)ampChar) && _istupper((wint_t)ampChar))
2138 ampChar = tolower(ampChar);
2139 if (ampChar == nChar)
2141 // yep, we found a menu which has the pressed key
2142 // as an accelerator. Add that menu to the list to
2143 // process later.
2144 accmenus.push_back(It->first);
2147 if (accmenus.size() == 0)
2149 // no menu with that accelerator key.
2150 *pResult = MAKELONG(0, MNC_IGNORE);
2151 return NOERROR;
2153 if (accmenus.size() == 1)
2155 // Only one menu with that accelerator key. We're lucky!
2156 // So just execute that menu entry.
2157 *pResult = MAKELONG(accmenus[0], MNC_EXECUTE);
2158 return NOERROR;
2160 if (accmenus.size() > 1)
2162 // we have more than one menu item with this accelerator key!
2163 MENUITEMINFO mif;
2164 mif.cbSize = sizeof(MENUITEMINFO);
2165 mif.fMask = MIIM_STATE;
2166 for (std::vector<int>::iterator it = accmenus.begin(); it != accmenus.end(); ++it)
2168 GetMenuItemInfo((HMENU)lParam, *it, TRUE, &mif);
2169 if (mif.fState == MFS_HILITE)
2171 // this is the selected item, so select the next one
2172 ++it;
2173 if (it == accmenus.end())
2174 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
2175 else
2176 *pResult = MAKELONG(*it, MNC_SELECT);
2177 return NOERROR;
2180 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
2183 break;
2184 default:
2185 return NOERROR;
2188 return NOERROR;
2191 LPCTSTR CShellExt::GetMenuTextFromResource(int id)
2193 TCHAR textbuf[255];
2194 LPCTSTR resource = NULL;
2195 unsigned __int64 layout = g_ShellCache.GetMenuLayout();
2196 space = 6;
2198 int menuIndex = 0;
2199 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
2201 if (menuInfo[menuIndex].command == id)
2203 MAKESTRING(menuInfo[menuIndex].menuTextID);
2204 resource = MAKEINTRESOURCE(menuInfo[menuIndex].iconID);
2205 switch (id)
2207 case ShellSubMenuMultiple:
2208 case ShellSubMenuLink:
2209 case ShellSubMenuFolder:
2210 case ShellSubMenuFile:
2211 case ShellSubMenu:
2212 space = 0;
2213 break;
2214 default:
2215 space = layout & menuInfo[menuIndex].menuID ? 0 : 6;
2216 if (layout & (menuInfo[menuIndex].menuID))
2218 _tcscpy_s(textbuf, 255, _T("Git "));
2219 _tcscat_s(textbuf, 255, stringtablebuffer);
2220 _tcscpy_s(stringtablebuffer, 255, textbuf);
2222 break;
2224 return resource;
2226 menuIndex++;
2228 return NULL;
2231 bool CShellExt::IsIllegalFolder(std::wstring folder, int * cslidarray)
2233 int i=0;
2234 TCHAR buf[MAX_PATH]; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
2235 LPITEMIDLIST pidl = NULL;
2236 while (cslidarray[i])
2238 ++i;
2239 pidl = NULL;
2240 if (SHGetFolderLocation(NULL, cslidarray[i-1], NULL, 0, &pidl)!=S_OK)
2241 continue;
2242 if (!SHGetPathFromIDList(pidl, buf))
2244 // not a file system path, definitely illegal for our use
2245 CoTaskMemFree(pidl);
2246 continue;
2248 CoTaskMemFree(pidl);
2249 if (_tcslen(buf)==0)
2250 continue;
2251 if (_tcscmp(buf, folder.c_str())==0)
2252 return true;
2254 return false;
2257 bool CShellExt::InsertIgnoreSubmenus(UINT &idCmd, UINT idCmdFirst, HMENU hMenu, HMENU subMenu, UINT &indexMenu, int &indexSubMenu, unsigned __int64 topmenu, bool bShowIcons, UINT uFlags)
2259 HMENU ignoresubmenu = NULL;
2260 int indexignoresub = 0;
2261 bool bShowIgnoreMenu = false;
2262 TCHAR maskbuf[MAX_PATH]; // MAX_PATH is ok, since this only holds a filename
2263 TCHAR ignorepath[MAX_PATH]; // MAX_PATH is ok, since this only holds a filename
2264 if (files_.size() == 0)
2265 return false;
2266 UINT icon = bShowIcons ? IDI_IGNORE : 0;
2268 std::vector<stdstring>::iterator I = files_.begin();
2269 if (_tcsrchr(I->c_str(), '\\'))
2270 _tcscpy_s(ignorepath, MAX_PATH, _tcsrchr(I->c_str(), '\\')+1);
2271 else
2272 _tcscpy_s(ignorepath, MAX_PATH, I->c_str());
2273 if ((itemStates & ITEMIS_IGNORED)&&(ignoredprops.size() > 0))
2275 // check if the item name is ignored or the mask
2276 size_t p = 0;
2277 while ( (p=ignoredprops.find( ignorepath,p )) != -1 )
2279 if ( (p==0 || ignoredprops[p-1]==TCHAR('\n'))
2280 && (p+_tcslen(ignorepath)==ignoredprops.length() || ignoredprops[p+_tcslen(ignorepath)+1]==TCHAR('\n')) )
2282 break;
2284 p++;
2286 if (p!=-1)
2288 ignoresubmenu = CreateMenu();
2289 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2290 stdstring verb = stdstring(ignorepath);
2291 myVerbsMap[verb] = idCmd - idCmdFirst;
2292 myVerbsMap[verb] = idCmd;
2293 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2294 myVerbsIDMap[idCmd] = verb;
2295 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnore;
2296 myIDMap[idCmd++] = ShellMenuUnIgnore;
2297 bShowIgnoreMenu = true;
2299 _tcscpy_s(maskbuf, MAX_PATH, _T("*"));
2300 if (_tcsrchr(ignorepath, '.'))
2302 _tcscat_s(maskbuf, MAX_PATH, _tcsrchr(ignorepath, '.'));
2303 p = ignoredprops.find(maskbuf);
2304 if ((p!=-1) &&
2305 ((ignoredprops.compare(maskbuf)==0) || (ignoredprops.find('\n', p)==p+_tcslen(maskbuf)+1) || (ignoredprops.rfind('\n', p)==p-1)))
2307 if (ignoresubmenu==NULL)
2308 ignoresubmenu = CreateMenu();
2310 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2311 stdstring verb = stdstring(maskbuf);
2312 myVerbsMap[verb] = idCmd - idCmdFirst;
2313 myVerbsMap[verb] = idCmd;
2314 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2315 myVerbsIDMap[idCmd] = verb;
2316 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreCaseSensitive;
2317 myIDMap[idCmd++] = ShellMenuUnIgnoreCaseSensitive;
2318 bShowIgnoreMenu = true;
2322 else if ((itemStates & ITEMIS_IGNORED) == 0)
2324 bShowIgnoreMenu = true;
2325 ignoresubmenu = CreateMenu();
2326 if (itemStates & ITEMIS_ONLYONE)
2328 if (itemStates & ITEMIS_INSVN)
2330 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2331 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
2332 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
2334 _tcscpy_s(maskbuf, MAX_PATH, _T("*"));
2335 if (_tcsrchr(ignorepath, '.'))
2337 _tcscat_s(maskbuf, MAX_PATH, _tcsrchr(ignorepath, '.'));
2338 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2339 stdstring verb = stdstring(maskbuf);
2340 myVerbsMap[verb] = idCmd - idCmdFirst;
2341 myVerbsMap[verb] = idCmd;
2342 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2343 myVerbsIDMap[idCmd] = verb;
2344 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
2345 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
2348 else
2350 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2351 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
2352 myIDMap[idCmd++] = ShellMenuIgnore;
2354 _tcscpy_s(maskbuf, MAX_PATH, _T("*"));
2355 if (_tcsrchr(ignorepath, '.'))
2357 _tcscat_s(maskbuf, MAX_PATH, _tcsrchr(ignorepath, '.'));
2358 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2359 stdstring verb = stdstring(maskbuf);
2360 myVerbsMap[verb] = idCmd - idCmdFirst;
2361 myVerbsMap[verb] = idCmd;
2362 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2363 myVerbsIDMap[idCmd] = verb;
2364 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2365 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2369 else
2371 if (itemStates & ITEMIS_INSVN)
2373 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE);
2374 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2375 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2376 stdstring verb = stdstring(ignorepath);
2377 myVerbsMap[verb] = idCmd - idCmdFirst;
2378 myVerbsMap[verb] = idCmd;
2379 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2380 myVerbsIDMap[idCmd] = verb;
2381 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
2382 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
2384 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK);
2385 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2386 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2387 verb = stdstring(ignorepath);
2388 myVerbsMap[verb] = idCmd - idCmdFirst;
2389 myVerbsMap[verb] = idCmd;
2390 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2391 myVerbsIDMap[idCmd] = verb;
2392 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
2393 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
2395 else
2397 MAKESTRING(IDS_MENUIGNOREMULTIPLE);
2398 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2399 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2400 stdstring verb = stdstring(ignorepath);
2401 myVerbsMap[verb] = idCmd - idCmdFirst;
2402 myVerbsMap[verb] = idCmd;
2403 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2404 myVerbsIDMap[idCmd] = verb;
2405 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
2406 myIDMap[idCmd++] = ShellMenuIgnore;
2408 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK);
2409 _stprintf_s(ignorepath, MAX_PATH, stringtablebuffer, files_.size());
2410 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2411 verb = stdstring(ignorepath);
2412 myVerbsMap[verb] = idCmd - idCmdFirst;
2413 myVerbsMap[verb] = idCmd;
2414 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2415 myVerbsIDMap[idCmd] = verb;
2416 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2417 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2422 if (bShowIgnoreMenu)
2424 MENUITEMINFO menuiteminfo;
2425 SecureZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
2426 menuiteminfo.cbSize = sizeof(menuiteminfo);
2427 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
2428 if (icon)
2430 menuiteminfo.fMask |= MIIM_BITMAP;
2431 menuiteminfo.hbmpItem = (SysInfo::Instance().IsVistaOrLater()) ? IconToBitmapPARGB32(icon) : HBMMENU_CALLBACK;
2433 menuiteminfo.fType = MFT_STRING;
2434 menuiteminfo.hSubMenu = ignoresubmenu;
2435 menuiteminfo.wID = idCmd;
2436 SecureZeroMemory(stringtablebuffer, sizeof(stringtablebuffer));
2437 if (itemStates & ITEMIS_IGNORED)
2438 GetMenuTextFromResource(ShellMenuUnIgnoreSub);
2439 else if (itemStates & ITEMIS_INSVN)
2440 GetMenuTextFromResource(ShellMenuDeleteIgnoreSub);
2441 else
2442 GetMenuTextFromResource(ShellMenuIgnoreSub);
2443 menuiteminfo.dwTypeData = stringtablebuffer;
2444 menuiteminfo.cch = (UINT)min(_tcslen(menuiteminfo.dwTypeData), UINT_MAX);
2446 InsertMenuItem((topmenu & MENUIGNORE) ? hMenu : subMenu, (topmenu & MENUIGNORE) ? indexMenu++ : indexSubMenu++, TRUE, &menuiteminfo);
2447 if (itemStates & ITEMIS_IGNORED)
2449 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreSub;
2450 myIDMap[idCmd++] = ShellMenuUnIgnoreSub;
2452 else
2454 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreSub;
2455 myIDMap[idCmd++] = ShellMenuIgnoreSub;
2458 return bShowIgnoreMenu;
2461 HBITMAP CShellExt::IconToBitmapPARGB32(UINT uIcon)
2463 std::map<UINT, HBITMAP>::iterator bitmap_it = bitmaps.lower_bound(uIcon);
2464 if (bitmap_it != bitmaps.end() && bitmap_it->first == uIcon)
2465 return bitmap_it->second;
2467 HICON hIcon = (HICON)LoadImage(g_hResInst, MAKEINTRESOURCE(uIcon), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
2468 if (!hIcon)
2469 return NULL;
2471 if (pfnBeginBufferedPaint == NULL || pfnEndBufferedPaint == NULL || pfnGetBufferedPaintBits == NULL)
2472 return NULL;
2474 SIZE sizIcon;
2475 sizIcon.cx = GetSystemMetrics(SM_CXSMICON);
2476 sizIcon.cy = GetSystemMetrics(SM_CYSMICON);
2478 RECT rcIcon;
2479 SetRect(&rcIcon, 0, 0, sizIcon.cx, sizIcon.cy);
2480 HBITMAP hBmp = NULL;
2482 HDC hdcDest = CreateCompatibleDC(NULL);
2483 if (hdcDest)
2485 if (SUCCEEDED(Create32BitHBITMAP(hdcDest, &sizIcon, NULL, &hBmp)))
2487 HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcDest, hBmp);
2488 if (hbmpOld)
2490 BLENDFUNCTION bfAlpha = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
2491 BP_PAINTPARAMS paintParams = {0};
2492 paintParams.cbSize = sizeof(paintParams);
2493 paintParams.dwFlags = BPPF_ERASE;
2494 paintParams.pBlendFunction = &bfAlpha;
2496 HDC hdcBuffer;
2497 HPAINTBUFFER hPaintBuffer = pfnBeginBufferedPaint(hdcDest, &rcIcon, BPBF_DIB, &paintParams, &hdcBuffer);
2498 if (hPaintBuffer)
2500 if (DrawIconEx(hdcBuffer, 0, 0, hIcon, sizIcon.cx, sizIcon.cy, 0, NULL, DI_NORMAL))
2502 // If icon did not have an alpha channel we need to convert buffer to PARGB
2503 ConvertBufferToPARGB32(hPaintBuffer, hdcDest, hIcon, sizIcon);
2506 // This will write the buffer contents to the destination bitmap
2507 pfnEndBufferedPaint(hPaintBuffer, TRUE);
2510 SelectObject(hdcDest, hbmpOld);
2514 DeleteDC(hdcDest);
2517 DestroyIcon(hIcon);
2519 if(hBmp)
2520 bitmaps.insert(bitmap_it, std::make_pair(uIcon, hBmp));
2521 return hBmp;
2524 HRESULT CShellExt::Create32BitHBITMAP(HDC hdc, const SIZE *psize, __deref_opt_out void **ppvBits, __out HBITMAP* phBmp)
2526 *phBmp = NULL;
2528 BITMAPINFO bmi;
2529 ZeroMemory(&bmi, sizeof(bmi));
2530 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
2531 bmi.bmiHeader.biPlanes = 1;
2532 bmi.bmiHeader.biCompression = BI_RGB;
2534 bmi.bmiHeader.biWidth = psize->cx;
2535 bmi.bmiHeader.biHeight = psize->cy;
2536 bmi.bmiHeader.biBitCount = 32;
2538 HDC hdcUsed = hdc ? hdc : GetDC(NULL);
2539 if (hdcUsed)
2541 *phBmp = CreateDIBSection(hdcUsed, &bmi, DIB_RGB_COLORS, ppvBits, NULL, 0);
2542 if (hdc != hdcUsed)
2544 ReleaseDC(NULL, hdcUsed);
2547 return (NULL == *phBmp) ? E_OUTOFMEMORY : S_OK;
2550 HRESULT CShellExt::ConvertBufferToPARGB32(HPAINTBUFFER hPaintBuffer, HDC hdc, HICON hicon, SIZE& sizIcon)
2552 RGBQUAD *prgbQuad;
2553 int cxRow;
2554 HRESULT hr = pfnGetBufferedPaintBits(hPaintBuffer, &prgbQuad, &cxRow);
2555 if (SUCCEEDED(hr))
2557 ARGB *pargb = reinterpret_cast<ARGB *>(prgbQuad);
2558 if (!HasAlpha(pargb, sizIcon, cxRow))
2560 ICONINFO info;
2561 if (GetIconInfo(hicon, &info))
2563 if (info.hbmMask)
2565 hr = ConvertToPARGB32(hdc, pargb, info.hbmMask, sizIcon, cxRow);
2568 DeleteObject(info.hbmColor);
2569 DeleteObject(info.hbmMask);
2574 return hr;
2577 bool CShellExt::HasAlpha(__in ARGB *pargb, SIZE& sizImage, int cxRow)
2579 ULONG cxDelta = cxRow - sizImage.cx;
2580 for (ULONG y = sizImage.cy; y; --y)
2582 for (ULONG x = sizImage.cx; x; --x)
2584 if (*pargb++ & 0xFF000000)
2586 return true;
2590 pargb += cxDelta;
2593 return false;
2596 HRESULT CShellExt::ConvertToPARGB32(HDC hdc, __inout ARGB *pargb, HBITMAP hbmp, SIZE& sizImage, int cxRow)
2598 BITMAPINFO bmi;
2599 ZeroMemory(&bmi, sizeof(bmi));
2600 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
2601 bmi.bmiHeader.biPlanes = 1;
2602 bmi.bmiHeader.biCompression = BI_RGB;
2604 bmi.bmiHeader.biWidth = sizImage.cx;
2605 bmi.bmiHeader.biHeight = sizImage.cy;
2606 bmi.bmiHeader.biBitCount = 32;
2608 HRESULT hr = E_OUTOFMEMORY;
2609 HANDLE hHeap = GetProcessHeap();
2610 void *pvBits = HeapAlloc(hHeap, 0, bmi.bmiHeader.biWidth * 4 * bmi.bmiHeader.biHeight);
2611 if (pvBits)
2613 hr = E_UNEXPECTED;
2614 if (GetDIBits(hdc, hbmp, 0, bmi.bmiHeader.biHeight, pvBits, &bmi, DIB_RGB_COLORS) == bmi.bmiHeader.biHeight)
2616 ULONG cxDelta = cxRow - bmi.bmiHeader.biWidth;
2617 ARGB *pargbMask = static_cast<ARGB *>(pvBits);
2619 for (ULONG y = bmi.bmiHeader.biHeight; y; --y)
2621 for (ULONG x = bmi.bmiHeader.biWidth; x; --x)
2623 if (*pargbMask++)
2625 // transparent pixel
2626 *pargb++ = 0;
2628 else
2630 // opaque pixel
2631 *pargb++ |= 0xFF000000;
2635 pargb += cxDelta;
2638 hr = S_OK;
2641 HeapFree(hHeap, 0, pvBits);
2644 return hr;