root / trunk / Pithos.ShellExtensions / Menus / FileContextMenu.cs @ b9f5b594
History | View | Annotate | Download (27.2 kB)
1 |
// ----------------------------------------------------------------------- |
---|---|
2 |
// <copyright file="FileContextMenu.cs" company="GRNET"> |
3 |
// Copyright 2011 GRNET S.A. All rights reserved. |
4 |
// |
5 |
// Redistribution and use in source and binary forms, with or |
6 |
// without modification, are permitted provided that the following |
7 |
// conditions are met: |
8 |
// |
9 |
// 1. Redistributions of source code must retain the above |
10 |
// copyright notice, this list of conditions and the following |
11 |
// disclaimer. |
12 |
// |
13 |
// 2. Redistributions in binary form must reproduce the above |
14 |
// copyright notice, this list of conditions and the following |
15 |
// disclaimer in the documentation and/or other materials |
16 |
// provided with the distribution. |
17 |
// |
18 |
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
19 |
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
20 |
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
21 |
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
22 |
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
23 |
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
24 |
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
25 |
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
26 |
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
27 |
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
28 |
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
29 |
// POSSIBILITY OF SUCH DAMAGE. |
30 |
// |
31 |
// The views and conclusions contained in the software and |
32 |
// documentation are those of the authors and should not be |
33 |
// interpreted as representing official policies, either expressed |
34 |
// or implied, of GRNET S.A. |
35 |
// </copyright> |
36 |
// ----------------------------------------------------------------------- |
37 |
|
38 |
using System; |
39 |
using System.Collections.Generic; |
40 |
using System.ComponentModel.Composition; |
41 |
using System.Diagnostics; |
42 |
using System.Diagnostics.Contracts; |
43 |
using System.Drawing; |
44 |
using System.Linq; |
45 |
using System.Runtime.InteropServices; |
46 |
using System.Runtime.InteropServices.ComTypes; |
47 |
using System.Text; |
48 |
using System.Threading.Tasks; |
49 |
using Pithos.ShellExtensions.Properties; |
50 |
|
51 |
namespace Pithos.ShellExtensions.Menus |
52 |
{ |
53 |
[ClassInterface(ClassInterfaceType.None)] |
54 |
[Guid("B1F1405D-94A1-4692-B72F-FC8CAF8B8700"), ComVisible(true)] |
55 |
public class FileContextMenu : IShellExtInit, IContextMenu |
56 |
{ |
57 |
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger("Pithos.FileContextMenu"); |
58 |
|
59 |
private const string MenuHandlername = "Pithos.FileContextMenu"; |
60 |
|
61 |
|
62 |
private readonly Dictionary<string, MenuItem> _items; |
63 |
|
64 |
|
65 |
[Import] |
66 |
public FileContext Context { get; set; } |
67 |
|
68 |
private IntPtr _gotoBitmap=IntPtr.Zero; |
69 |
private IntPtr _versionBitmap = IntPtr.Zero; |
70 |
private IntPtr _propertiesBitmap = IntPtr.Zero; |
71 |
|
72 |
public FileContextMenu() |
73 |
{ |
74 |
_gotoBitmap = GetBitmapPtr(Resources.MenuGoToPithos); |
75 |
_versionBitmap = GetBitmapPtr(Resources.MenuHistory); |
76 |
_propertiesBitmap = GetBitmapPtr(Resources.MenuProperties); |
77 |
|
78 |
|
79 |
|
80 |
|
81 |
_items = new Dictionary<string, MenuItem>{ |
82 |
{"gotoPithos",new MenuItem{ |
83 |
MenuText = "&Go to Pithos", |
84 |
Verb = "gotoPithos", |
85 |
VerbCanonicalName = "PITHOSGoTo", |
86 |
VerbHelpText = "Go to Pithos", |
87 |
MenuDisplayId = 1, |
88 |
MenuCommand=OnGotoPithos, |
89 |
DisplayFlags=DisplayFlags.All, |
90 |
MenuBitmap = _gotoBitmap |
91 |
}}, |
92 |
{"showProperties",new MenuItem{ |
93 |
MenuText = "&Pithos Properties", |
94 |
Verb = "showProperties", |
95 |
VerbCanonicalName = "PITHOSProperties", |
96 |
VerbHelpText = "Pithos Properties", |
97 |
MenuDisplayId = 2, |
98 |
MenuCommand=OnShowProperties, |
99 |
DisplayFlags=DisplayFlags.All, |
100 |
MenuBitmap = _propertiesBitmap |
101 |
}}/*, |
102 |
{"prevVersions",new MenuItem{ |
103 |
MenuText = "&Show Previous Versions", |
104 |
Verb = "prevVersions", |
105 |
VerbCanonicalName = "PITHOSPrevVersions", |
106 |
VerbHelpText = "Go to Pithos and display previous versions", |
107 |
MenuDisplayId = 3, |
108 |
MenuCommand=OnVerbDisplayFileName, |
109 |
DisplayFlags=DisplayFlags.File, |
110 |
MenuBitmap=_versionBitmap |
111 |
}}*/ |
112 |
}; |
113 |
|
114 |
IoC.Current.Compose(this); |
115 |
|
116 |
|
117 |
} |
118 |
|
119 |
|
120 |
~FileContextMenu() |
121 |
{ |
122 |
if (_gotoBitmap != IntPtr.Zero) |
123 |
{ |
124 |
NativeMethods.DeleteObject(_gotoBitmap); |
125 |
_gotoBitmap= IntPtr.Zero; |
126 |
} |
127 |
if (_versionBitmap != IntPtr.Zero) |
128 |
{ |
129 |
NativeMethods.DeleteObject(_versionBitmap); |
130 |
_versionBitmap = IntPtr.Zero; |
131 |
} |
132 |
if (_propertiesBitmap != IntPtr.Zero) |
133 |
{ |
134 |
NativeMethods.DeleteObject(_propertiesBitmap); |
135 |
_propertiesBitmap = IntPtr.Zero; |
136 |
} |
137 |
|
138 |
} |
139 |
private static IntPtr GetBitmapPtr(Bitmap gotoBitmap) |
140 |
{ |
141 |
gotoBitmap.MakeTransparent(gotoBitmap.GetPixel(0, 0)); |
142 |
var hbitmap = gotoBitmap.GetHbitmap(); |
143 |
return hbitmap; |
144 |
} |
145 |
|
146 |
void OnShowProperties(IntPtr hWnd) |
147 |
{ |
148 |
var filePath = Context.CurrentFile ?? Context.CurrentFolder; |
149 |
if (String.IsNullOrWhiteSpace(filePath)) |
150 |
{ |
151 |
Debug.WriteLine("No current file or folder"); |
152 |
return; |
153 |
} |
154 |
else |
155 |
{ |
156 |
Debug.WriteLine("Will display properties for {0}",filePath); |
157 |
} |
158 |
|
159 |
var client = PithosHost.GetCommandsClient(); |
160 |
client.BeginShowProperties(Context.CurrentFile,c=> |
161 |
{ |
162 |
try |
163 |
{ |
164 |
c.AsyncWaitHandle.WaitOne(); |
165 |
client.Close(); |
166 |
} |
167 |
catch (Exception exc) |
168 |
{ |
169 |
Trace.WriteLine(exc.ToString()); |
170 |
} |
171 |
},null); |
172 |
|
173 |
} |
174 |
|
175 |
void OnVerbDisplayFileName(IntPtr hWnd) |
176 |
{ |
177 |
string message = String.Format("The selected file is {0}\r\n\r\nThe selected Path is {1}", |
178 |
Context.CurrentFile, |
179 |
Context.CurrentFolder); |
180 |
|
181 |
System.Windows.Forms.MessageBox.Show( |
182 |
message, |
183 |
"Pithos Shell Extensions"); |
184 |
NativeMethods.SHChangeNotify(HChangeNotifyEventID.SHCNE_ASSOCCHANGED, HChangeNotifyFlags.SHCNF_IDLIST, |
185 |
IntPtr.Zero, IntPtr.Zero); |
186 |
} |
187 |
|
188 |
void OnGotoPithos(IntPtr hWnd) |
189 |
{ |
190 |
var client = PithosHost.GetCommandsClient(); |
191 |
|
192 |
client.BeginGotoSite(Context.CurrentFile, c => |
193 |
{ |
194 |
try |
195 |
{ |
196 |
c.AsyncWaitHandle.WaitOne(); |
197 |
client.Close(); |
198 |
} |
199 |
catch (Exception exc) |
200 |
{ |
201 |
Trace.WriteLine(exc.ToString()); |
202 |
} |
203 |
}, null); |
204 |
|
205 |
|
206 |
//TODO: Move this operation to the application. This is a slow process that freeze Windows Explorer |
207 |
/* |
208 |
var settings = Context.Settings; |
209 |
var activeAccount = settings.Accounts.FirstOrDefault(acc => Context.CurrentFile.StartsWith(acc.RootPath,StringComparison.InvariantCultureIgnoreCase)); |
210 |
var address = String.Format("{0}/ui/?token={1}&user={2}", |
211 |
activeAccount.ServerUrl, |
212 |
activeAccount.ApiKey, |
213 |
Uri.EscapeUriString(activeAccount.AccountName)); |
214 |
|
215 |
settings.Reload(); |
216 |
Process.Start(address); |
217 |
*/ |
218 |
} |
219 |
|
220 |
|
221 |
#region Shell Extension Registration |
222 |
|
223 |
[ComRegisterFunction] |
224 |
public static void Register(Type t) |
225 |
{ |
226 |
try |
227 |
{ |
228 |
ShellExtReg.RegisterShellExtContextMenuHandler(t.GUID, ".cs", |
229 |
MenuHandlername); |
230 |
ShellExtReg.RegisterShellExtContextMenuHandler(t.GUID, "Directory", |
231 |
MenuHandlername); |
232 |
ShellExtReg.RegisterShellExtContextMenuHandler(t.GUID, @"Directory\Background", |
233 |
MenuHandlername); |
234 |
ShellExtReg.RegisterShellExtContextMenuHandler(t.GUID, "*", |
235 |
MenuHandlername); |
236 |
|
237 |
//ShellExtReg.MarkApproved(t.GUID, MenuHandlername); |
238 |
} |
239 |
catch (Exception ex) |
240 |
{ |
241 |
Console.WriteLine(ex.Message); // Log the error |
242 |
throw; // Re-throw the exception |
243 |
} |
244 |
} |
245 |
|
246 |
[ComUnregisterFunction] |
247 |
public static void Unregister(Type t) |
248 |
{ |
249 |
try |
250 |
{ |
251 |
ShellExtReg.UnregisterShellExtContextMenuHandler(t.GUID, ".cs", MenuHandlername); |
252 |
ShellExtReg.UnregisterShellExtContextMenuHandler(t.GUID, "Directory", MenuHandlername); |
253 |
ShellExtReg.UnregisterShellExtContextMenuHandler(t.GUID, @"Directory\Background", MenuHandlername); |
254 |
ShellExtReg.UnregisterShellExtContextMenuHandler(t.GUID, "*", MenuHandlername); |
255 |
|
256 |
//ShellExtReg.RemoveApproved(t.GUID, MenuHandlername); |
257 |
} |
258 |
catch (Exception ex) |
259 |
{ |
260 |
Console.WriteLine(ex.Message); // Log the error |
261 |
throw; // Re-throw the exception |
262 |
} |
263 |
} |
264 |
|
265 |
#endregion |
266 |
|
267 |
|
268 |
#region IShellExtInit Members |
269 |
|
270 |
/// <summary> |
271 |
/// Initialize the context menu handler. |
272 |
/// </summary> |
273 |
/// <param name="pidlFolder"> |
274 |
/// A pointer to an ITEMIDLIST structure that uniquely identifies a folder. |
275 |
/// </param> |
276 |
/// <param name="pDataObj"> |
277 |
/// A pointer to an IDataObject interface object that can be used to retrieve |
278 |
/// the objects being acted upon. |
279 |
/// </param> |
280 |
/// <param name="hKeyProgID"> |
281 |
/// The registry key for the file object or folder type. |
282 |
/// </param> |
283 |
public void Initialize(IntPtr pidlFolder, IntPtr pDataObj, IntPtr hKeyProgID) |
284 |
{ |
285 |
|
286 |
if(pDataObj == IntPtr.Zero && pidlFolder == IntPtr.Zero) |
287 |
{ |
288 |
throw new ArgumentException("pidlFolder and pDataObj shouldn't be null at the same time"); |
289 |
} |
290 |
|
291 |
|
292 |
Debug.WriteLine("Initializing", LogCategories.ShellMenu); |
293 |
|
294 |
if (pDataObj != IntPtr.Zero) |
295 |
{ |
296 |
Debug.WriteLine("Got a data object", LogCategories.ShellMenu); |
297 |
|
298 |
FORMATETC fe = new FORMATETC(); |
299 |
fe.cfFormat = (short)CLIPFORMAT.CF_HDROP; |
300 |
fe.ptd = IntPtr.Zero; |
301 |
fe.dwAspect = DVASPECT.DVASPECT_CONTENT; |
302 |
fe.lindex = -1; |
303 |
fe.tymed = TYMED.TYMED_HGLOBAL; |
304 |
STGMEDIUM stm = new STGMEDIUM(); |
305 |
|
306 |
// The pDataObj pointer contains the objects being acted upon. In this |
307 |
// example, we get an HDROP handle for enumerating the selected files |
308 |
// and folders. |
309 |
IDataObject dataObject = (IDataObject)Marshal.GetObjectForIUnknown(pDataObj); |
310 |
dataObject.GetData(ref fe, out stm); |
311 |
|
312 |
try |
313 |
{ |
314 |
// Get an HDROP handle. |
315 |
IntPtr hDrop = stm.unionmember; |
316 |
if (hDrop == IntPtr.Zero) |
317 |
{ |
318 |
throw new ArgumentException(); |
319 |
} |
320 |
|
321 |
// Determine how many files are involved in this operation. |
322 |
uint nFiles = NativeMethods.DragQueryFile(hDrop, UInt32.MaxValue, null, 0); |
323 |
|
324 |
Debug.WriteLine(String.Format("Got {0} files", nFiles), LogCategories.ShellMenu); |
325 |
// This code sample displays the custom context menu item when only |
326 |
// one file is selected. |
327 |
if (nFiles == 1) |
328 |
{ |
329 |
// Get the path of the file. |
330 |
var fileName = new StringBuilder(260); |
331 |
if (0 == NativeMethods.DragQueryFile(hDrop, 0, fileName, |
332 |
fileName.Capacity)) |
333 |
{ |
334 |
Marshal.ThrowExceptionForHR(WinError.E_FAIL); |
335 |
} |
336 |
Context.CurrentFile = fileName.ToString(); |
337 |
} |
338 |
/* else |
339 |
{ |
340 |
Marshal.ThrowExceptionForHR(WinError.E_FAIL); |
341 |
}*/ |
342 |
|
343 |
// [-or-] |
344 |
|
345 |
// Enumerate the selected files and folders. |
346 |
//if (nFiles > 0) |
347 |
//{ |
348 |
// StringCollection selectedFiles = new StringCollection(); |
349 |
// StringBuilder fileName = new StringBuilder(260); |
350 |
// for (uint i = 0; i < nFiles; i++) |
351 |
// { |
352 |
// // Get the next file name. |
353 |
// if (0 != NativeMethods.DragQueryFile(hDrop, i, fileName, |
354 |
// fileName.Capacity)) |
355 |
// { |
356 |
// // Add the file name to the list. |
357 |
// selectedFiles.Add(fileName.ToString()); |
358 |
// } |
359 |
// } |
360 |
// |
361 |
// // If we did not find any files we can work with, throw |
362 |
// // exception. |
363 |
// if (selectedFiles.Count == 0) |
364 |
// { |
365 |
// Marshal.ThrowExceptionForHR(WinError.E_FAIL); |
366 |
// } |
367 |
//} |
368 |
//else |
369 |
//{ |
370 |
// Marshal.ThrowExceptionForHR(WinError.E_FAIL); |
371 |
//} |
372 |
} |
373 |
finally |
374 |
{ |
375 |
NativeMethods.ReleaseStgMedium(ref stm); |
376 |
} |
377 |
} |
378 |
|
379 |
if (pidlFolder != IntPtr.Zero) |
380 |
{ |
381 |
Debug.WriteLine("Got a folder", LogCategories.ShellMenu); |
382 |
StringBuilder path = new StringBuilder(); |
383 |
if (!NativeMethods.SHGetPathFromIDList(pidlFolder, path)) |
384 |
{ |
385 |
int error = Marshal.GetHRForLastWin32Error(); |
386 |
Marshal.ThrowExceptionForHR(error); |
387 |
} |
388 |
Context.CurrentFolder = path.ToString(); |
389 |
Debug.WriteLine(String.Format("Folder is {0}", Context.CurrentFolder), LogCategories.ShellMenu); |
390 |
} |
391 |
} |
392 |
|
393 |
#endregion |
394 |
|
395 |
|
396 |
#region IContextMenu Members |
397 |
|
398 |
/// <summary> |
399 |
/// Add commands to a shortcut menu. |
400 |
/// </summary> |
401 |
/// <param name="hMenu">A handle to the shortcut menu.</param> |
402 |
/// <param name="iMenu"> |
403 |
/// The zero-based position at which to insert the first new menu item. |
404 |
/// </param> |
405 |
/// <param name="idCmdFirst"> |
406 |
/// The minimum value that the handler can specify for a menu item ID. |
407 |
/// </param> |
408 |
/// <param name="idCmdLast"> |
409 |
/// The maximum value that the handler can specify for a menu item ID. |
410 |
/// </param> |
411 |
/// <param name="uFlags"> |
412 |
/// Optional flags that specify how the shortcut menu can be changed. |
413 |
/// </param> |
414 |
/// <returns> |
415 |
/// If successful, returns an HRESULT value that has its severity value set |
416 |
/// to SEVERITY_SUCCESS and its code value set to the offset of the largest |
417 |
/// command identifier that was assigned, plus one. |
418 |
/// </returns> |
419 |
public int QueryContextMenu( |
420 |
IntPtr hMenu, |
421 |
uint iMenu, |
422 |
uint idCmdFirst, |
423 |
uint idCmdLast, |
424 |
uint uFlags) |
425 |
{ |
426 |
Debug.WriteLine("Start qcm", LogCategories.ShellMenu); |
427 |
// If uFlags include CMF_DEFAULTONLY then we should not do anything. |
428 |
Debug.WriteLine(String.Format("Flags {0}", uFlags), LogCategories.ShellMenu); |
429 |
|
430 |
if (((uint)CMF.CMF_DEFAULTONLY & uFlags) != 0) |
431 |
{ |
432 |
Debug.WriteLine("Default only flag, returning", LogCategories.ShellMenu); |
433 |
return WinError.MAKE_HRESULT(WinError.SEVERITY_SUCCESS, 0, 0); |
434 |
} |
435 |
|
436 |
if (!Context.IsManaged) |
437 |
{ |
438 |
Debug.WriteLine("Not a PITHOS folder",LogCategories.ShellMenu); |
439 |
return WinError.MAKE_HRESULT(WinError.SEVERITY_SUCCESS, 0, 0); |
440 |
} |
441 |
|
442 |
/* |
443 |
if (!selectedFolder.ToLower().Contains("pithos")) |
444 |
return WinError.MAKE_HRESULT(WinError.SEVERITY_SUCCESS, 0, 0); |
445 |
*/ |
446 |
|
447 |
// Use either InsertMenu or InsertMenuItem to add menu items. |
448 |
|
449 |
uint largestID = 0; |
450 |
|
451 |
DisplayFlags itemType = (Context.IsFolder) ? DisplayFlags.Folder : DisplayFlags.File; |
452 |
|
453 |
Debug.WriteLine(String.Format("Item Flags {0}", itemType), LogCategories.ShellMenu); |
454 |
|
455 |
if (!NativeMethods.InsertMenu(hMenu, idCmdFirst, MF.MF_SEPARATOR | MF.MF_BYPOSITION, 0, String.Empty)) |
456 |
{ |
457 |
Log.ErrorFormat("Error adding separator 1\r\n{0}", Marshal.GetLastWin32Error()); |
458 |
return Marshal.GetHRForLastWin32Error(); |
459 |
} |
460 |
|
461 |
foreach (var menuItem in _items.Values) |
462 |
{ |
463 |
Debug.WriteLine(String.Format("Menu Flags {0}", menuItem.DisplayFlags), LogCategories.ShellMenu); |
464 |
if ((itemType & menuItem.DisplayFlags) != DisplayFlags.None) |
465 |
{ |
466 |
Debug.WriteLine("Adding Menu", LogCategories.ShellMenu); |
467 |
|
468 |
MENUITEMINFO mii = menuItem.CreateInfo(idCmdFirst); |
469 |
if (!NativeMethods.InsertMenuItem(hMenu, iMenu, true, ref mii)) |
470 |
{ |
471 |
var lastError = Marshal.GetLastWin32Error(); |
472 |
var lastErrorHR = Marshal.GetHRForLastWin32Error(); |
473 |
return lastErrorHR; |
474 |
} |
475 |
if (largestID < menuItem.MenuDisplayId) |
476 |
largestID = menuItem.MenuDisplayId; |
477 |
} |
478 |
} |
479 |
|
480 |
Debug.Write("Adding Separator 1", LogCategories.ShellMenu); |
481 |
// Add a separator. |
482 |
/* MENUITEMINFO sep = new MENUITEMINFO(); |
483 |
sep.cbSize = (uint)Marshal.SizeOf(sep); |
484 |
sep.fMask = MIIM.MIIM_TYPE; |
485 |
sep.fType = MFT.MFT_SEPARATOR;*/ |
486 |
if (!NativeMethods.InsertMenu(hMenu, (uint)_items.Values.Count + idCmdFirst+1,MF.MF_SEPARATOR|MF.MF_BYPOSITION, 0, String.Empty)) |
487 |
{ |
488 |
Log.ErrorFormat("Error adding separator 1\r\n{0}", Marshal.GetLastWin32Error()); |
489 |
return Marshal.GetHRForLastWin32Error(); |
490 |
} |
491 |
|
492 |
|
493 |
|
494 |
|
495 |
Debug.WriteLine("Menus added", LogCategories.ShellOverlays); |
496 |
// Return an HRESULT value with the severity set to SEVERITY_SUCCESS. |
497 |
// Set the code value to the offset of the largest command identifier |
498 |
// that was assigned, plus one (1). |
499 |
return WinError.MAKE_HRESULT(WinError.SEVERITY_SUCCESS, 0, |
500 |
largestID + 1); |
501 |
} |
502 |
|
503 |
/// <summary> |
504 |
/// Carry out the command associated with a shortcut menu item. |
505 |
/// </summary> |
506 |
/// <param name="pici"> |
507 |
/// A pointer to a CMINVOKECOMMANDINFO or CMINVOKECOMMANDINFOEX structure |
508 |
/// containing information about the command. |
509 |
/// </param> |
510 |
public void InvokeCommand(IntPtr pici) |
511 |
{ |
512 |
bool isUnicode = false; |
513 |
|
514 |
// Determine which structure is being passed in, CMINVOKECOMMANDINFO or |
515 |
// CMINVOKECOMMANDINFOEX based on the cbSize member of lpcmi. Although |
516 |
// the lpcmi parameter is declared in Shlobj.h as a CMINVOKECOMMANDINFO |
517 |
// structure, in practice it often points to a CMINVOKECOMMANDINFOEX |
518 |
// structure. This struct is an extended version of CMINVOKECOMMANDINFO |
519 |
// and has additional members that allow Unicode strings to be passed. |
520 |
CMINVOKECOMMANDINFO ici = (CMINVOKECOMMANDINFO)Marshal.PtrToStructure( |
521 |
pici, typeof(CMINVOKECOMMANDINFO)); |
522 |
CMINVOKECOMMANDINFOEX iciex = new CMINVOKECOMMANDINFOEX(); |
523 |
if (ici.cbSize == Marshal.SizeOf(typeof(CMINVOKECOMMANDINFOEX))) |
524 |
{ |
525 |
if ((ici.fMask & CMIC.CMIC_MASK_UNICODE) != 0) |
526 |
{ |
527 |
isUnicode = true; |
528 |
iciex = (CMINVOKECOMMANDINFOEX)Marshal.PtrToStructure(pici, |
529 |
typeof(CMINVOKECOMMANDINFOEX)); |
530 |
} |
531 |
} |
532 |
|
533 |
// Determines whether the command is identified by its offset or verb. |
534 |
// There are two ways to identify commands: |
535 |
// |
536 |
// 1) The command's verb string |
537 |
// 2) The command's identifier offset |
538 |
// |
539 |
// If the high-order word of lpcmi->lpVerb (for the ANSI case) or |
540 |
// lpcmi->lpVerbW (for the Unicode case) is nonzero, lpVerb or lpVerbW |
541 |
// holds a verb string. If the high-order word is zero, the command |
542 |
// offset is in the low-order word of lpcmi->lpVerb. |
543 |
|
544 |
// For the ANSI case, if the high-order word is not zero, the command's |
545 |
// verb string is in lpcmi->lpVerb. |
546 |
if (!isUnicode && NativeMethods.HighWord(ici.verb.ToInt32()) != 0) |
547 |
{ |
548 |
// Is the verb supported by this context menu extension? |
549 |
string verbAnsi = Marshal.PtrToStringAnsi(ici.verb); |
550 |
if (_items.ContainsKey(verbAnsi)) |
551 |
{ |
552 |
_items[verbAnsi].MenuCommand(ici.hwnd); |
553 |
} |
554 |
else |
555 |
{ |
556 |
// If the verb is not recognized by the context menu handler, it |
557 |
// must return E_FAIL to allow it to be passed on to the other |
558 |
// context menu handlers that might implement that verb. |
559 |
Marshal.ThrowExceptionForHR(WinError.E_FAIL); |
560 |
} |
561 |
} |
562 |
|
563 |
// For the Unicode case, if the high-order word is not zero, the |
564 |
// command's verb string is in lpcmi->lpVerbW. |
565 |
else if (isUnicode && NativeMethods.HighWord(iciex.verbW.ToInt32()) != 0) |
566 |
{ |
567 |
// Is the verb supported by this context menu extension? |
568 |
string verbUTF = Marshal.PtrToStringUni(iciex.verbW); |
569 |
if (_items.ContainsKey(verbUTF)) |
570 |
{ |
571 |
_items[verbUTF].MenuCommand(ici.hwnd); |
572 |
} |
573 |
else |
574 |
{ |
575 |
// If the verb is not recognized by the context menu handler, it |
576 |
// must return E_FAIL to allow it to be passed on to the other |
577 |
// context menu handlers that might implement that verb. |
578 |
Marshal.ThrowExceptionForHR(WinError.E_FAIL); |
579 |
} |
580 |
} |
581 |
|
582 |
// If the command cannot be identified through the verb string, then |
583 |
// check the identifier offset. |
584 |
else |
585 |
{ |
586 |
// Is the command identifier offset supported by this context menu |
587 |
// extension? |
588 |
int menuID = NativeMethods.LowWord(ici.verb.ToInt32()); |
589 |
var menuItem = _items.FirstOrDefault(item => item.Value.MenuDisplayId == menuID).Value; |
590 |
if (menuItem != null) |
591 |
{ |
592 |
menuItem.MenuCommand(ici.hwnd); |
593 |
} |
594 |
else |
595 |
{ |
596 |
// If the verb is not recognized by the context menu handler, it |
597 |
// must return E_FAIL to allow it to be passed on to the other |
598 |
// context menu handlers that might implement that verb. |
599 |
Marshal.ThrowExceptionForHR(WinError.E_FAIL); |
600 |
} |
601 |
} |
602 |
} |
603 |
|
604 |
/// <summary> |
605 |
/// Get information about a shortcut menu command, including the help string |
606 |
/// and the language-independent, or canonical, name for the command. |
607 |
/// </summary> |
608 |
/// <param name="idCmd">Menu command identifier offset.</param> |
609 |
/// <param name="uFlags"> |
610 |
/// Flags specifying the information to return. This parameter can have one |
611 |
/// of the following values: GCS_HELPTEXTA, GCS_HELPTEXTW, GCS_VALIDATEA, |
612 |
/// GCS_VALIDATEW, GCS_VERBA, GCS_VERBW. |
613 |
/// </param> |
614 |
/// <param name="pReserved">Reserved. Must be IntPtr.Zero</param> |
615 |
/// <param name="pszName"> |
616 |
/// The address of the buffer to receive the null-terminated string being |
617 |
/// retrieved. |
618 |
/// </param> |
619 |
/// <param name="cchMax"> |
620 |
/// Size of the buffer, in characters, to receive the null-terminated string. |
621 |
/// </param> |
622 |
public void GetCommandString( |
623 |
UIntPtr idCmd, |
624 |
uint uFlags, |
625 |
IntPtr pReserved, |
626 |
StringBuilder pszName, |
627 |
uint cchMax) |
628 |
{ |
629 |
uint menuID = idCmd.ToUInt32(); |
630 |
var menuItem = _items.FirstOrDefault(item => item.Value.MenuDisplayId == menuID).Value; |
631 |
if (menuItem != null) |
632 |
{ |
633 |
switch ((GCS)uFlags) |
634 |
{ |
635 |
case GCS.GCS_VERBW: |
636 |
if (menuItem.VerbCanonicalName.Length > cchMax - 1) |
637 |
{ |
638 |
Marshal.ThrowExceptionForHR(WinError.STRSAFE_E_INSUFFICIENT_BUFFER); |
639 |
} |
640 |
else |
641 |
{ |
642 |
pszName.Clear(); |
643 |
pszName.Append(menuItem.VerbCanonicalName); |
644 |
} |
645 |
break; |
646 |
|
647 |
case GCS.GCS_HELPTEXTW: |
648 |
if (menuItem.VerbHelpText.Length > cchMax - 1) |
649 |
{ |
650 |
Marshal.ThrowExceptionForHR(WinError.STRSAFE_E_INSUFFICIENT_BUFFER); |
651 |
} |
652 |
else |
653 |
{ |
654 |
pszName.Clear(); |
655 |
pszName.Append(menuItem.VerbHelpText); |
656 |
} |
657 |
break; |
658 |
} |
659 |
} |
660 |
} |
661 |
|
662 |
#endregion |
663 |
} |
664 |
} |