1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "ItemIDList.h"
22 #include "PreserveChdir.h"
23 #include "UnicodeUtils.h"
24 //#include "GitProperties.h"
25 #include "GitStatus.h"
28 #define GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
29 #define GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
31 int g_shellidlist
=RegisterClipboardFormat(CFSTR_SHELLIDLIST
);
33 CShellExt::MenuInfo
CShellExt::menuInfo
[] =
35 { ShellMenuClone
, MENUCLONE
, IDI_CLONE
, IDS_MENUCLONE
, IDS_MENUDESCCHECKOUT
,
36 ITEMIS_FOLDER
, ITEMIS_INSVN
|ITEMIS_FOLDERINSVN
, 0, 0, 0, 0, 0, 0 },
38 { ShellMenuPull
, MENUPULL
, IDI_PULL
, IDS_MENUPULL
, IDS_MENUPULL
,
39 ITEMIS_INSVN
, 0, ITEMIS_FOLDERINSVN
, 0, 0, 0, 0, 0 },
41 { ShellMenuFetch
, MENUFETCH
, IDI_PULL
, IDS_MENUFETCH
, IDS_MENUFETCH
,
42 ITEMIS_INSVN
, 0, ITEMIS_FOLDERINSVN
, 0, 0, 0, 0, 0 },
44 { ShellMenuPush
, MENUPUSH
, IDI_PUSH
, IDS_MENUPUSH
, IDS_MENUPUSH
,
45 ITEMIS_INSVN
, 0, ITEMIS_FOLDERINSVN
, 0, 0, 0, 0, 0 },
47 // { ShellMenuCheckout, MENUCHECKOUT, IDI_CHECKOUT, IDS_MENUCHECKOUT, IDS_MENUDESCCHECKOUT,
48 // ITEMIS_FOLDER, ITEMIS_INSVN|ITEMIS_FOLDERINSVN, 0, 0, 0, 0, 0, 0 },
50 // { ShellMenuUpdate, MENUSUBUPDATE, IDI_UPDATE, IDS_MENUUPDATE, IDS_MENUDESCUPDATE,
51 // ITEMIS_INSVN, 0, ITEMIS_FOLDERINSVN, 0, 0, 0, 0, 0 },
53 { ShellSeparator
, ITEMIS_GITSVN
, 0, 0, 0, 0, 0, 0, 0},
55 { ShellMenuCommit
, MENUCOMMIT
, IDI_COMMIT
, IDS_MENUCOMMIT
, IDS_MENUDESCCOMMIT
,
56 ITEMIS_INSVN
, 0, ITEMIS_FOLDERINSVN
, 0, 0, 0, 0, 0 },
58 { ShellMenuGitSVNDCommit
, MENUSVNDCOMMIT
, IDI_COMMIT
, IDS_MENUSVNDCOMMIT
, IDS_MENUSVNDCOMMIT_DESC
,
59 ITEMIS_INSVN
|ITEMIS_GITSVN
, 0, ITEMIS_FOLDERINSVN
|ITEMIS_GITSVN
, 0, 0, 0, 0, 0 },
61 { ShellMenuGitSVNRebase
, MENUSVNREBASE
, IDI_REBASE
, IDS_MENUSVNREBASE
, IDS_MENUSVNREBASE_DESC
,
62 ITEMIS_INSVN
|ITEMIS_ONLYONE
|ITEMIS_GITSVN
, 0, ITEMIS_FOLDER
|ITEMIS_FOLDERINSVN
|ITEMIS_ONLYONE
|ITEMIS_GITSVN
, 0, 0, 0, 0, 0},
64 { ShellSeparator
, 0, 0, 0, 0, 0, 0, 0, 0},
66 { ShellMenuDiff
, MENUDIFF
, IDI_DIFF
, IDS_MENUDIFF
, IDS_MENUDESCDIFF
,
67 ITEMIS_INSVN
|ITEMIS_ONLYONE
, ITEMIS_FOLDER
|ITEMIS_NORMAL
, ITEMIS_TWO
, 0, 0, 0, 0, 0 },
69 { ShellMenuPrevDiff
, MENUPREVDIFF
, IDI_DIFF
, IDS_MENUPREVDIFF
, IDS_MENUDESCPREVDIFF
,
70 ITEMIS_INSVN
|ITEMIS_ONLYONE
, ITEMIS_FOLDER
, 0, 0, 0, 0, 0, 0 },
72 // { ShellMenuUrlDiff, MENUURLDIFF, IDI_DIFF, IDS_MENUURLDIFF, IDS_MENUDESCURLDIFF,
73 // ITEMIS_INSVN|ITEMIS_ONLYONE|ITEMIS_EXTENDED, 0, ITEMIS_FOLDERINSVN|ITEMIS_EXTENDED|ITEMIS_ONLYONE, 0, 0, 0, 0, 0 },
75 { ShellMenuLog
, MENULOG
, IDI_LOG
, IDS_MENULOG
, IDS_MENUDESCLOG
,
76 ITEMIS_INSVN
|ITEMIS_ONLYONE
, ITEMIS_ADDED
, ITEMIS_FOLDER
|ITEMIS_FOLDERINSVN
|ITEMIS_ONLYONE
, ITEMIS_ADDED
, ITEMIS_FOLDERINSVN
|ITEMIS_ONLYONE
, ITEMIS_ADDED
, 0, 0 },
78 { ShellMenuRefLog
, MENUREFLOG
, IDI_LOG
, IDS_MENUREFLOG
, IDS_MENUDESCREFLOG
,
79 ITEMIS_INSVN
|ITEMIS_ONLYONE
|ITEMIS_EXTENDED
, ITEMIS_ADDED
, ITEMIS_FOLDER
|ITEMIS_FOLDERINSVN
|ITEMIS_ONLYONE
|ITEMIS_EXTENDED
, ITEMIS_ADDED
, ITEMIS_FOLDERINSVN
|ITEMIS_ONLYONE
|ITEMIS_EXTENDED
, ITEMIS_ADDED
, 0, 0 },
81 { ShellMenuRefBrowse
, MENUREFBROWSE
, IDI_REPOBROWSE
, IDS_MENUREFBROWSE
, IDS_MENUDESCREFBROWSE
,
82 ITEMIS_INSVN
|ITEMIS_ONLYONE
|ITEMIS_EXTENDED
, ITEMIS_ADDED
, ITEMIS_FOLDER
|ITEMIS_FOLDERINSVN
|ITEMIS_ONLYONE
|ITEMIS_EXTENDED
, ITEMIS_ADDED
, ITEMIS_FOLDERINSVN
|ITEMIS_ONLYONE
|ITEMIS_EXTENDED
, ITEMIS_ADDED
, 0, 0 },
85 // { ShellMenuRepoBrowse, MENUREPOBROWSE, IDI_REPOBROWSE, IDS_MENUREPOBROWSE, IDS_MENUDESCREPOBROWSE,
86 // ITEMIS_ONLYONE, 0, ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE, 0, 0, 0, 0, 0 },
88 { ShellMenuShowChanged
, MENUSHOWCHANGED
, IDI_SHOWCHANGED
, IDS_MENUSHOWCHANGED
, IDS_MENUDESCSHOWCHANGED
,
89 ITEMIS_INSVN
|ITEMIS_ONLYONE
, 0, ITEMIS_FOLDER
|ITEMIS_FOLDERINSVN
|ITEMIS_ONLYONE
, 0, 0, 0, 0, 0},
91 { ShellMenuRebase
, MENUREBASE
, IDI_REBASE
, IDS_MENUREBASE
, IDS_MENUREBASE
,
92 ITEMIS_INSVN
|ITEMIS_ONLYONE
, 0, ITEMIS_FOLDER
|ITEMIS_FOLDERINSVN
|ITEMIS_ONLYONE
, 0, 0, 0, 0, 0},
94 // { ShellMenuRevisionGraph, MENUREVISIONGRAPH, IDI_REVISIONGRAPH, IDS_MENUREVISIONGRAPH, IDS_MENUDESCREVISIONGRAPH,
95 // ITEMIS_INSVN|ITEMIS_ONLYONE, ITEMIS_ADDED, ITEMIS_FOLDER|ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE, ITEMIS_ADDED, 0, 0, 0, 0},
97 { ShellMenuStashSave
, MENUSTASHSAVE
, IDI_COMMIT
, IDS_MENUSTASHSAVE
, IDS_MENUSTASHSAVE
,
98 ITEMIS_INSVN
, 0, ITEMIS_FOLDERINSVN
, 0, 0, 0, 0, 0 },
99 { ShellMenuStashApply
, MENUSTASHAPPLY
, IDI_RELOCATE
, IDS_MENUSTASHAPPLY
, IDS_MENUSTASHAPPLY
,
100 ITEMIS_INSVN
, 0, ITEMIS_FOLDERINSVN
, 0, 0, 0, 0, 0 },
101 { ShellMenuStashList
, MENUSTASHAPPLY
, IDI_LOG
, IDS_MENUSTASHLIST
, IDS_MENUSTASHLIST
,
102 ITEMIS_INSVN
|ITEMIS_EXTENDED
, 0, ITEMIS_FOLDERINSVN
|ITEMIS_EXTENDED
, 0, 0, 0, 0, 0 },
105 { ShellSeparator
, 0, 0, 0, 0, 0, 0, 0, 0},
107 { ShellMenuConflictEditor
, MENUCONFLICTEDITOR
, IDI_CONFLICT
, IDS_MENUCONFLICT
, IDS_MENUDESCCONFLICT
,
108 ITEMIS_INSVN
|ITEMIS_CONFLICTED
, ITEMIS_FOLDER
, 0, 0, 0, 0, 0, 0 },
110 { ShellMenuResolve
, MENURESOLVE
, IDI_RESOLVE
, IDS_MENURESOLVE
, IDS_MENUDESCRESOLVE
,
111 ITEMIS_INSVN
|ITEMIS_CONFLICTED
, 0, ITEMIS_INSVN
|ITEMIS_FOLDER
, 0, ITEMIS_FOLDERINSVN
, 0, 0, 0 },
114 { ShellMenuRename
, MENURENAME
, IDI_RENAME
, IDS_MENURENAME
, IDS_MENUDESCRENAME
,
115 ITEMIS_INSVN
|ITEMIS_ONLYONE
|ITEMIS_INVERSIONEDFOLDER
, 0, 0, 0, 0, 0, 0, 0 },
117 { ShellMenuRemove
, MENUREMOVE
, IDI_DELETE
, IDS_MENUREMOVE
, IDS_MENUDESCREMOVE
,
118 ITEMIS_INSVN
|ITEMIS_INVERSIONEDFOLDER
, ITEMIS_ADDED
, 0, 0, 0, 0, 0, 0 },
120 { ShellMenuRemoveKeep
, MENUREMOVE
, IDI_DELETE
, IDS_MENUREMOVEKEEP
, IDS_MENUDESCREMOVEKEEP
,
121 ITEMIS_INSVN
|ITEMIS_INVERSIONEDFOLDER
|ITEMIS_EXTENDED
, ITEMIS_ADDED
, 0, 0, 0, 0, 0, 0 },
123 { ShellMenuRevert
, MENUREVERT
, IDI_REVERT
, IDS_MENUREVERT
, IDS_MENUDESCREVERT
,
124 ITEMIS_INSVN
, ITEMIS_NORMAL
|ITEMIS_ADDED
, ITEMIS_FOLDERINSVN
, ITEMIS_ADDED
, 0, 0, 0, 0 },
126 { ShellMenuRevert
, MENUREVERT
, IDI_REVERT
, IDS_MENUUNDOADD
, IDS_MENUDESCUNDOADD
,
127 ITEMIS_ADDED
, ITEMIS_NORMAL
, ITEMIS_FOLDERINSVN
|ITEMIS_ADDED
, 0, 0, 0, 0, 0 },
129 { ShellMenuDelUnversioned
, MENUDELUNVERSIONED
, IDI_DELUNVERSIONED
, IDS_MENUDELUNVERSIONED
, IDS_MENUDESCDELUNVERSIONED
,
130 ITEMIS_FOLDER
|ITEMIS_INSVN
|ITEMIS_EXTENDED
, 0, ITEMIS_FOLDER
|ITEMIS_FOLDERINSVN
|ITEMIS_EXTENDED
, 0, 0, 0, 0, 0 },
132 { ShellMenuCleanup
, MENUCLEANUP
, IDI_CLEANUP
, IDS_MENUCLEANUP
, IDS_MENUDESCCLEANUP
,
133 ITEMIS_INSVN
|ITEMIS_FOLDER
, 0, ITEMIS_FOLDERINSVN
|ITEMIS_FOLDER
, 0, 0, 0, 0, 0 },
135 // { ShellMenuLock, MENULOCK, IDI_LOCK, IDS_MENU_LOCK, IDS_MENUDESC_LOCK,
136 // ITEMIS_INSVN, ITEMIS_LOCKED|ITEMIS_ADDED, ITEMIS_FOLDERINSVN, ITEMIS_LOCKED|ITEMIS_ADDED, 0, 0, 0, 0 },
138 // { ShellMenuUnlock, MENUUNLOCK, IDI_UNLOCK, IDS_MENU_UNLOCK, IDS_MENUDESC_UNLOCK,
139 // ITEMIS_INSVN|ITEMIS_LOCKED, 0, ITEMIS_FOLDER|ITEMIS_INSVN, 0, ITEMIS_FOLDERINSVN, 0, 0, 0 },
141 // { ShellMenuUnlockForce, MENUUNLOCK, IDI_UNLOCK, IDS_MENU_UNLOCKFORCE, IDS_MENUDESC_UNLOCKFORCE,
142 // ITEMIS_INSVN|ITEMIS_LOCKED, 0, ITEMIS_FOLDER|ITEMIS_INSVN|ITEMIS_EXTENDED, 0, 0, 0, 0, 0 },
144 { ShellSeparator
, 0, 0, 0, 0, 0, 0, 0, 0},
146 // { ShellMenuCopy, MENUCOPY, IDI_COPY, IDS_MENUBRANCH, IDS_MENUDESCCOPY,
147 // ITEMIS_INSVN|ITEMIS_ONLYONE, ITEMIS_ADDED, ITEMIS_FOLDER|ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE, 0, 0, 0, 0, 0 },
149 { ShellMenuSwitch
, MENUSWITCH
, IDI_SWITCH
, IDS_MENUSWITCH
, IDS_MENUDESCSWITCH
,
150 ITEMIS_INSVN
|ITEMIS_ONLYONE
, ITEMIS_ADDED
, ITEMIS_FOLDER
|ITEMIS_FOLDERINSVN
|ITEMIS_ONLYONE
, 0, 0, 0, 0, 0 },
152 { ShellMenuMerge
, MENUMERGE
, IDI_MERGE
, IDS_MENUMERGE
, IDS_MENUDESCMERGE
,
153 ITEMIS_INSVN
|ITEMIS_ONLYONE
, ITEMIS_ADDED
, ITEMIS_FOLDER
|ITEMIS_FOLDERINSVN
|ITEMIS_ONLYONE
, 0, 0, 0, 0, 0 },
154 // { ShellMenuMergeAll, MENUMERGEALL, IDI_MERGE, IDS_MENUMERGEALL, IDS_MENUDESCMERGEALL,
155 // ITEMIS_INSVN|ITEMIS_ONLYONE|ITEMIS_EXTENDED, ITEMIS_ADDED, ITEMIS_FOLDER|ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE|ITEMIS_EXTENDED, 0, 0, 0, 0, 0 },
157 { ShellMenuBranch
, MENUCOPY
, IDI_COPY
, IDS_MENUBRANCH
, IDS_MENUDESCCOPY
,
158 ITEMIS_INSVN
, 0, ITEMIS_FOLDERINSVN
, 0, 0, 0, 0, 0 },
159 { ShellMenuTag
, MENUTAG
, IDI_TAG
, IDS_MENUTAG
, IDS_MENUDESCCOPY
,
160 ITEMIS_INSVN
, 0, ITEMIS_FOLDERINSVN
, 0, 0, 0, 0, 0 },
162 { ShellMenuExport
, MENUEXPORT
, IDI_EXPORT
, IDS_MENUEXPORT
, IDS_MENUDESCEXPORT
,
163 ITEMIS_INSVN
, 0, ITEMIS_FOLDERINSVN
, 0, 0, 0, 0, 0 },
165 // { ShellMenuRelocate, MENURELOCATE, IDI_RELOCATE, IDS_MENURELOCATE, IDS_MENUDESCRELOCATE,
166 // ITEMIS_INSVN|ITEMIS_FOLDER|ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE, 0, ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE, 0, 0, 0, 0, 0 },
168 { ShellSeparator
, 0, 0, 0, 0, 0, 0, 0, 0},
170 { ShellMenuCreateRepos
, MENUCREATEREPOS
, IDI_CREATEREPOS
, IDS_MENUCREATEREPOS
, IDS_MENUDESCCREATEREPOS
,
171 ITEMIS_FOLDER
, ITEMIS_INSVN
|ITEMIS_FOLDERINSVN
, 0, 0, 0, 0, 0, 0 },
173 { ShellMenuAdd
, MENUADD
, IDI_ADD
, IDS_MENUADD
, IDS_MENUDESCADD
,
174 ITEMIS_INVERSIONEDFOLDER
, ITEMIS_INSVN
, ITEMIS_INSVN
|ITEMIS_FOLDER
, 0, ITEMIS_IGNORED
, 0, ITEMIS_DELETED
, ITEMIS_FOLDER
|ITEMIS_ONLYONE
},
176 // { ShellMenuAddAsReplacement, MENUADD, IDI_ADD, IDS_MENUADDASREPLACEMENT, IDS_MENUADDASREPLACEMENT,
177 // ITEMIS_DELETED|ITEMIS_ONLYONE, ITEMIS_FOLDER, 0, 0, 0, 0, 0, 0 },
179 // { ShellMenuImport, MENUIMPORT, IDI_IMPORT, IDS_MENUIMPORT, IDS_MENUDESCIMPORT,
180 // ITEMIS_FOLDER, ITEMIS_INSVN, 0, 0, 0, 0, 0, 0 },
182 { ShellMenuBlame
, MENUBLAME
, IDI_BLAME
, IDS_MENUBLAME
, IDS_MENUDESCBLAME
,
183 ITEMIS_NORMAL
|ITEMIS_ONLYONE
, ITEMIS_FOLDER
|ITEMIS_ADDED
, 0, 0, 0, 0, 0, 0 },
184 // TODO: original code is ITEMIS_INSVN|ITEMIS_ONLYONE, makes sense to only allow blaming of versioned files
185 // why was this changed, is this related to GitStatus?
187 { ShellMenuIgnoreSub
, MENUIGNORE
, IDI_IGNORE
, IDS_MENUIGNORE
, IDS_MENUDESCIGNORE
,
188 ITEMIS_INVERSIONEDFOLDER
, ITEMIS_IGNORED
|ITEMIS_INSVN
, 0, 0, 0, 0, 0, 0 },
190 { ShellMenuUnIgnoreSub
, MENUIGNORE
, IDI_IGNORE
, IDS_MENUUNIGNORE
, IDS_MENUDESCUNIGNORE
,
191 ITEMIS_IGNORED
, 0, 0, 0, 0, 0, 0, 0 },
193 { ShellSeparator
, 0, 0, 0, 0, 0, 0, 0, 0},
195 { ShellMenuSubAdd
, MENUSUBADD
, IDI_ADD
, IDS_MENUSUBADD
, IDS_MENUSUBADD
,
196 ITEMIS_INSVN
, 0, ITEMIS_FOLDERINSVN
, 0, 0, 0, 0, 0 },
198 { ShellMenuUpdateExt
, MENUUPDATEEXT
, IDI_UPDATE
, IDS_MENUUPDATEEXT
, IDS_MENUDESCUPDATEEXT
,
199 ITEMIS_INSVN
|ITEMIS_FOLDER
|ITEMIS_SUBMODULE
, 0, 0, 0, 0, 0, 0, 0 },
201 { ShellMenuSubSync
, MENUSUBSYNC
, IDI_MENUSYNC
, IDS_MENUSUBSYNC
, IDS_MENUSUBSYNC
,
202 ITEMIS_INSVN
|ITEMIS_FOLDER
|ITEMIS_SUBMODULE
|ITEMIS_EXTENDED
, 0, 0, 0, 0, 0, 0, 0 },
206 { ShellSeparator
, 0, 0, 0, 0, 0, 0, 0, 0},
208 // { ShellMenuCherryPick, MENUCHERRYPICK, IDI_CREATEPATCH, IDS_MENUCHERRYPICK, IDS_MENUDESCCREATEPATCH,
209 // ITEMIS_INSVN, ITEMIS_NORMAL, ITEMIS_FOLDERINSVN, 0, 0, 0, 0, 0 },
211 { ShellMenuFormatPatch
, MENUFORMATPATCH
, IDI_CREATEPATCH
, IDS_MENUFORMATPATCH
, IDS_MENUDESCCREATEPATCH
,
212 ITEMIS_INSVN
, ITEMIS_NORMAL
, ITEMIS_FOLDERINSVN
, 0, 0, 0, 0, 0 },
214 { ShellMenuImportPatch
, MENUIMPORTPATCH
, IDI_PATCH
, IDS_MENUIMPORTPATCH
, IDS_MENUDESCCREATEPATCH
,
215 ITEMIS_INSVN
, ITEMIS_NORMAL
, ITEMIS_FOLDERINSVN
, 0, ITEMIS_PATCHFILE
, 0, 0, 0 },
218 { ShellMenuCreatePatch
, MENUCREATEPATCH
, IDI_CREATEPATCH
, IDS_MENUCREATEPATCH
, IDS_MENUDESCCREATEPATCH
,
219 ITEMIS_INSVN
|ITEMIS_EXTENDED
, ITEMIS_NORMAL
, ITEMIS_FOLDERINSVN
|ITEMIS_EXTENDED
, 0, 0, 0, 0, 0 },
221 { ShellMenuApplyPatch
, MENUAPPLYPATCH
, IDI_PATCH
, IDS_MENUAPPLYPATCH
, IDS_MENUDESCAPPLYPATCH
,
222 ITEMIS_INSVN
|ITEMIS_FOLDER
|ITEMIS_FOLDERINSVN
|ITEMIS_EXTENDED
, ITEMIS_ADDED
, ITEMIS_ONLYONE
|ITEMIS_PATCHFILE
, 0, ITEMIS_FOLDERINSVN
|ITEMIS_EXTENDED
, ITEMIS_ADDED
, 0, 0 },
224 { ShellMenuSendMail
, MENUSENDMAIL
, IDI_MENUSENDMAIL
, IDS_MENUSENDMAIL
, IDS_MENUDESSENDMAIL
,
225 ITEMIS_PATCHFILE
, 0, 0, 0, 0, 0, 0, 0 },
227 // { ShellMenuProperties, MENUPROPERTIES, IDI_PROPERTIES, IDS_MENUPROPERTIES, IDS_MENUDESCPROPERTIES,
228 // ITEMIS_INSVN, 0, ITEMIS_FOLDERINSVN, 0, 0, 0, 0, 0 },
230 { ShellSeparator
, 0, 0, 0, 0, 0, 0, 0, 0},
231 // { ShellMenuClipPaste, MENUCLIPPASTE, IDI_CLIPPASTE, IDS_MENUCLIPPASTE, IDS_MENUDESCCLIPPASTE,
232 // ITEMIS_INSVN|ITEMIS_FOLDER|ITEMIS_PATHINCLIPBOARD, 0, 0, 0, 0, 0, 0, 0 },
234 { ShellSeparator
, 0, 0, 0, 0, 0, 0, 0, 0},
236 { ShellMenuSettings
, MENUSETTINGS
, IDI_SETTINGS
, IDS_MENUSETTINGS
, IDS_MENUDESCSETTINGS
,
237 ITEMIS_FOLDER
, 0, 0, ITEMIS_FOLDER
, 0, 0, 0, 0 },
238 { ShellMenuHelp
, MENUHELP
, IDI_HELP
, IDS_MENUHELP
, IDS_MENUDESCHELP
,
239 ITEMIS_FOLDER
, 0, 0, ITEMIS_FOLDER
, 0, 0, 0, 0 },
240 { ShellMenuAbout
, MENUABOUT
, IDI_ABOUT
, IDS_MENUABOUT
, IDS_MENUDESCABOUT
,
241 ITEMIS_FOLDER
, 0, 0, ITEMIS_FOLDER
, 0, 0, 0, 0 },
243 // the sub menus - they're not added like the the commands, therefore the menu ID is zero
244 // but they still need to be in here, because we use the icon and string information anyway.
245 { ShellSubMenu
, NULL
, IDI_APP
, IDS_MENUSUBMENU
, 0,
246 0, 0, 0, 0, 0, 0, 0, 0 },
247 { ShellSubMenuFile
, NULL
, IDI_MENUFILE
, IDS_MENUSUBMENU
, 0,
248 0, 0, 0, 0, 0, 0, 0, 0 },
249 { ShellSubMenuFolder
, NULL
, IDI_MENUFOLDER
, IDS_MENUSUBMENU
, 0,
250 0, 0, 0, 0, 0, 0, 0, 0 },
251 { ShellSubMenuLink
, NULL
, IDI_MENULINK
, IDS_MENUSUBMENU
, 0,
252 0, 0, 0, 0, 0, 0, 0, 0 },
253 { ShellSubMenuMultiple
, NULL
, IDI_MENUMULTIPLE
, IDS_MENUSUBMENU
, 0,
254 0, 0, 0, 0, 0, 0, 0, 0 },
255 // mark the last entry to tell the loop where to stop iterating over this array
256 { ShellMenuLastEntry
, 0, 0, 0, 0,
257 0, 0, 0, 0, 0, 0, 0, 0 },
261 STDMETHODIMP
CShellExt::Initialize(LPCITEMIDLIST pIDFolder
,
262 LPDATAOBJECT pDataObj
,
266 ATLTRACE("Shell :: Initialize\n");
267 PreserveChdir preserveChdir
;
273 itemStatesFolder
= 0;
274 stdstring statuspath
;
275 git_wc_status_kind fetchedstatus
= git_wc_status_none
;
276 // get selected files/folders
280 FORMATETC fmte
= {(CLIPFORMAT
)g_shellidlist
,
281 (DVTARGETDEVICE FAR
*)NULL
,
285 HRESULT hres
= pDataObj
->GetData(&fmte
, &medium
);
287 if (SUCCEEDED(hres
) && medium
.hGlobal
)
289 if (m_State
== FileStateDropHandler
)
292 FORMATETC etc
= { CF_HDROP
, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
};
293 STGMEDIUM stg
= { TYMED_HGLOBAL
};
294 if ( FAILED( pDataObj
->GetData ( &etc
, &stg
)))
296 ReleaseStgMedium ( &medium
);
301 HDROP drop
= (HDROP
)GlobalLock(stg
.hGlobal
);
304 ReleaseStgMedium ( &stg
);
305 ReleaseStgMedium ( &medium
);
309 int count
= DragQueryFile(drop
, (UINT
)-1, NULL
, 0);
311 itemStates
|= ITEMIS_ONLYONE
;
312 for (int i
= 0; i
< count
; i
++)
314 // find the path length in chars
315 UINT len
= DragQueryFile(drop
, i
, NULL
, 0);
318 TCHAR
* szFileName
= new TCHAR
[len
+1];
319 if (0 == DragQueryFile(drop
, i
, szFileName
, len
+1))
321 delete [] szFileName
;
324 stdstring str
= stdstring(szFileName
);
325 delete [] szFileName
;
326 if ((str
.empty() == false)&&(g_ShellCache
.IsContextPathAllowed(szFileName
)))
328 if (itemStates
& ITEMIS_ONLYONE
)
331 strpath
.SetFromWin(str
.c_str());
332 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE
: 0;
333 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE
: 0;
335 files_
.push_back(str
);
338 //get the Subversion status of the item
339 git_wc_status_kind status
= git_wc_status_none
;
341 askedpath
.SetFromWin(str
.c_str());
345 stat
.GetStatus(CTGitPath(str
.c_str()), false, true, true);
349 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
350 fetchedstatus
= status
;
351 //if ((stat.status->entry)&&(stat.status->entry->lock_token))
352 // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
353 if ( askedpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
355 itemStates
|= ITEMIS_FOLDER
;
356 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
357 itemStates
|= ITEMIS_FOLDERINSVN
;
359 //if ((stat.status->entry)&&(stat.status->entry->present_props))
361 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
362 // itemStates |= ITEMIS_NEEDSLOCK;
364 //if ((stat.status->entry)&&(stat.status->entry->uuid))
365 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
369 // sometimes, git_client_status() returns with an error.
370 // in that case, we have to check if the working copy is versioned
371 // anyway to show the 'correct' context menu
372 if (askedpath
.HasAdminDir())
373 status
= git_wc_status_normal
;
378 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
381 // TODO: should we really assume any sub-directory to be versioned
382 // or only if it contains versioned files
383 if ( askedpath
.IsDirectory() )
385 if (askedpath
.HasAdminDir())
386 itemStates
|= ITEMIS_INSVN
;
387 if (askedpath
.HasSubmodules())
389 itemStates
|= ITEMIS_SUBMODULE
;
392 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
393 itemStates
|= ITEMIS_INSVN
;
394 if (status
== git_wc_status_ignored
)
395 itemStates
|= ITEMIS_IGNORED
;
396 if (status
== git_wc_status_normal
)
397 itemStates
|= ITEMIS_NORMAL
;
398 if (status
== git_wc_status_conflicted
)
399 itemStates
|= ITEMIS_CONFLICTED
;
400 if (status
== git_wc_status_added
)
401 itemStates
|= ITEMIS_ADDED
;
402 if (status
== git_wc_status_deleted
)
403 itemStates
|= ITEMIS_DELETED
;
406 } // for (int i = 0; i < count; i++)
407 GlobalUnlock ( drop
);
408 ReleaseStgMedium ( &stg
);
410 } // if (m_State == FileStateDropHandler)
414 //Enumerate PIDLs which the user has selected
415 CIDA
* cida
= (CIDA
*)GlobalLock(medium
.hGlobal
);
416 ItemIDList
parent( GetPIDLFolder (cida
));
418 int count
= cida
->cidl
;
419 BOOL statfetched
= FALSE
;
420 for (int i
= 0; i
< count
; ++i
)
422 ItemIDList
child (GetPIDLItem (cida
, i
), &parent
);
423 stdstring str
= child
.toString();
424 if ((str
.empty() == false)&&(g_ShellCache
.IsContextPathAllowed(str
.c_str())))
426 //check if our menu is requested for a subversion admin directory
427 if (g_GitAdminDir
.IsAdminDirPath(str
.c_str()))
430 files_
.push_back(str
);
432 strpath
.SetFromWin(str
.c_str());
433 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE
: 0;
434 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE
: 0;
437 //get the Subversion status of the item
438 git_wc_status_kind status
= git_wc_status_none
;
439 if ((g_ShellCache
.IsSimpleContext())&&(strpath
.IsDirectory()))
441 if (strpath
.HasAdminDir())
442 status
= git_wc_status_normal
;
449 if (strpath
.HasAdminDir())
450 stat
.GetStatus(strpath
, false, true, true);
454 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
455 fetchedstatus
= status
;
456 //if ((stat.status->entry)&&(stat.status->entry->lock_token))
457 // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
458 if ( strpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
460 itemStates
|= ITEMIS_FOLDER
;
461 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
462 itemStates
|= ITEMIS_FOLDERINSVN
;
464 // TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
465 if (status
== git_wc_status_conflicted
)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
466 itemStates
|= ITEMIS_CONFLICTED
;
467 //if ((stat.status->entry)&&(stat.status->entry->present_props))
469 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
470 // itemStates |= ITEMIS_NEEDSLOCK;
472 //if ((stat.status->entry)&&(stat.status->entry->uuid))
473 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
477 // sometimes, git_client_status() returns with an error.
478 // in that case, we have to check if the working copy is versioned
479 // anyway to show the 'correct' context menu
480 if (strpath
.HasAdminDir())
482 status
= git_wc_status_normal
;
483 fetchedstatus
= status
;
490 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
494 // TODO: should we really assume any sub-directory to be versioned
495 // or only if it contains versioned files
496 if ( strpath
.IsDirectory() )
498 if (strpath
.HasAdminDir())
499 itemStates
|= ITEMIS_INSVN
;
500 if (strpath
.HasSubmodules())
502 itemStates
|= ITEMIS_SUBMODULE
;
505 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
506 itemStates
|= ITEMIS_INSVN
;
507 if (status
== git_wc_status_ignored
)
509 itemStates
|= ITEMIS_IGNORED
;
510 // the item is ignored. Get the svn:ignored properties so we can (maybe) later
511 // offer a 'remove from ignored list' entry
512 // GitProperties props(strpath.GetContainingDirectory(), false);
513 // ignoredprops.empty();
514 // for (int p=0; p<props.GetCount(); ++p)
516 // if (props.GetItemName(p).compare(stdstring(_T("svn:ignore")))==0)
518 // std::string st = props.GetItemValue(p);
519 // ignoredprops = MultibyteToWide(st.c_str());
520 // // remove all escape chars ('\\')
521 // std::remove(ignoredprops.begin(), ignoredprops.end(), '\\');
526 if (status
== git_wc_status_normal
)
527 itemStates
|= ITEMIS_NORMAL
;
528 if (status
== git_wc_status_conflicted
)
529 itemStates
|= ITEMIS_CONFLICTED
;
530 if (status
== git_wc_status_added
)
531 itemStates
|= ITEMIS_ADDED
;
532 if (status
== git_wc_status_deleted
)
533 itemStates
|= ITEMIS_DELETED
;
536 } // for (int i = 0; i < count; ++i)
537 ItemIDList
child (GetPIDLItem (cida
, 0), &parent
);
538 if (g_ShellCache
.HasSVNAdminDir(child
.toString().c_str(), FALSE
))
539 itemStates
|= ITEMIS_INVERSIONEDFOLDER
;
540 GlobalUnlock(medium
.hGlobal
);
542 // if the item is a versioned folder, check if there's a patch file
543 // in the clipboard to be used in "Apply Patch"
544 UINT cFormatDiff
= RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
547 if (IsClipboardFormatAvailable(cFormatDiff
))
548 itemStates
|= ITEMIS_PATCHINCLIPBOARD
;
550 if (IsClipboardFormatAvailable(CF_HDROP
))
551 itemStates
|= ITEMIS_PATHINCLIPBOARD
;
555 ReleaseStgMedium ( &medium
);
556 if (medium
.pUnkForRelease
)
558 IUnknown
* relInterface
= (IUnknown
*)medium
.pUnkForRelease
;
559 relInterface
->Release();
564 // get folder background
568 ItemIDList
list(pIDFolder
);
569 folder_
= list
.toString();
570 git_wc_status_kind status
= git_wc_status_none
;
571 if (IsClipboardFormatAvailable(CF_HDROP
))
572 itemStatesFolder
|= ITEMIS_PATHINCLIPBOARD
;
575 askedpath
.SetFromWin(folder_
.c_str());
577 if ((folder_
.compare(statuspath
)!=0)&&(g_ShellCache
.IsContextPathAllowed(folder_
.c_str())))
583 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, true, true);
586 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
587 // if ((stat.status->entry)&&(stat.status->entry->lock_token))
588 // itemStatesFolder |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
589 // if ((stat.status->entry)&&(stat.status->entry->present_props))
591 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
592 // itemStatesFolder |= ITEMIS_NEEDSLOCK;
594 // if ((stat.status->entry)&&(stat.status->entry->uuid))
595 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
600 // sometimes, git_client_status() returns with an error.
601 // in that case, we have to check if the working copy is versioned
602 // anyway to show the 'correct' context menu
603 if (askedpath
.HasAdminDir())
604 status
= git_wc_status_normal
;
607 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
608 if (askedpath
.HasAdminDir())
609 itemStatesFolder
|= ITEMIS_INSVN
;
610 if (status
== git_wc_status_normal
)
611 itemStatesFolder
|= ITEMIS_NORMAL
;
612 if (status
== git_wc_status_conflicted
)
613 itemStatesFolder
|= ITEMIS_CONFLICTED
;
614 if (status
== git_wc_status_added
)
615 itemStatesFolder
|= ITEMIS_ADDED
;
616 if (status
== git_wc_status_deleted
)
617 itemStatesFolder
|= ITEMIS_DELETED
;
622 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
627 status
= fetchedstatus
;
629 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
630 if (askedpath
.HasAdminDir())
632 itemStatesFolder
|= ITEMIS_FOLDERINSVN
;
634 if (askedpath
.HasSubmodules())
636 itemStatesFolder
|= ITEMIS_SUBMODULE
;
638 if (askedpath
.HasGitSVNDir())
640 itemStatesFolder
|= ITEMIS_GITSVN
;
642 if (status
== git_wc_status_ignored
)
643 itemStatesFolder
|= ITEMIS_IGNORED
;
644 itemStatesFolder
|= ITEMIS_FOLDER
;
645 if (files_
.size() == 0)
646 itemStates
|= ITEMIS_ONLYONE
;
647 if (m_State
!= FileStateDropHandler
)
648 itemStates
|= itemStatesFolder
;
652 if (files_
.size() == 2)
653 itemStates
|= ITEMIS_TWO
;
654 if ((files_
.size() == 1)&&(g_ShellCache
.IsContextPathAllowed(files_
.front().c_str())))
657 itemStates
|= ITEMIS_ONLYONE
;
658 if (m_State
!= FileStateDropHandler
)
660 if (PathIsDirectory(files_
.front().c_str()))
662 folder_
= files_
.front();
663 git_wc_status_kind status
= git_wc_status_none
;
665 askedpath
.SetFromWin(folder_
.c_str());
667 if (folder_
.compare(statuspath
)!=0)
672 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, true, true);
675 status
= GitStatus::GetMoreImportant(stat
.status
->text_status
, stat
.status
->prop_status
);
676 // if ((stat.status->entry)&&(stat.status->entry->lock_token))
677 // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0;
678 // if ((stat.status->entry)&&(stat.status->entry->present_props))
680 // if (strstr(stat.status->entry->present_props, "svn:needs-lock"))
681 // itemStates |= ITEMIS_NEEDSLOCK;
683 // if ((stat.status->entry)&&(stat.status->entry->uuid))
684 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
689 ATLTRACE2(_T("Exception in GitStatus::GetStatus()\n"));
694 status
= fetchedstatus
;
696 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
697 if (askedpath
.HasAdminDir())
698 itemStates
|= ITEMIS_FOLDERINSVN
;
699 if (status
== git_wc_status_ignored
)
700 itemStates
|= ITEMIS_IGNORED
;
701 itemStates
|= ITEMIS_FOLDER
;
702 if (status
== git_wc_status_added
)
703 itemStates
|= ITEMIS_ADDED
;
704 if (status
== git_wc_status_deleted
)
705 itemStates
|= ITEMIS_DELETED
;
714 void CShellExt::InsertGitMenu(BOOL istop
, HMENU menu
, UINT pos
, UINT_PTR id
, UINT stringid
, UINT icon
, UINT idCmdFirst
, GitCommands com
, UINT uFlags
)
716 TCHAR menutextbuffer
[512] = {0};
717 TCHAR verbsbuffer
[255] = {0};
718 MAKESTRING(stringid
);
722 //menu entry for the top context menu, so append an "Git " before
723 //the menu text to indicate where the entry comes from
724 _tcscpy_s(menutextbuffer
, 255, _T("Git "));
726 _tcscat_s(menutextbuffer
, 255, stringtablebuffer
);
728 // insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
729 // so we have an easy and fast way to check the current branch
730 // (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
731 if (com
== ShellMenuCommit
)
734 CTGitPath
path(folder_
.empty() ? files_
.front().c_str() : folder_
.c_str());
735 CString sProjectRoot
;
738 if (path
.HasAdminDir(&sProjectRoot
) && !g_Git
.GetCurrentBranchFromFile(sProjectRoot
, sBranchName
))
740 if (sBranchName
.GetLength() == 40)
742 // if SHA1 only show 4 first bytes
744 for (int i
=0; i
<40; i
++)
745 if ( !iswxdigit(sBranchName
[i
]) )
751 sBranchName
= sBranchName
.Left(8) + _T("....");
755 if (sBranchName
.GetLength() > 64)
756 sBranchName
= sBranchName
.Left(64) + _T("...");
758 // scan to before "..."
759 LPTSTR s
= menutextbuffer
+ _tcslen(menutextbuffer
)-1;
760 if (s
> menutextbuffer
)
762 while (s
> menutextbuffer
)
777 // append branch name and end with ...
778 _tcscpy(s
, _T(" -> \"") + sBranchName
+ _T("\"..."));
782 if ((fullver
< 0x500)||(fullver
== 0x500 && !(uFlags
&~(CMF_RESERVED
|CMF_EXPLORE
|CMF_EXTENDEDVERBS
))))
784 // on win2k, the context menu does not work properly if we use
785 // icon bitmaps. At least the menu text is empty in the context menu
786 // for folder backgrounds (seems like a win2k bug).
787 // the workaround is to use the check/unchecked bitmaps, which are drawn
788 // with AND raster op, but it's better than nothing at all
789 InsertMenu(menu
, pos
, MF_BYPOSITION
| MF_STRING
, id
, menutextbuffer
);
792 HBITMAP bmp
= IconToBitmap(icon
);
793 SetMenuItemBitmaps(menu
, pos
, MF_BYPOSITION
, bmp
, bmp
);
798 MENUITEMINFO menuiteminfo
;
799 SecureZeroMemory(&menuiteminfo
, sizeof(menuiteminfo
));
800 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
801 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_STRING
;
802 menuiteminfo
.fType
= MFT_STRING
;
803 menuiteminfo
.dwTypeData
= menutextbuffer
;
806 menuiteminfo
.fMask
|= MIIM_BITMAP
;
807 menuiteminfo
.hbmpItem
= (fullver
>= 0x600) ? IconToBitmapPARGB32(icon
) : HBMMENU_CALLBACK
;
809 menuiteminfo
.wID
= id
;
810 InsertMenuItem(menu
, pos
, TRUE
, &menuiteminfo
);
815 //menu entry for the top context menu, so append an "Git " before
816 //the menu text to indicate where the entry comes from
817 _tcscpy_s(menutextbuffer
, 255, _T("Git "));
819 LoadString(g_hResInst
, stringid
, verbsbuffer
, sizeof(verbsbuffer
));
820 _tcscat_s(menutextbuffer
, 255, verbsbuffer
);
821 stdstring verb
= stdstring(menutextbuffer
);
822 if (verb
.find('&') != -1)
824 verb
.erase(verb
.find('&'),1);
826 myVerbsMap
[verb
] = id
- idCmdFirst
;
827 myVerbsMap
[verb
] = id
;
828 myVerbsIDMap
[id
- idCmdFirst
] = verb
;
829 myVerbsIDMap
[id
] = verb
;
830 // We store the relative and absolute diameter
831 // (drawitem callback uses absolute, others relative)
832 myIDMap
[id
- idCmdFirst
] = com
;
835 mySubMenuMap
[pos
] = com
;
838 HBITMAP
CShellExt::IconToBitmap(UINT uIcon
)
840 std::map
<UINT
, HBITMAP
>::iterator bitmap_it
= bitmaps
.lower_bound(uIcon
);
841 if (bitmap_it
!= bitmaps
.end() && bitmap_it
->first
== uIcon
)
842 return bitmap_it
->second
;
844 HICON hIcon
= (HICON
)LoadImage(g_hResInst
, MAKEINTRESOURCE(uIcon
), IMAGE_ICON
, 12, 12, LR_DEFAULTCOLOR
);
850 rect
.right
= ::GetSystemMetrics(SM_CXMENUCHECK
);
851 rect
.bottom
= ::GetSystemMetrics(SM_CYMENUCHECK
);
853 rect
.left
= rect
.top
= 0;
855 HWND desktop
= ::GetDesktopWindow();
862 HDC screen_dev
= ::GetDC(desktop
);
863 if (screen_dev
== NULL
)
869 // Create a compatible DC
870 HDC dst_hdc
= ::CreateCompatibleDC(screen_dev
);
874 ::ReleaseDC(desktop
, screen_dev
);
878 // Create a new bitmap of icon size
879 HBITMAP bmp
= ::CreateCompatibleBitmap(screen_dev
, rect
.right
, rect
.bottom
);
884 ::ReleaseDC(desktop
, screen_dev
);
888 // Select it into the compatible DC
889 HBITMAP old_dst_bmp
= (HBITMAP
)::SelectObject(dst_hdc
, bmp
);
890 if (old_dst_bmp
== NULL
)
896 // Fill the background of the compatible DC with the white color
897 // that is taken by menu routines as transparent
898 ::SetBkColor(dst_hdc
, RGB(255, 255, 255));
899 ::ExtTextOut(dst_hdc
, 0, 0, ETO_OPAQUE
, &rect
, NULL
, 0, NULL
);
901 // Draw the icon into the compatible DC
902 ::DrawIconEx(dst_hdc
, 0, 0, hIcon
, rect
.right
, rect
.bottom
, 0, NULL
, DI_NORMAL
);
905 ::SelectObject(dst_hdc
, old_dst_bmp
);
907 ::ReleaseDC(desktop
, screen_dev
);
910 bitmaps
.insert(bitmap_it
, std::make_pair(uIcon
, bmp
));
914 bool CShellExt::WriteClipboardPathsToTempFile(stdstring
& tempfile
)
917 tempfile
= stdstring();
918 //write all selected files and paths to a temporary file
919 //for TortoiseProc.exe to read out again.
921 DWORD pathlength
= GetTempPath(0, NULL
);
922 TCHAR
* path
= new TCHAR
[pathlength
+1];
923 TCHAR
* tempFile
= new TCHAR
[pathlength
+ 100];
924 GetTempPath (pathlength
+1, path
);
925 GetTempFileName (path
, _T("git"), 0, tempFile
);
926 tempfile
= stdstring(tempFile
);
928 HANDLE file
= ::CreateFile (tempFile
,
933 FILE_ATTRIBUTE_TEMPORARY
,
938 if (file
== INVALID_HANDLE_VALUE
)
941 if (!IsClipboardFormatAvailable(CF_HDROP
))
943 if (!OpenClipboard(NULL
))
946 stdstring sClipboardText
;
947 HGLOBAL hglb
= GetClipboardData(CF_HDROP
);
948 HDROP hDrop
= (HDROP
)GlobalLock(hglb
);
951 TCHAR szFileName
[MAX_PATH
];
952 UINT cFiles
= DragQueryFile(hDrop
, 0xFFFFFFFF, NULL
, 0);
953 for(UINT i
= 0; i
< cFiles
; ++i
)
955 DragQueryFile(hDrop
, i
, szFileName
, sizeof(szFileName
));
956 stdstring filename
= szFileName
;
957 ::WriteFile (file
, filename
.c_str(), filename
.size()*sizeof(TCHAR
), &written
, 0);
958 ::WriteFile (file
, _T("\n"), 2, &written
, 0);
971 stdstring
CShellExt::WriteFileListToTempFile()
973 //write all selected files and paths to a temporary file
974 //for TortoiseProc.exe to read out again.
975 DWORD pathlength
= GetTempPath(0, NULL
);
976 TCHAR
* path
= new TCHAR
[pathlength
+1];
977 TCHAR
* tempFile
= new TCHAR
[pathlength
+ 100];
978 GetTempPath (pathlength
+1, path
);
979 GetTempFileName (path
, _T("git"), 0, tempFile
);
980 stdstring retFilePath
= stdstring(tempFile
);
982 HANDLE file
= ::CreateFile (tempFile
,
987 FILE_ATTRIBUTE_TEMPORARY
,
992 if (file
== INVALID_HANDLE_VALUE
)
998 ::WriteFile (file
, folder_
.c_str(), folder_
.size()*sizeof(TCHAR
), &written
, 0);
999 ::WriteFile (file
, _T("\n"), 2, &written
, 0);
1002 for (std::vector
<stdstring
>::iterator I
= files_
.begin(); I
!= files_
.end(); ++I
)
1004 ::WriteFile (file
, I
->c_str(), I
->size()*sizeof(TCHAR
), &written
, 0);
1005 ::WriteFile (file
, _T("\n"), 2, &written
, 0);
1007 ::CloseHandle(file
);
1011 STDMETHODIMP
CShellExt::QueryDropContext(UINT uFlags
, UINT idCmdFirst
, HMENU hMenu
, UINT
&indexMenu
)
1013 PreserveChdir preserveChdir
;
1016 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
1017 return NOERROR
; //we don't change the default action
1019 if ((files_
.size() == 0)||(folder_
.size() == 0))
1022 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
1025 bool bSourceAndTargetFromSameRepository
= (uuidSource
.compare(uuidTarget
) == 0) || uuidSource
.empty() || uuidTarget
.empty();
1027 //the drop handler only has eight commands, but not all are visible at the same time:
1028 //if the source file(s) are under version control then those files can be moved
1029 //to the new location or they can be moved with a rename,
1030 //if they are unversioned then they can be added to the working copy
1031 //if they are versioned, they also can be exported to an unversioned location
1032 UINT idCmd
= idCmdFirst
;
1035 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
1036 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINSVN
)&&((itemStates
& ITEMIS_INSVN
)&&((~itemStates
) & ITEMIS_ADDED
)))
1037 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVEMENU
, 0, idCmdFirst
, ShellMenuDropMove
, uFlags
);
1039 // Git move and rename here
1040 // 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
1041 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINSVN
)&&(itemStates
& ITEMIS_INSVN
)&&(itemStates
& ITEMIS_ONLYONE
)&&((~itemStates
) & ITEMIS_ADDED
))
1042 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVERENAMEMENU
, 0, idCmdFirst
, ShellMenuDropMoveRename
, uFlags
);
1045 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
1046 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINSVN
)&&(itemStates
& ITEMIS_INSVN
)&&((~itemStates
) & ITEMIS_ADDED
))
1047 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYMENU
, 0, idCmdFirst
, ShellMenuDropCopy
, uFlags
);
1049 // Git copy and rename here, source and target from same repository
1050 // available if source is a single, versioned but not added item, target is versioned or target folder is added
1051 if ((bSourceAndTargetFromSameRepository
||(itemStatesFolder
& ITEMIS_ADDED
))&&(itemStatesFolder
& ITEMIS_FOLDERINSVN
)&&(itemStates
& ITEMIS_INSVN
)&&(itemStates
& ITEMIS_ONLYONE
)&&((~itemStates
) & ITEMIS_ADDED
))
1052 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYRENAMEMENU
, 0, idCmdFirst
, ShellMenuDropCopyRename
, uFlags
);
1055 // available if target is versioned and source is either unversioned or from another repository
1056 if ((itemStatesFolder
& ITEMIS_FOLDERINSVN
)&&(((~itemStates
) & ITEMIS_INSVN
)||!bSourceAndTargetFromSameRepository
))
1057 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYADDMENU
, 0, idCmdFirst
, ShellMenuDropCopyAdd
, uFlags
);
1060 // available if source is versioned and a folder
1061 if ((itemStates
& ITEMIS_INSVN
)&&(itemStates
& ITEMIS_FOLDER
))
1062 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPEXPORTMENU
, 0, idCmdFirst
, ShellMenuDropExport
, uFlags
);
1064 // Git export all here
1065 // available if source is versioned and a folder
1066 if ((itemStates
& ITEMIS_INSVN
)&&(itemStates
& ITEMIS_FOLDER
))
1067 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPEXPORTEXTENDEDMENU
, 0, idCmdFirst
, ShellMenuDropExportExtended
, uFlags
);
1070 // available if source is a patchfile
1071 if (itemStates
& ITEMIS_PATCHFILE
)
1072 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_MENUAPPLYPATCH
, 0, idCmdFirst
, ShellMenuApplyPatch
, uFlags
);
1075 if (idCmd
!= idCmdFirst
)
1076 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, NULL
);
1078 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
1081 STDMETHODIMP
CShellExt::QueryContextMenu(HMENU hMenu
,
1087 ATLTRACE("Shell :: QueryContextMenu\n");
1088 PreserveChdir preserveChdir
;
1090 //first check if our drop handler is called
1091 //and then (if true) provide the context menu for the
1093 if (m_State
== FileStateDropHandler
)
1095 return QueryDropContext(uFlags
, idCmdFirst
, hMenu
, indexMenu
);
1098 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
1099 return NOERROR
; //we don't change the default action
1101 if ((files_
.size() == 0)&&(folder_
.size() == 0))
1104 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
1111 CSIDL_COMMON_FAVORITES
,
1112 CSIDL_COMMON_STARTMENU
,
1113 CSIDL_COMPUTERSNEARME
,
1121 CSIDL_INTERNET_CACHE
,
1131 if (IsIllegalFolder(folder_
, csidlarray
))
1134 if (folder_
.empty())
1136 // folder is empty, but maybe files are selected
1137 if (files_
.size() == 0)
1138 return NOERROR
; // nothing selected - we don't have a menu to show
1139 // check whether a selected entry is an UID - those are namespace extensions
1140 // which we can't handle
1141 for (std::vector
<stdstring
>::const_iterator it
= files_
.begin(); it
!= files_
.end(); ++it
)
1143 if (_tcsncmp(it
->c_str(), _T("::{"), 3)==0)
1148 //check if our menu is requested for a subversion admin directory
1149 if (g_GitAdminDir
.IsAdminDirPath(folder_
.c_str()))
1152 if (uFlags
& CMF_EXTENDEDVERBS
)
1153 itemStates
|= ITEMIS_EXTENDED
;
1155 const BOOL bShortcut
= !!(uFlags
& CMF_VERBSONLY
);
1156 if ( bShortcut
&& (files_
.size()==1))
1158 // Don't show the context menu for a link if the
1159 // destination is not part of a working copy.
1160 // It would only show the standard menu items
1161 // which are already shown for the lnk-file.
1162 CString path
= files_
.front().c_str();
1163 if ( !g_GitAdminDir
.HasAdminDir(path
) )
1169 //check if we already added our menu entry for a folder.
1170 //we check that by iterating through all menu entries and check if
1171 //the dwItemData member points to our global ID string. That string is set
1172 //by our shell extension when the folder menu is inserted.
1173 TCHAR menubuf
[MAX_PATH
];
1174 int count
= GetMenuItemCount(hMenu
);
1175 for (int i
=0; i
<count
; ++i
)
1178 SecureZeroMemory(&miif
, sizeof(MENUITEMINFO
));
1179 miif
.cbSize
= sizeof(MENUITEMINFO
);
1180 miif
.fMask
= MIIM_DATA
;
1181 miif
.dwTypeData
= menubuf
;
1182 miif
.cch
= sizeof(menubuf
)/sizeof(TCHAR
);
1183 GetMenuItemInfo(hMenu
, i
, TRUE
, &miif
);
1184 if (miif
.dwItemData
== (ULONG_PTR
)g_MenuIDString
)
1189 UINT idCmd
= idCmdFirst
;
1191 //create the sub menu
1192 HMENU subMenu
= CreateMenu();
1193 int indexSubMenu
= 0;
1195 unsigned __int64 topmenu
= g_ShellCache
.GetMenuLayout();
1196 unsigned __int64 menumask
= g_ShellCache
.GetMenuMask();
1199 bool bAddSeparator
= false;
1200 bool bMenuEntryAdded
= false;
1201 bool bMenuEmpty
= true;
1202 // insert separator at start
1203 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, NULL
); idCmd
++;
1204 bool bShowIcons
= !!DWORD(CRegStdWORD(_T("Software\\TortoiseGit\\ShowContextMenuIcons"), TRUE
));
1205 // ?? TSV disabled icons for win2k and earlier, but they work for win2k and should work for win95 and up
1206 /*if (fullver <= 0x500)
1207 bShowIcons = false;*/
1210 if (itemStates
& (ITEMIS_INSVN
|ITEMIS_FOLDERINSVN
))
1212 // show current branch name (as a "read-only" menu entry)
1214 CTGitPath
path(folder_
.empty() ? files_
.front().c_str() : folder_
.c_str());
1215 CString sProjectRoot
;
1216 CString sBranchName
;
1218 if (path
.HasAdminDir(&sProjectRoot
) && !g_Git
.GetCurrentBranchFromFile(sProjectRoot
, sBranchName
))
1220 if (sBranchName
.GetLength() == 40)
1222 // if SHA1 only show 4 first bytes
1223 BOOL bIsSha1
= TRUE
;
1224 for (int i
=0; i
<40; i
++)
1225 if ( !iswxdigit(sBranchName
[i
]) )
1231 sBranchName
= sBranchName
.Left(8) + _T("....");
1234 sBranchName
= _T('"') + sBranchName
+ _T('"');
1236 const int icon
= IDI_COPY
;
1237 const int pos
= indexMenu
++;
1238 const int id
= idCmd
++;
1240 if ((fullver
< 0x500)||(fullver
== 0x500 && !(uFlags
&~(CMF_RESERVED
|CMF_EXPLORE
|CMF_EXTENDEDVERBS
))))
1242 InsertMenu(hMenu
, pos
, MF_DISABLED
|MF_GRAYED
|MF_BYPOSITION
|MF_STRING
, id
, sBranchName
);
1243 HBITMAP bmp
= IconToBitmap(icon
);
1244 SetMenuItemBitmaps(hMenu
, pos
, MF_BYPOSITION
, bmp
, bmp
);
1248 MENUITEMINFO menuiteminfo
;
1249 SecureZeroMemory(&menuiteminfo
, sizeof(menuiteminfo
));
1250 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
1251 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_STRING
| MIIM_STATE
;
1252 menuiteminfo
.fState
= MFS_DISABLED
;
1253 menuiteminfo
.fType
= MFT_STRING
;
1254 menuiteminfo
.dwTypeData
= (LPWSTR
)sBranchName
.GetString();
1257 menuiteminfo
.fMask
|= MIIM_BITMAP
;
1258 menuiteminfo
.hbmpItem
= (fullver
>= 0x600) ? IconToBitmapPARGB32(icon
) : HBMMENU_CALLBACK
;
1260 if (menuiteminfo
.hbmpItem
== HBMMENU_CALLBACK
)
1262 // WM_DRAWITEM uses myIDMap to get icon, we use the same icon as create branch
1263 myIDMap
[id
- idCmdFirst
] = ShellMenuBranch
;
1264 myIDMap
[id
] = ShellMenuBranch
;
1267 menuiteminfo
.wID
= id
;
1268 InsertMenuItem(hMenu
, pos
, TRUE
, &menuiteminfo
);
1274 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1276 if (menuInfo
[menuIndex
].command
== ShellSeparator
)
1278 // we don't add a separator immediately. Because there might not be
1279 // another 'normal' menu entry after we insert a separator.
1280 // we simply set a flag here, indicating that before the next
1281 // 'normal' menu entry, a separator should be added.
1283 bAddSeparator
= true;
1287 // check the conditions whether to show the menu entry or not
1288 bool bInsertMenu
= false;
1290 if (menuInfo
[menuIndex
].firstyes
&& menuInfo
[menuIndex
].firstno
)
1292 if (((menuInfo
[menuIndex
].firstyes
& itemStates
) == menuInfo
[menuIndex
].firstyes
)
1294 ((menuInfo
[menuIndex
].firstno
& (~itemStates
)) == menuInfo
[menuIndex
].firstno
))
1297 else if ((menuInfo
[menuIndex
].firstyes
)&&((menuInfo
[menuIndex
].firstyes
& itemStates
) == menuInfo
[menuIndex
].firstyes
))
1299 else if ((menuInfo
[menuIndex
].firstno
)&&((menuInfo
[menuIndex
].firstno
& (~itemStates
)) == menuInfo
[menuIndex
].firstno
))
1302 if (menuInfo
[menuIndex
].secondyes
&& menuInfo
[menuIndex
].secondno
)
1304 if (((menuInfo
[menuIndex
].secondyes
& itemStates
) == menuInfo
[menuIndex
].secondyes
)
1306 ((menuInfo
[menuIndex
].secondno
& (~itemStates
)) == menuInfo
[menuIndex
].secondno
))
1309 else if ((menuInfo
[menuIndex
].secondyes
)&&((menuInfo
[menuIndex
].secondyes
& itemStates
) == menuInfo
[menuIndex
].secondyes
))
1311 else if ((menuInfo
[menuIndex
].secondno
)&&((menuInfo
[menuIndex
].secondno
& (~itemStates
)) == menuInfo
[menuIndex
].secondno
))
1314 if (menuInfo
[menuIndex
].thirdyes
&& menuInfo
[menuIndex
].thirdno
)
1316 if (((menuInfo
[menuIndex
].thirdyes
& itemStates
) == menuInfo
[menuIndex
].thirdyes
)
1318 ((menuInfo
[menuIndex
].thirdno
& (~itemStates
)) == menuInfo
[menuIndex
].thirdno
))
1321 else if ((menuInfo
[menuIndex
].thirdyes
)&&((menuInfo
[menuIndex
].thirdyes
& itemStates
) == menuInfo
[menuIndex
].thirdyes
))
1323 else if ((menuInfo
[menuIndex
].thirdno
)&&((menuInfo
[menuIndex
].thirdno
& (~itemStates
)) == menuInfo
[menuIndex
].thirdno
))
1326 if (menuInfo
[menuIndex
].fourthyes
&& menuInfo
[menuIndex
].fourthno
)
1328 if (((menuInfo
[menuIndex
].fourthyes
& itemStates
) == menuInfo
[menuIndex
].fourthyes
)
1330 ((menuInfo
[menuIndex
].fourthno
& (~itemStates
)) == menuInfo
[menuIndex
].fourthno
))
1333 else if ((menuInfo
[menuIndex
].fourthyes
)&&((menuInfo
[menuIndex
].fourthyes
& itemStates
) == menuInfo
[menuIndex
].fourthyes
))
1335 else if ((menuInfo
[menuIndex
].fourthno
)&&((menuInfo
[menuIndex
].fourthno
& (~itemStates
)) == menuInfo
[menuIndex
].fourthno
))
1338 if (menuInfo
[menuIndex
].menuID
& (~menumask
))
1342 // insert a separator
1343 if ((bMenuEntryAdded
)&&(bAddSeparator
))
1345 bAddSeparator
= false;
1346 bMenuEntryAdded
= false;
1347 InsertMenu(subMenu
, indexSubMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, NULL
);
1351 // handle special cases (sub menus)
1352 if ((menuInfo
[menuIndex
].command
== ShellMenuIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuUnIgnoreSub
))
1354 InsertIgnoreSubmenus(idCmd
, idCmdFirst
, hMenu
, subMenu
, indexMenu
, indexSubMenu
, topmenu
, bShowIcons
, uFlags
);
1355 bMenuEntryAdded
= true;
1360 bool bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1362 // insert the menu entry
1363 InsertGitMenu( bIsTop
,
1364 bIsTop
? hMenu
: subMenu
,
1365 bIsTop
? indexMenu
++ : indexSubMenu
++,
1367 menuInfo
[menuIndex
].menuTextID
,
1368 bShowIcons
? menuInfo
[menuIndex
].iconID
: 0,
1370 menuInfo
[menuIndex
].command
,
1374 bMenuEntryAdded
= true;
1384 //add sub menu to main context menu
1385 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1386 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1387 MAKESTRING(IDS_MENUSUBMENU
);
1388 MENUITEMINFO menuiteminfo
;
1389 SecureZeroMemory(&menuiteminfo
, sizeof(menuiteminfo
));
1390 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
1391 menuiteminfo
.fType
= MFT_STRING
;
1392 menuiteminfo
.dwTypeData
= stringtablebuffer
;
1394 UINT uIcon
= bShowIcons
? IDI_APP
: 0;
1397 uIcon
= bShowIcons
? IDI_MENUFOLDER
: 0;
1398 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFolder
;
1399 myIDMap
[idCmd
] = ShellSubMenuFolder
;
1400 menuiteminfo
.dwItemData
= (ULONG_PTR
)g_MenuIDString
;
1402 else if (!bShortcut
&& (files_
.size()==1))
1404 uIcon
= bShowIcons
? IDI_MENUFILE
: 0;
1405 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFile
;
1406 myIDMap
[idCmd
] = ShellSubMenuFile
;
1408 else if (bShortcut
&& (files_
.size()==1))
1410 uIcon
= bShowIcons
? IDI_MENULINK
: 0;
1411 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuLink
;
1412 myIDMap
[idCmd
] = ShellSubMenuLink
;
1414 else if (files_
.size() > 1)
1416 uIcon
= bShowIcons
? IDI_MENUMULTIPLE
: 0;
1417 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuMultiple
;
1418 myIDMap
[idCmd
] = ShellSubMenuMultiple
;
1422 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenu
;
1423 myIDMap
[idCmd
] = ShellSubMenu
;
1426 if ((fullver
< 0x500)||(fullver
== 0x500 && !(uFlags
&~(CMF_RESERVED
|CMF_EXPLORE
|CMF_EXTENDEDVERBS
))))
1428 menuiteminfo
.fMask
= MIIM_STRING
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
;
1431 menuiteminfo
.fMask
|= MIIM_CHECKMARKS
;
1432 bmp
= IconToBitmap(uIcon
);
1433 menuiteminfo
.hbmpChecked
= bmp
;
1434 menuiteminfo
.hbmpUnchecked
= bmp
;
1439 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
1442 menuiteminfo
.fMask
|= MIIM_BITMAP
;
1443 menuiteminfo
.hbmpItem
= (fullver
>= 0x600) ? IconToBitmapPARGB32(uIcon
) : HBMMENU_CALLBACK
;
1446 menuiteminfo
.hSubMenu
= subMenu
;
1447 menuiteminfo
.wID
= idCmd
++;
1448 InsertMenuItem(hMenu
, indexMenu
++, TRUE
, &menuiteminfo
);
1451 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
|MF_BYPOSITION
, 0, NULL
); idCmd
++;
1453 //return number of menu items added
1454 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, (USHORT
)(idCmd
- idCmdFirst
)));
1458 // This is called when you invoke a command on the menu:
1459 STDMETHODIMP
CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi
)
1461 PreserveChdir preserveChdir
;
1462 HRESULT hr
= E_INVALIDARG
;
1466 std::string command
;
1470 if ((files_
.size() > 0)||(folder_
.size() > 0))
1472 UINT idCmd
= LOWORD(lpcmi
->lpVerb
);
1474 if (HIWORD(lpcmi
->lpVerb
))
1476 stdstring verb
= stdstring(MultibyteToWide(lpcmi
->lpVerb
));
1477 std::map
<stdstring
, UINT_PTR
>::const_iterator verb_it
= myVerbsMap
.lower_bound(verb
);
1478 if (verb_it
!= myVerbsMap
.end() && verb_it
->first
== verb
)
1479 idCmd
= verb_it
->second
;
1484 // See if we have a handler interface for this id
1485 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1486 if (id_it
!= myIDMap
.end() && id_it
->first
== idCmd
)
1488 STARTUPINFO startup
;
1489 PROCESS_INFORMATION process
;
1490 memset(&startup
, 0, sizeof(startup
));
1491 startup
.cb
= sizeof(startup
);
1492 memset(&process
, 0, sizeof(process
));
1493 CRegStdString
tortoiseProcPath(_T("Software\\TortoiseGit\\ProcPath"), _T("TortoiseProc.exe"), false, HKEY_LOCAL_MACHINE
);
1494 CRegStdString
tortoiseMergePath(_T("Software\\TortoiseGit\\TMergePath"), _T("TortoiseMerge.exe"), false, HKEY_LOCAL_MACHINE
);
1496 //TortoiseProc expects a command line of the form:
1497 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1499 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1501 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1502 //* pathfile is a path to a temporary file which contains a list of file paths
1503 stdstring svnCmd
= _T(" /command:");
1505 switch (id_it
->second
)
1508 case ShellMenuCheckout
:
1509 svnCmd
+= _T("checkout /path:\"");
1513 case ShellMenuUpdate
:
1514 tempfile
= WriteFileListToTempFile();
1515 svnCmd
+= _T("update /pathfile:\"");
1518 svnCmd
+= _T(" /deletepathfile");
1520 case ShellMenuSubSync
:
1521 tempfile
= WriteFileListToTempFile();
1522 svnCmd
+= _T("subsync /pathfile:\"");
1525 svnCmd
+= _T(" /deletepathfile");
1526 if(itemStatesFolder
&ITEMIS_SUBMODULE
)
1528 svnCmd
+= _T(" /bkpath:");
1532 case ShellMenuUpdateExt
:
1533 tempfile
= WriteFileListToTempFile();
1534 svnCmd
+= _T("subupdate /pathfile:\"");
1537 svnCmd
+= _T(" /deletepathfile");
1538 if(itemStatesFolder
&ITEMIS_SUBMODULE
)
1540 svnCmd
+= _T(" /bkpath:");
1544 case ShellMenuCommit
:
1545 tempfile
= WriteFileListToTempFile();
1546 svnCmd
+= _T("commit /pathfile:\"");
1549 svnCmd
+= _T(" /deletepathfile");
1552 case ShellMenuAddAsReplacement
:
1553 tempfile
= WriteFileListToTempFile();
1554 svnCmd
+= _T("add /pathfile:\"");
1557 svnCmd
+= _T(" /deletepathfile");
1559 case ShellMenuIgnore
:
1560 tempfile
= WriteFileListToTempFile();
1561 svnCmd
+= _T("ignore /pathfile:\"");
1564 svnCmd
+= _T(" /deletepathfile");
1566 case ShellMenuIgnoreCaseSensitive
:
1567 tempfile
= WriteFileListToTempFile();
1568 svnCmd
+= _T("ignore /pathfile:\"");
1571 svnCmd
+= _T(" /deletepathfile");
1572 svnCmd
+= _T(" /onlymask");
1574 case ShellMenuUnIgnore
:
1575 tempfile
= WriteFileListToTempFile();
1576 svnCmd
+= _T("unignore /pathfile:\"");
1579 svnCmd
+= _T(" /deletepathfile");
1581 case ShellMenuUnIgnoreCaseSensitive
:
1582 tempfile
= WriteFileListToTempFile();
1583 svnCmd
+= _T("unignore /pathfile:\"");
1586 svnCmd
+= _T(" /deletepathfile");
1587 svnCmd
+= _T(" /onlymask");
1589 case ShellMenuRevert
:
1590 tempfile
= WriteFileListToTempFile();
1591 svnCmd
+= _T("revert /pathfile:\"");
1594 svnCmd
+= _T(" /deletepathfile");
1596 case ShellMenuDelUnversioned
:
1597 svnCmd
+= _T("delunversioned /path:\"");
1601 case ShellMenuCleanup
:
1602 tempfile
= WriteFileListToTempFile();
1603 svnCmd
+= _T("cleanup /pathfile:\"");
1606 svnCmd
+= _T(" /deletepathfile");
1608 case ShellMenuSendMail
:
1609 tempfile
= WriteFileListToTempFile();
1610 svnCmd
+= _T("sendmail /pathfile:\"");
1613 svnCmd
+= _T(" /deletepathfile");
1615 case ShellMenuResolve
:
1616 tempfile
= WriteFileListToTempFile();
1617 svnCmd
+= _T("resolve /pathfile:\"");
1620 svnCmd
+= _T(" /deletepathfile");
1622 case ShellMenuSwitch
:
1623 svnCmd
+= _T("switch /path:\"");
1624 if (files_
.size() > 0)
1625 svnCmd
+= files_
.front();
1630 case ShellMenuImport
:
1631 svnCmd
+= _T("import /path:\"");
1635 case ShellMenuExport
:
1636 svnCmd
+= _T("export /path:\"");
1640 case ShellMenuAbout
:
1641 svnCmd
+= _T("about");
1643 case ShellMenuCreateRepos
:
1644 svnCmd
+= _T("repocreate /path:\"");
1648 case ShellMenuMerge
:
1649 svnCmd
+= _T("merge /path:\"");
1650 if (files_
.size() > 0)
1651 svnCmd
+= files_
.front();
1656 case ShellMenuMergeAll
:
1657 svnCmd
+= _T("mergeall /path:\"");
1658 if (files_
.size() > 0)
1659 svnCmd
+= files_
.front();
1665 svnCmd
+= _T("copy /path:\"");
1666 if (files_
.size() > 0)
1667 svnCmd
+= files_
.front();
1672 case ShellMenuSettings
:
1673 svnCmd
+= _T("settings /path:\"");
1674 if (files_
.size() > 0)
1675 svnCmd
+= files_
.front();
1681 svnCmd
+= _T("help");
1683 case ShellMenuRename
:
1684 svnCmd
+= _T("rename /path:\"");
1685 if (files_
.size() > 0)
1686 svnCmd
+= files_
.front();
1691 case ShellMenuRemove
:
1692 tempfile
= WriteFileListToTempFile();
1693 svnCmd
+= _T("remove /pathfile:\"");
1696 svnCmd
+= _T(" /deletepathfile");
1698 case ShellMenuRemoveKeep
:
1699 tempfile
= WriteFileListToTempFile();
1700 svnCmd
+= _T("remove /pathfile:\"");
1703 svnCmd
+= _T(" /deletepathfile");
1704 svnCmd
+= _T(" /keep");
1707 svnCmd
+= _T("diff /path:\"");
1708 if (files_
.size() == 1)
1709 svnCmd
+= files_
.front();
1710 else if (files_
.size() == 2)
1712 std::vector
<stdstring
>::iterator I
= files_
.begin();
1715 svnCmd
+= _T("\" /path2:\"");
1721 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1722 svnCmd
+= _T(" /alternative");
1724 case ShellMenuPrevDiff
:
1725 svnCmd
+= _T("prevdiff /path:\"");
1726 if (files_
.size() == 1)
1727 svnCmd
+= files_
.front();
1731 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1732 svnCmd
+= _T(" /alternative");
1734 case ShellMenuUrlDiff
:
1735 svnCmd
+= _T("urldiff /path:\"");
1736 if (files_
.size() == 1)
1737 svnCmd
+= files_
.front();
1742 case ShellMenuDropCopyAdd
:
1743 tempfile
= WriteFileListToTempFile();
1744 svnCmd
+= _T("dropcopyadd /pathfile:\"");
1747 svnCmd
+= _T(" /deletepathfile");
1748 svnCmd
+= _T(" /droptarget:\"");
1752 case ShellMenuDropCopy
:
1753 tempfile
= WriteFileListToTempFile();
1754 svnCmd
+= _T("dropcopy /pathfile:\"");
1757 svnCmd
+= _T(" /deletepathfile");
1758 svnCmd
+= _T(" /droptarget:\"");
1762 case ShellMenuDropCopyRename
:
1763 tempfile
= WriteFileListToTempFile();
1764 svnCmd
+= _T("dropcopy /pathfile:\"");
1767 svnCmd
+= _T(" /deletepathfile");
1768 svnCmd
+= _T(" /droptarget:\"");
1770 svnCmd
+= _T("\" /rename";)
1772 case ShellMenuDropMove
:
1773 tempfile
= WriteFileListToTempFile();
1774 svnCmd
+= _T("dropmove /pathfile:\"");
1777 svnCmd
+= _T(" /deletepathfile");
1778 svnCmd
+= _T(" /droptarget:\"");
1782 case ShellMenuDropMoveRename
:
1783 tempfile
= WriteFileListToTempFile();
1784 svnCmd
+= _T("dropmove /pathfile:\"");
1787 svnCmd
+= _T(" /deletepathfile");
1788 svnCmd
+= _T(" /droptarget:\"");
1790 svnCmd
+= _T("\" /rename";)
1792 case ShellMenuDropExport
:
1793 tempfile
= WriteFileListToTempFile();
1794 svnCmd
+= _T("dropexport /pathfile:\"");
1797 svnCmd
+= _T(" /deletepathfile");
1798 svnCmd
+= _T(" /droptarget:\"");
1802 case ShellMenuDropExportExtended
:
1803 tempfile
= WriteFileListToTempFile();
1804 svnCmd
+= _T("dropexport /pathfile:\"");
1807 svnCmd
+= _T(" /deletepathfile");
1808 svnCmd
+= _T(" /droptarget:\"");
1811 svnCmd
+= _T(" /extended");
1814 svnCmd
+= _T("log /path:\"");
1815 if (files_
.size() > 0)
1816 svnCmd
+= files_
.front();
1821 case ShellMenuConflictEditor
:
1822 svnCmd
+= _T("conflicteditor /path:\"");
1823 if (files_
.size() > 0)
1824 svnCmd
+= files_
.front();
1829 case ShellMenuRelocate
:
1830 svnCmd
+= _T("relocate /path:\"");
1831 if (files_
.size() > 0)
1832 svnCmd
+= files_
.front();
1837 case ShellMenuGitSVNRebase
:
1838 svnCmd
+= _T("svnrebase /path:\"");
1839 if (files_
.size() > 0)
1840 svnCmd
+= files_
.front();
1845 case ShellMenuGitSVNDCommit
:
1846 svnCmd
+= _T("svndcommit /path:\"");
1847 if (files_
.size() > 0)
1848 svnCmd
+= files_
.front();
1853 case ShellMenuRebase
:
1854 svnCmd
+= _T("rebase /path:\"");
1855 if (files_
.size() > 0)
1856 svnCmd
+= files_
.front();
1861 case ShellMenuShowChanged
:
1862 if (files_
.size() > 1)
1864 tempfile
= WriteFileListToTempFile();
1865 svnCmd
+= _T("repostatus /pathfile:\"");
1868 svnCmd
+= _T(" /deletepathfile");
1872 svnCmd
+= _T("repostatus /path:\"");
1873 if (files_
.size() > 0)
1874 svnCmd
+= files_
.front();
1880 case ShellMenuRefBrowse
:
1881 svnCmd
+= _T("refbrowse /path:\"");
1882 if (files_
.size() > 0)
1883 svnCmd
+= files_
.front();
1888 case ShellMenuRefLog
:
1889 svnCmd
+= _T("reflog /path:\"");
1890 if (files_
.size() > 0)
1891 svnCmd
+= files_
.front();
1897 case ShellMenuStashSave
:
1898 svnCmd
+= _T("stashsave /path:\"");
1899 if (files_
.size() > 0)
1900 svnCmd
+= files_
.front();
1906 case ShellMenuStashApply
:
1907 svnCmd
+= _T("stashapply /path:\"");
1908 if (files_
.size() > 0)
1909 svnCmd
+= files_
.front();
1915 case ShellMenuStashList
:
1916 svnCmd
+= _T("reflog /path:\"");
1917 if (files_
.size() > 0)
1918 svnCmd
+= files_
.front();
1921 svnCmd
+= _T("\" /ref:refs/stash");
1924 case ShellMenuSubAdd
:
1925 svnCmd
+= _T("subadd /path:\"");
1926 if (files_
.size() > 0)
1927 svnCmd
+= files_
.front();
1933 case ShellMenuBlame
:
1934 svnCmd
+= _T("blame /path:\"");
1935 if (files_
.size() > 0)
1936 svnCmd
+= files_
.front();
1941 case ShellMenuCreatePatch
:
1942 tempfile
= WriteFileListToTempFile();
1943 svnCmd
+= _T("createpatch /pathfile:\"");
1946 svnCmd
+= _T(" /deletepathfile");
1948 case ShellMenuApplyPatch
:
1949 if ((itemStates
& ITEMIS_PATCHINCLIPBOARD
) && ((~itemStates
) & ITEMIS_PATCHFILE
))
1951 // if there's a patch file in the clipboard, we save it
1952 // to a temporary file and tell TortoiseMerge to use that one
1953 UINT cFormat
= RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
1954 if ((cFormat
)&&(OpenClipboard(NULL
)))
1956 HGLOBAL hglb
= GetClipboardData(cFormat
);
1957 LPCSTR lpstr
= (LPCSTR
)GlobalLock(hglb
);
1959 DWORD len
= GetTempPath(0, NULL
);
1960 TCHAR
* path
= new TCHAR
[len
+1];
1961 TCHAR
* tempF
= new TCHAR
[len
+100];
1962 GetTempPath (len
+1, path
);
1963 GetTempFileName (path
, TEXT("git"), 0, tempF
);
1964 std::wstring sTempFile
= std::wstring(tempF
);
1969 size_t patchlen
= strlen(lpstr
);
1970 _tfopen_s(&outFile
, sTempFile
.c_str(), _T("wb"));
1973 size_t size
= fwrite(lpstr
, sizeof(char), patchlen
, outFile
);
1974 if (size
== patchlen
)
1976 itemStates
|= ITEMIS_PATCHFILE
;
1978 files_
.push_back(sTempFile
);
1986 if (itemStates
& ITEMIS_PATCHFILE
)
1988 svnCmd
= _T(" /diff:\"");
1989 if (files_
.size() > 0)
1991 svnCmd
+= files_
.front();
1992 if (itemStatesFolder
& ITEMIS_FOLDERINSVN
)
1994 svnCmd
+= _T("\" /patchpath:\"");
2000 if (itemStates
& ITEMIS_INVERSIONEDFOLDER
)
2001 svnCmd
+= _T("\" /wc");
2007 svnCmd
= _T(" /patchpath:\"");
2008 if (files_
.size() > 0)
2009 svnCmd
+= files_
.front();
2015 myVerbsIDMap
.clear();
2017 if (CreateProcess(((stdstring
)tortoiseMergePath
).c_str(), const_cast<TCHAR
*>(svnCmd
.c_str()), NULL
, NULL
, FALSE
, 0, 0, 0, &startup
, &process
)==0)
2020 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
|
2021 FORMAT_MESSAGE_FROM_SYSTEM
|
2022 FORMAT_MESSAGE_IGNORE_INSERTS
,
2025 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), // Default language
2030 MessageBox( NULL
, (LPCTSTR
)lpMsgBuf
, _T("TortoiseMerge launch failed"), MB_OK
| MB_ICONINFORMATION
);
2031 LocalFree( lpMsgBuf
);
2033 CloseHandle(process
.hThread
);
2034 CloseHandle(process
.hProcess
);
2037 case ShellMenuRevisionGraph
:
2038 svnCmd
+= _T("revisiongraph /path:\"");
2039 if (files_
.size() > 0)
2040 svnCmd
+= files_
.front();
2045 case ShellMenuProperties
:
2046 tempfile
= WriteFileListToTempFile();
2047 svnCmd
+= _T("properties /pathfile:\"");
2050 svnCmd
+= _T(" /deletepathfile");
2052 case ShellMenuClipPaste
:
2053 if (WriteClipboardPathsToTempFile(tempfile
))
2056 UINT cPrefDropFormat
= RegisterClipboardFormat(_T("Preferred DropEffect"));
2057 if (cPrefDropFormat
)
2059 if (OpenClipboard(lpcmi
->hwnd
))
2061 HGLOBAL hglb
= GetClipboardData(cPrefDropFormat
);
2064 DWORD
* effect
= (DWORD
*) GlobalLock(hglb
);
2065 if (*effect
== DROPEFFECT_MOVE
)
2074 svnCmd
+= _T("pastecopy /pathfile:\"");
2076 svnCmd
+= _T("pastemove /pathfile:\"");
2079 svnCmd
+= _T(" /deletepathfile");
2080 svnCmd
+= _T(" /droptarget:\"");
2084 else return NOERROR
;
2086 case ShellMenuClone
:
2087 svnCmd
+= _T("clone /path:\"");
2092 svnCmd
+= _T("pull /path:\"");
2093 if (files_
.size() > 0)
2094 svnCmd
+= files_
.front();
2100 svnCmd
+= _T("push /path:\"");
2101 if (files_
.size() > 0)
2102 svnCmd
+= files_
.front();
2107 case ShellMenuBranch
:
2108 svnCmd
+= _T("branch /path:\"");
2109 if (files_
.size() > 0)
2110 svnCmd
+= files_
.front();
2117 svnCmd
+= _T("tag /path:\"");
2118 if (files_
.size() > 0)
2119 svnCmd
+= files_
.front();
2125 case ShellMenuFormatPatch
:
2126 svnCmd
+= _T("formatpatch /path:\"");
2127 if (files_
.size() > 0)
2128 svnCmd
+= files_
.front();
2134 case ShellMenuImportPatch
:
2135 tempfile
= WriteFileListToTempFile();
2136 svnCmd
+= _T("importpatch /pathfile:\"");
2139 svnCmd
+= _T(" /deletepathfile");
2142 case ShellMenuCherryPick
:
2143 svnCmd
+= _T("cherrypick /path:\"");
2144 if (files_
.size() > 0)
2145 svnCmd
+= files_
.front();
2150 case ShellMenuFetch
:
2151 svnCmd
+= _T("fetch /path:\"");
2152 if (files_
.size() > 0)
2153 svnCmd
+= files_
.front();
2162 } // switch (id_it->second)
2163 svnCmd
+= _T(" /hwnd:");
2165 _stprintf_s(buf
, 30, _T("%d"), lpcmi
->hwnd
);
2168 myVerbsIDMap
.clear();
2170 if (CreateProcess(((stdstring
)tortoiseProcPath
).c_str(), const_cast<TCHAR
*>(svnCmd
.c_str()), NULL
, NULL
, FALSE
, 0, 0, 0, &startup
, &process
)==0)
2173 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
|
2174 FORMAT_MESSAGE_FROM_SYSTEM
|
2175 FORMAT_MESSAGE_IGNORE_INSERTS
,
2178 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), // Default language
2183 MessageBox( NULL
, (LPCTSTR
)lpMsgBuf
, _T("TortoiseProc Launch failed"), MB_OK
| MB_ICONINFORMATION
);
2184 LocalFree( lpMsgBuf
);
2186 CloseHandle(process
.hThread
);
2187 CloseHandle(process
.hProcess
);
2189 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
2190 } // if ((files_.size() > 0)||(folder_.size() > 0))
2195 // This is for the status bar and things like that:
2196 STDMETHODIMP
CShellExt::GetCommandString(UINT_PTR idCmd
,
2198 UINT FAR
* /*reserved*/,
2202 PreserveChdir preserveChdir
;
2203 //do we know the id?
2204 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
2205 if (id_it
== myIDMap
.end() || id_it
->first
!= idCmd
)
2207 return E_INVALIDARG
; //no, we don't
2211 HRESULT hr
= E_INVALIDARG
;
2213 MAKESTRING(IDS_MENUDESCDEFAULT
);
2215 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
2217 if (menuInfo
[menuIndex
].command
== (GitCommands
)id_it
->second
)
2219 MAKESTRING(menuInfo
[menuIndex
].menuDescID
);
2225 const TCHAR
* desc
= stringtablebuffer
;
2230 std::string help
= WideToMultibyte(desc
);
2231 lstrcpynA(pszName
, help
.c_str(), cchMax
);
2237 wide_string help
= desc
;
2238 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
);
2244 std::map
<UINT_PTR
, stdstring
>::const_iterator verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
2245 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
2247 std::string help
= WideToMultibyte(verb_id_it
->second
);
2248 lstrcpynA(pszName
, help
.c_str(), cchMax
);
2255 std::map
<UINT_PTR
, stdstring
>::const_iterator verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
2256 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
2258 wide_string help
= verb_id_it
->second
;
2259 ATLTRACE("verb : %ws\n", help
.c_str());
2260 lstrcpynW((LPWSTR
)pszName
, help
.c_str(), cchMax
);
2269 STDMETHODIMP
CShellExt::HandleMenuMsg(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
2272 return HandleMenuMsg2(uMsg
, wParam
, lParam
, &res
);
2275 STDMETHODIMP
CShellExt::HandleMenuMsg2(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*pResult
)
2277 PreserveChdir preserveChdir
;
2280 if (pResult
== NULL
)
2287 case WM_MEASUREITEM
:
2289 MEASUREITEMSTRUCT
* lpmis
= (MEASUREITEMSTRUCT
*)lParam
;
2290 if (lpmis
==NULL
||lpmis
->CtlType
!=ODT_MENU
)
2292 lpmis
->itemWidth
+= 2;
2293 if (lpmis
->itemHeight
< 16)
2294 lpmis
->itemHeight
= 16;
2301 DRAWITEMSTRUCT
* lpdis
= (DRAWITEMSTRUCT
*)lParam
;
2302 if ((lpdis
==NULL
)||(lpdis
->CtlType
!= ODT_MENU
))
2303 return S_OK
; //not for a menu
2304 resource
= GetMenuTextFromResource(myIDMap
[lpdis
->itemID
]);
2305 if (resource
== NULL
)
2307 HICON hIcon
= (HICON
)LoadImage(g_hResInst
, resource
, IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
2310 DrawIconEx(lpdis
->hDC
,
2311 lpdis
->rcItem
.left
- 16,
2312 lpdis
->rcItem
.top
+ (lpdis
->rcItem
.bottom
- lpdis
->rcItem
.top
- 16) / 2,
2314 0, NULL
, DI_NORMAL
);
2323 if (HIWORD(wParam
) != MF_POPUP
)
2325 int nChar
= LOWORD(wParam
);
2326 if (_istascii((wint_t)nChar
) && _istupper((wint_t)nChar
))
2327 nChar
= tolower(nChar
);
2328 // we have the char the user pressed, now search that char in all our
2330 std::vector
<int> accmenus
;
2331 for (std::map
<UINT_PTR
, UINT_PTR
>::iterator It
= mySubMenuMap
.begin(); It
!= mySubMenuMap
.end(); ++It
)
2333 resource
= GetMenuTextFromResource(mySubMenuMap
[It
->first
]);
2334 if (resource
== NULL
)
2336 szItem
= stringtablebuffer
;
2337 TCHAR
* amp
= _tcschr(szItem
, '&');
2341 int ampChar
= LOWORD(*amp
);
2342 if (_istascii((wint_t)ampChar
) && _istupper((wint_t)ampChar
))
2343 ampChar
= tolower(ampChar
);
2344 if (ampChar
== nChar
)
2346 // yep, we found a menu which has the pressed key
2347 // as an accelerator. Add that menu to the list to
2349 accmenus
.push_back(It
->first
);
2352 if (accmenus
.size() == 0)
2354 // no menu with that accelerator key.
2355 *pResult
= MAKELONG(0, MNC_IGNORE
);
2358 if (accmenus
.size() == 1)
2360 // Only one menu with that accelerator key. We're lucky!
2361 // So just execute that menu entry.
2362 *pResult
= MAKELONG(accmenus
[0], MNC_EXECUTE
);
2365 if (accmenus
.size() > 1)
2367 // we have more than one menu item with this accelerator key!
2369 mif
.cbSize
= sizeof(MENUITEMINFO
);
2370 mif
.fMask
= MIIM_STATE
;
2371 for (std::vector
<int>::iterator it
= accmenus
.begin(); it
!= accmenus
.end(); ++it
)
2373 GetMenuItemInfo((HMENU
)lParam
, *it
, TRUE
, &mif
);
2374 if (mif
.fState
== MFS_HILITE
)
2376 // this is the selected item, so select the next one
2378 if (it
== accmenus
.end())
2379 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
2381 *pResult
= MAKELONG(*it
, MNC_SELECT
);
2385 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
2396 LPCTSTR
CShellExt::GetMenuTextFromResource(int id
)
2399 LPCTSTR resource
= NULL
;
2400 unsigned __int64 layout
= g_ShellCache
.GetMenuLayout();
2404 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
2406 if (menuInfo
[menuIndex
].command
== id
)
2408 MAKESTRING(menuInfo
[menuIndex
].menuTextID
);
2409 resource
= MAKEINTRESOURCE(menuInfo
[menuIndex
].iconID
);
2412 case ShellSubMenuMultiple
:
2413 case ShellSubMenuLink
:
2414 case ShellSubMenuFolder
:
2415 case ShellSubMenuFile
:
2420 space
= layout
& menuInfo
[menuIndex
].menuID
? 0 : 6;
2421 if (layout
& (menuInfo
[menuIndex
].menuID
))
2423 _tcscpy_s(textbuf
, 255, _T("Git "));
2424 _tcscat_s(textbuf
, 255, stringtablebuffer
);
2425 _tcscpy_s(stringtablebuffer
, 255, textbuf
);
2436 bool CShellExt::IsIllegalFolder(std::wstring folder
, int * cslidarray
)
2439 TCHAR buf
[MAX_PATH
]; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
2440 LPITEMIDLIST pidl
= NULL
;
2441 while (cslidarray
[i
])
2445 if (SHGetFolderLocation(NULL
, cslidarray
[i
-1], NULL
, 0, &pidl
)!=S_OK
)
2447 if (!SHGetPathFromIDList(pidl
, buf
))
2449 // not a file system path, definitely illegal for our use
2450 CoTaskMemFree(pidl
);
2453 CoTaskMemFree(pidl
);
2454 if (_tcslen(buf
)==0)
2456 if (_tcscmp(buf
, folder
.c_str())==0)
2462 void CShellExt::InsertIgnoreSubmenus(UINT
&idCmd
, UINT idCmdFirst
, HMENU hMenu
, HMENU subMenu
, UINT
&indexMenu
, int &indexSubMenu
, unsigned __int64 topmenu
, bool bShowIcons
, UINT uFlags
)
2464 HMENU ignoresubmenu
= NULL
;
2465 int indexignoresub
= 0;
2466 bool bShowIgnoreMenu
= false;
2467 TCHAR maskbuf
[MAX_PATH
]; // MAX_PATH is ok, since this only holds a filename
2468 TCHAR ignorepath
[MAX_PATH
]; // MAX_PATH is ok, since this only holds a filename
2469 if (files_
.size() == 0)
2471 UINT icon
= bShowIcons
? IDI_IGNORE
: 0;
2473 std::vector
<stdstring
>::iterator I
= files_
.begin();
2474 if (_tcsrchr(I
->c_str(), '\\'))
2475 _tcscpy_s(ignorepath
, MAX_PATH
, _tcsrchr(I
->c_str(), '\\')+1);
2477 _tcscpy_s(ignorepath
, MAX_PATH
, I
->c_str());
2478 if ((itemStates
& ITEMIS_IGNORED
)&&(ignoredprops
.size() > 0))
2480 // check if the item name is ignored or the mask
2482 while ( (p
=ignoredprops
.find( ignorepath
,p
)) != -1 )
2484 if ( (p
==0 || ignoredprops
[p
-1]==TCHAR('\n'))
2485 && (p
+_tcslen(ignorepath
)==ignoredprops
.length() || ignoredprops
[p
+_tcslen(ignorepath
)+1]==TCHAR('\n')) )
2493 ignoresubmenu
= CreateMenu();
2494 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2495 stdstring verb
= stdstring(ignorepath
);
2496 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2497 myVerbsMap
[verb
] = idCmd
;
2498 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2499 myVerbsIDMap
[idCmd
] = verb
;
2500 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnore
;
2501 myIDMap
[idCmd
++] = ShellMenuUnIgnore
;
2502 bShowIgnoreMenu
= true;
2504 _tcscpy_s(maskbuf
, MAX_PATH
, _T("*"));
2505 if (_tcsrchr(ignorepath
, '.'))
2507 _tcscat_s(maskbuf
, MAX_PATH
, _tcsrchr(ignorepath
, '.'));
2508 p
= ignoredprops
.find(maskbuf
);
2510 ((ignoredprops
.compare(maskbuf
)==0) || (ignoredprops
.find('\n', p
)==p
+_tcslen(maskbuf
)+1) || (ignoredprops
.rfind('\n', p
)==p
-1)))
2512 if (ignoresubmenu
==NULL
)
2513 ignoresubmenu
= CreateMenu();
2515 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2516 stdstring verb
= stdstring(maskbuf
);
2517 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2518 myVerbsMap
[verb
] = idCmd
;
2519 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2520 myVerbsIDMap
[idCmd
] = verb
;
2521 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreCaseSensitive
;
2522 myIDMap
[idCmd
++] = ShellMenuUnIgnoreCaseSensitive
;
2523 bShowIgnoreMenu
= true;
2527 else if ((itemStates
& ITEMIS_IGNORED
) == 0)
2529 bShowIgnoreMenu
= true;
2530 ignoresubmenu
= CreateMenu();
2531 if (itemStates
& ITEMIS_ONLYONE
)
2533 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2534 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
2535 myIDMap
[idCmd
++] = ShellMenuIgnore
;
2537 _tcscpy_s(maskbuf
, MAX_PATH
, _T("*"));
2538 if (_tcsrchr(ignorepath
, '.'))
2540 _tcscat_s(maskbuf
, MAX_PATH
, _tcsrchr(ignorepath
, '.'));
2541 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2542 stdstring verb
= stdstring(maskbuf
);
2543 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2544 myVerbsMap
[verb
] = idCmd
;
2545 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2546 myVerbsIDMap
[idCmd
] = verb
;
2547 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2548 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2553 MAKESTRING(IDS_MENUIGNOREMULTIPLE
);
2554 _stprintf_s(ignorepath
, MAX_PATH
, stringtablebuffer
, files_
.size());
2555 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2556 stdstring verb
= stdstring(ignorepath
);
2557 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2558 myVerbsMap
[verb
] = idCmd
;
2559 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2560 myVerbsIDMap
[idCmd
] = verb
;
2561 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
2562 myIDMap
[idCmd
++] = ShellMenuIgnore
;
2564 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK
);
2565 _stprintf_s(ignorepath
, MAX_PATH
, stringtablebuffer
, files_
.size());
2566 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2567 verb
= stdstring(ignorepath
);
2568 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2569 myVerbsMap
[verb
] = idCmd
;
2570 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2571 myVerbsIDMap
[idCmd
] = verb
;
2572 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2573 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2577 if (bShowIgnoreMenu
)
2579 MENUITEMINFO menuiteminfo
;
2580 SecureZeroMemory(&menuiteminfo
, sizeof(menuiteminfo
));
2581 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
2582 if (fullver
< 0x500 || (fullver
== 0x500 && !(uFlags
&~(CMF_RESERVED
|CMF_EXPLORE
|CMF_EXTENDEDVERBS
))))
2584 menuiteminfo
.fMask
= MIIM_STRING
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
;
2587 HBITMAP bmp
= IconToBitmap(icon
);
2588 menuiteminfo
.fMask
|= MIIM_CHECKMARKS
;
2589 menuiteminfo
.hbmpChecked
= bmp
;
2590 menuiteminfo
.hbmpUnchecked
= bmp
;
2595 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
2598 menuiteminfo
.fMask
|= MIIM_BITMAP
;
2599 menuiteminfo
.hbmpItem
= (fullver
>= 0x600) ? IconToBitmapPARGB32(icon
) : HBMMENU_CALLBACK
;
2602 menuiteminfo
.fType
= MFT_STRING
;
2603 menuiteminfo
.hSubMenu
= ignoresubmenu
;
2604 menuiteminfo
.wID
= idCmd
;
2605 SecureZeroMemory(stringtablebuffer
, sizeof(stringtablebuffer
));
2606 if (itemStates
& ITEMIS_IGNORED
)
2607 GetMenuTextFromResource(ShellMenuUnIgnoreSub
);
2609 GetMenuTextFromResource(ShellMenuIgnoreSub
);
2610 menuiteminfo
.dwTypeData
= stringtablebuffer
;
2611 menuiteminfo
.cch
= (UINT
)min(_tcslen(menuiteminfo
.dwTypeData
), UINT_MAX
);
2613 InsertMenuItem((topmenu
& MENUIGNORE
) ? hMenu
: subMenu
, (topmenu
& MENUIGNORE
) ? indexMenu
++ : indexSubMenu
++, TRUE
, &menuiteminfo
);
2614 if (itemStates
& ITEMIS_IGNORED
)
2616 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreSub
;
2617 myIDMap
[idCmd
++] = ShellMenuUnIgnoreSub
;
2621 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreSub
;
2622 myIDMap
[idCmd
++] = ShellMenuIgnoreSub
;
2627 HBITMAP
CShellExt::IconToBitmapPARGB32(UINT uIcon
)
2629 std::map
<UINT
, HBITMAP
>::iterator bitmap_it
= bitmaps
.lower_bound(uIcon
);
2630 if (bitmap_it
!= bitmaps
.end() && bitmap_it
->first
== uIcon
)
2631 return bitmap_it
->second
;
2633 HICON hIcon
= (HICON
)LoadImage(g_hResInst
, MAKEINTRESOURCE(uIcon
), IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
2637 if (pfnBeginBufferedPaint
== NULL
|| pfnEndBufferedPaint
== NULL
|| pfnGetBufferedPaintBits
== NULL
)
2641 sizIcon
.cx
= GetSystemMetrics(SM_CXSMICON
);
2642 sizIcon
.cy
= GetSystemMetrics(SM_CYSMICON
);
2645 SetRect(&rcIcon
, 0, 0, sizIcon
.cx
, sizIcon
.cy
);
2646 HBITMAP hBmp
= NULL
;
2648 HDC hdcDest
= CreateCompatibleDC(NULL
);
2651 if (SUCCEEDED(Create32BitHBITMAP(hdcDest
, &sizIcon
, NULL
, &hBmp
)))
2653 HBITMAP hbmpOld
= (HBITMAP
)SelectObject(hdcDest
, hBmp
);
2656 BLENDFUNCTION bfAlpha
= { AC_SRC_OVER
, 0, 255, AC_SRC_ALPHA
};
2657 BP_PAINTPARAMS paintParams
= {0};
2658 paintParams
.cbSize
= sizeof(paintParams
);
2659 paintParams
.dwFlags
= BPPF_ERASE
;
2660 paintParams
.pBlendFunction
= &bfAlpha
;
2663 HPAINTBUFFER hPaintBuffer
= pfnBeginBufferedPaint(hdcDest
, &rcIcon
, BPBF_DIB
, &paintParams
, &hdcBuffer
);
2666 if (DrawIconEx(hdcBuffer
, 0, 0, hIcon
, sizIcon
.cx
, sizIcon
.cy
, 0, NULL
, DI_NORMAL
))
2668 // If icon did not have an alpha channel we need to convert buffer to PARGB
2669 ConvertBufferToPARGB32(hPaintBuffer
, hdcDest
, hIcon
, sizIcon
);
2672 // This will write the buffer contents to the destination bitmap
2673 pfnEndBufferedPaint(hPaintBuffer
, TRUE
);
2676 SelectObject(hdcDest
, hbmpOld
);
2686 bitmaps
.insert(bitmap_it
, std::make_pair(uIcon
, hBmp
));
2690 HRESULT
CShellExt::Create32BitHBITMAP(HDC hdc
, const SIZE
*psize
, __deref_opt_out
void **ppvBits
, __out HBITMAP
* phBmp
)
2695 ZeroMemory(&bmi
, sizeof(bmi
));
2696 bmi
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
2697 bmi
.bmiHeader
.biPlanes
= 1;
2698 bmi
.bmiHeader
.biCompression
= BI_RGB
;
2700 bmi
.bmiHeader
.biWidth
= psize
->cx
;
2701 bmi
.bmiHeader
.biHeight
= psize
->cy
;
2702 bmi
.bmiHeader
.biBitCount
= 32;
2704 HDC hdcUsed
= hdc
? hdc
: GetDC(NULL
);
2707 *phBmp
= CreateDIBSection(hdcUsed
, &bmi
, DIB_RGB_COLORS
, ppvBits
, NULL
, 0);
2710 ReleaseDC(NULL
, hdcUsed
);
2713 return (NULL
== *phBmp
) ? E_OUTOFMEMORY
: S_OK
;
2716 HRESULT
CShellExt::ConvertBufferToPARGB32(HPAINTBUFFER hPaintBuffer
, HDC hdc
, HICON hicon
, SIZE
& sizIcon
)
2720 HRESULT hr
= pfnGetBufferedPaintBits(hPaintBuffer
, &prgbQuad
, &cxRow
);
2723 ARGB
*pargb
= reinterpret_cast<ARGB
*>(prgbQuad
);
2724 if (!HasAlpha(pargb
, sizIcon
, cxRow
))
2727 if (GetIconInfo(hicon
, &info
))
2731 hr
= ConvertToPARGB32(hdc
, pargb
, info
.hbmMask
, sizIcon
, cxRow
);
2734 DeleteObject(info
.hbmColor
);
2735 DeleteObject(info
.hbmMask
);
2743 bool CShellExt::HasAlpha(__in ARGB
*pargb
, SIZE
& sizImage
, int cxRow
)
2745 ULONG cxDelta
= cxRow
- sizImage
.cx
;
2746 for (ULONG y
= sizImage
.cy
; y
; --y
)
2748 for (ULONG x
= sizImage
.cx
; x
; --x
)
2750 if (*pargb
++ & 0xFF000000)
2762 HRESULT
CShellExt::ConvertToPARGB32(HDC hdc
, __inout ARGB
*pargb
, HBITMAP hbmp
, SIZE
& sizImage
, int cxRow
)
2765 ZeroMemory(&bmi
, sizeof(bmi
));
2766 bmi
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
2767 bmi
.bmiHeader
.biPlanes
= 1;
2768 bmi
.bmiHeader
.biCompression
= BI_RGB
;
2770 bmi
.bmiHeader
.biWidth
= sizImage
.cx
;
2771 bmi
.bmiHeader
.biHeight
= sizImage
.cy
;
2772 bmi
.bmiHeader
.biBitCount
= 32;
2774 HRESULT hr
= E_OUTOFMEMORY
;
2775 HANDLE hHeap
= GetProcessHeap();
2776 void *pvBits
= HeapAlloc(hHeap
, 0, bmi
.bmiHeader
.biWidth
* 4 * bmi
.bmiHeader
.biHeight
);
2780 if (GetDIBits(hdc
, hbmp
, 0, bmi
.bmiHeader
.biHeight
, pvBits
, &bmi
, DIB_RGB_COLORS
) == bmi
.bmiHeader
.biHeight
)
2782 ULONG cxDelta
= cxRow
- bmi
.bmiHeader
.biWidth
;
2783 ARGB
*pargbMask
= static_cast<ARGB
*>(pvBits
);
2785 for (ULONG y
= bmi
.bmiHeader
.biHeight
; y
; --y
)
2787 for (ULONG x
= bmi
.bmiHeader
.biWidth
; x
; --x
)
2791 // transparent pixel
2797 *pargb
++ |= 0xFF000000;
2807 HeapFree(hHeap
, 0, pvBits
);