Statistics
| Branch: | Revision:

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
}