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