Statistics
| Branch: | Revision:

root / trunk / Pithos.ShellExtensions / Menus / FileContextMenu.cs @ 4d301e8e

History | View | Annotate | Download (22.5 kB)

1
using System;
2
using System.Collections.Generic;
3
using System.ComponentModel.Composition;
4
using System.Diagnostics;
5
using System.Diagnostics.Contracts;
6
using System.Drawing;
7
using System.Linq;
8
using System.Runtime.InteropServices;
9
using System.Runtime.InteropServices.ComTypes;
10
using System.Text;
11
using Pithos.ShellExtensions.Properties;
12

    
13
namespace Pithos.ShellExtensions.Menus
14
{
15
    [ClassInterface(ClassInterfaceType.None)]
16
    [Guid("B1F1405D-94A1-4692-B72F-FC8CAF8B8700"), ComVisible(true)]
17
    public class FileContextMenu : IShellExtInit, IContextMenu
18
    {
19
        private const string MenuHandlername = "CSShellExtContextMenuHandler.FileContextMenuExt";
20

    
21

    
22
        private readonly Dictionary<string, MenuItem> _items;
23

    
24

    
25
        [Import]
26
        public FileContext Context { get; set; }
27

    
28
        private IntPtr _gotoBitmap=IntPtr.Zero;
29
        private IntPtr _versionBitmap = IntPtr.Zero;
30

    
31
        public FileContextMenu()
32
        {                        
33
            _gotoBitmap = GetBitmapPtr(Resources.MenuGoToPithos);
34
            _versionBitmap = GetBitmapPtr(Resources.MenuHistory);
35

    
36

    
37
            
38

    
39
            _items = new Dictionary<string, MenuItem>{
40
                {"gotoPithos",new MenuItem{
41
                                           MenuText = "&Go to Pithos",
42
                                            Verb = "gotoPithos",
43
                                             VerbCanonicalName = "PITHOSGoTo",
44
                                              VerbHelpText = "Go to Pithos",
45
                                               MenuDisplayId = 1,
46
                                               MenuCommand=OnGotoPithos,
47
                                               DisplayFlags=DisplayFlags.All,
48
                                               MenuBitmap = _gotoBitmap
49
                                           }},
50
                {"prevVersions",new MenuItem{
51
                                           MenuText = "&Show Previous Versions",
52
                                            Verb = "prevVersions",
53
                                             VerbCanonicalName = "PITHOSPrevVersions",
54
                                              VerbHelpText = "Go to Pithos and display previous versions",
55
                                               MenuDisplayId = 2,
56
                                               MenuCommand=OnVerbDisplayFileName,
57
                                               DisplayFlags=DisplayFlags.File,
58
                                               MenuBitmap=_versionBitmap
59
                                           }}
60
            };
61

    
62
            IoC.Current.Compose(this);
63

    
64

    
65
        }
66

    
67

    
68
        ~FileContextMenu()
69
        {
70
            if (_gotoBitmap != IntPtr.Zero)
71
            {
72
                NativeMethods.DeleteObject(_gotoBitmap);
73
                _gotoBitmap= IntPtr.Zero;
74
            }
75
            if (_versionBitmap != IntPtr.Zero)
76
            {
77
                NativeMethods.DeleteObject(_versionBitmap);
78
                _versionBitmap = IntPtr.Zero;
79
            }
80

    
81
        }
82
        private static IntPtr GetBitmapPtr(Bitmap gotoBitmap)
83
        {
84
            gotoBitmap.MakeTransparent(gotoBitmap.GetPixel(0, 0));
85
            var hbitmap = gotoBitmap.GetHbitmap();
86
            return hbitmap;
87
        }
88

    
89

    
90
        void OnVerbDisplayFileName(IntPtr hWnd)
91
        {
92
            string message = String.Format("The selected file is {0}\r\n\r\nThe selected Path is {1}",
93
                                           Context.CurrentFile,
94
                                           Context.CurrentFolder);
95

    
96
            System.Windows.Forms.MessageBox.Show(
97
                message,
98
                "CSShellExtContextMenuHandler");
99
            NativeMethods.SHChangeNotify(HChangeNotifyEventID.SHCNE_ASSOCCHANGED, HChangeNotifyFlags.SHCNF_IDLIST,
100
                                             IntPtr.Zero, IntPtr.Zero);
101
        }
102

    
103
        void OnGotoPithos(IntPtr hWnd)
104
        {
105
            Context.Settings.Reload();
106
            Process.Start(Context.Settings.PithosSite);
107
        }
108
        
109

    
110
        #region Shell Extension Registration
111

    
112
        [ComRegisterFunction]
113
        public static void Register(Type t)
114
        {
115
            try
116
            {
117
                ShellExtReg.RegisterShellExtContextMenuHandler(t.GUID, ".cs",
118
                    MenuHandlername);
119
                ShellExtReg.RegisterShellExtContextMenuHandler(t.GUID, "Directory",
120
                    MenuHandlername);
121
                ShellExtReg.RegisterShellExtContextMenuHandler(t.GUID, @"Directory\Background",
122
                    MenuHandlername);
123
                ShellExtReg.RegisterShellExtContextMenuHandler(t.GUID, "*",
124
                    MenuHandlername);
125

    
126
                //ShellExtReg.MarkApproved(t.GUID, MenuHandlername);
127
            }
128
            catch (Exception ex)
129
            {
130
                Console.WriteLine(ex.Message); // Log the error
131
                throw;  // Re-throw the exception
132
            }
133
        }
134

    
135
        [ComUnregisterFunction]
136
        public static void Unregister(Type t)
137
        {
138
            try
139
            {
140
                ShellExtReg.UnregisterShellExtContextMenuHandler(t.GUID, ".cs", MenuHandlername);
141
                ShellExtReg.UnregisterShellExtContextMenuHandler(t.GUID, "Directory", MenuHandlername);
142
                ShellExtReg.UnregisterShellExtContextMenuHandler(t.GUID, @"Directory\Background", MenuHandlername);
143
                ShellExtReg.UnregisterShellExtContextMenuHandler(t.GUID, "*", MenuHandlername);
144

    
145
                //ShellExtReg.RemoveApproved(t.GUID, MenuHandlername);
146
            }
147
            catch (Exception ex)
148
            {
149
                Console.WriteLine(ex.Message); // Log the error
150
                throw;  // Re-throw the exception
151
            }
152
        }
153

    
154
        #endregion
155

    
156

    
157
        #region IShellExtInit Members
158

    
159
        /// <summary>
160
        /// Initialize the context menu handler.
161
        /// </summary>
162
        /// <param name="pidlFolder">
163
        /// A pointer to an ITEMIDLIST structure that uniquely identifies a folder.
164
        /// </param>
165
        /// <param name="pDataObj">
166
        /// A pointer to an IDataObject interface object that can be used to retrieve 
167
        /// the objects being acted upon.
168
        /// </param>
169
        /// <param name="hKeyProgID">
170
        /// The registry key for the file object or folder type.
171
        /// </param>
172
        public void Initialize(IntPtr pidlFolder, IntPtr pDataObj, IntPtr hKeyProgID)
173
        {
174
            
175
            if(pDataObj == IntPtr.Zero && pidlFolder == IntPtr.Zero)
176
            {
177
                throw new ArgumentException("pidlFolder and pDataObj shouldn't be null at the same time");
178
            }
179

    
180

    
181
            Debug.WriteLine("Initializing", LogCategories.ShellMenu);
182

    
183
            if (pDataObj != IntPtr.Zero)
184
            {
185
                Debug.WriteLine("Got a data object", LogCategories.ShellMenu);
186

    
187
                FORMATETC fe = new FORMATETC();
188
                fe.cfFormat = (short)CLIPFORMAT.CF_HDROP;
189
                fe.ptd = IntPtr.Zero;
190
                fe.dwAspect = DVASPECT.DVASPECT_CONTENT;
191
                fe.lindex = -1;
192
                fe.tymed = TYMED.TYMED_HGLOBAL;
193
                STGMEDIUM stm = new STGMEDIUM();
194

    
195
                // The pDataObj pointer contains the objects being acted upon. In this 
196
                // example, we get an HDROP handle for enumerating the selected files 
197
                // and folders.
198
                IDataObject dataObject = (IDataObject)Marshal.GetObjectForIUnknown(pDataObj);
199
                dataObject.GetData(ref fe, out stm);
200

    
201
                try
202
                {
203
                    // Get an HDROP handle.
204
                    IntPtr hDrop = stm.unionmember;
205
                    if (hDrop == IntPtr.Zero)
206
                    {
207
                        throw new ArgumentException();
208
                    }
209

    
210
                    // Determine how many files are involved in this operation.
211
                    uint nFiles = NativeMethods.DragQueryFile(hDrop, UInt32.MaxValue, null, 0);
212

    
213
                    Debug.WriteLine(String.Format("Got {0} files", nFiles), LogCategories.ShellMenu);
214
                    // This code sample displays the custom context menu item when only 
215
                    // one file is selected. 
216
                    if (nFiles == 1)
217
                    {
218
                        // Get the path of the file.
219
                        var fileName = new StringBuilder(260);
220
                        if (0 == NativeMethods.DragQueryFile(hDrop, 0, fileName,
221
                                                             fileName.Capacity))
222
                        {
223
                            Marshal.ThrowExceptionForHR(WinError.E_FAIL);
224
                        }
225
                        Context.CurrentFile = fileName.ToString();
226
                    }
227
                    /* else
228
                     {
229
                         Marshal.ThrowExceptionForHR(WinError.E_FAIL);
230
                     }*/
231

    
232
                    // [-or-]
233

    
234
                    // Enumerate the selected files and folders.
235
                    //if (nFiles > 0)
236
                    //{
237
                    //    StringCollection selectedFiles = new StringCollection();
238
                    //    StringBuilder fileName = new StringBuilder(260);
239
                    //    for (uint i = 0; i < nFiles; i++)
240
                    //    {
241
                    //        // Get the next file name.
242
                    //        if (0 != NativeMethods.DragQueryFile(hDrop, i, fileName,
243
                    //            fileName.Capacity))
244
                    //        {
245
                    //            // Add the file name to the list.
246
                    //            selectedFiles.Add(fileName.ToString());
247
                    //        }
248
                    //    }
249
                    //
250
                    //    // If we did not find any files we can work with, throw 
251
                    //    // exception.
252
                    //    if (selectedFiles.Count == 0)
253
                    //    {
254
                    //        Marshal.ThrowExceptionForHR(WinError.E_FAIL);
255
                    //    }
256
                    //}
257
                    //else
258
                    //{
259
                    //    Marshal.ThrowExceptionForHR(WinError.E_FAIL);
260
                    //}
261
                }
262
                finally
263
                {
264
                    NativeMethods.ReleaseStgMedium(ref stm);
265
                }
266
            }
267

    
268
            if (pidlFolder != IntPtr.Zero)
269
            {
270
                Debug.WriteLine("Got a folder", LogCategories.ShellMenu);
271
                StringBuilder path = new StringBuilder();
272
                if (!NativeMethods.SHGetPathFromIDList(pidlFolder, path))
273
                {
274
                    int error = Marshal.GetHRForLastWin32Error();
275
                    Marshal.ThrowExceptionForHR(error);
276
                }
277
                Context.CurrentFolder = path.ToString();
278
                Debug.WriteLine(String.Format("Folder is {0}", Context.CurrentFolder), LogCategories.ShellMenu);
279
            }
280
        }
281

    
282
        #endregion
283

    
284

    
285
        #region IContextMenu Members
286

    
287
        /// <summary>
288
        /// Add commands to a shortcut menu.
289
        /// </summary>
290
        /// <param name="hMenu">A handle to the shortcut menu.</param>
291
        /// <param name="iMenu">
292
        /// The zero-based position at which to insert the first new menu item.
293
        /// </param>
294
        /// <param name="idCmdFirst">
295
        /// The minimum value that the handler can specify for a menu item ID.
296
        /// </param>
297
        /// <param name="idCmdLast">
298
        /// The maximum value that the handler can specify for a menu item ID.
299
        /// </param>
300
        /// <param name="uFlags">
301
        /// Optional flags that specify how the shortcut menu can be changed.
302
        /// </param>
303
        /// <returns>
304
        /// If successful, returns an HRESULT value that has its severity value set 
305
        /// to SEVERITY_SUCCESS and its code value set to the offset of the largest 
306
        /// command identifier that was assigned, plus one.
307
        /// </returns>
308
        public int QueryContextMenu(
309
            IntPtr hMenu,
310
            uint iMenu,
311
            uint idCmdFirst,
312
            uint idCmdLast,
313
            uint uFlags)
314
        {
315
            Debug.WriteLine("Start qcm", LogCategories.ShellMenu);
316
            // If uFlags include CMF_DEFAULTONLY then we should not do anything.
317
            Debug.WriteLine(String.Format("Flags {0}", uFlags), LogCategories.ShellMenu);
318

    
319
            if (((uint)CMF.CMF_DEFAULTONLY & uFlags) != 0)
320
            {
321
                Debug.WriteLine("Default only flag, returning", LogCategories.ShellMenu);
322
                return WinError.MAKE_HRESULT(WinError.SEVERITY_SUCCESS, 0, 0);
323
            }
324

    
325
            if (!Context.IsManaged)
326
            {
327
                Debug.WriteLine("Not a PITHOS folder",LogCategories.ShellMenu);
328
                return WinError.MAKE_HRESULT(WinError.SEVERITY_SUCCESS, 0, 0);
329
            }
330

    
331
            /*
332
                        if (!selectedFolder.ToLower().Contains("pithos"))
333
                            return WinError.MAKE_HRESULT(WinError.SEVERITY_SUCCESS, 0, 0);
334
            */
335

    
336
            // Use either InsertMenu or InsertMenuItem to add menu items.
337

    
338
            uint largestID = 0;
339

    
340
            DisplayFlags itemType = (Context.IsFolder) ? DisplayFlags.Folder : DisplayFlags.File;
341

    
342
            Debug.WriteLine(String.Format("Item Flags {0}", itemType), LogCategories.ShellMenu);
343

    
344
            if (!NativeMethods.InsertMenu(hMenu, idCmdFirst, MF.MF_SEPARATOR | MF.MF_BYPOSITION, 0, String.Empty))
345
            {
346
                Trace.TraceError("Error adding separator 1\r\n{0}", Marshal.GetLastWin32Error());
347
                return Marshal.GetHRForLastWin32Error();
348
            }
349

    
350
            foreach (var menuItem in _items.Values)
351
            {
352
                Debug.WriteLine(String.Format("Menu Flags {0}", menuItem.DisplayFlags), LogCategories.ShellMenu);
353
                if ((itemType & menuItem.DisplayFlags) != DisplayFlags.None)
354
                {
355
                    Debug.WriteLine("Adding Menu", LogCategories.ShellMenu);
356

    
357
                    MENUITEMINFO mii = menuItem.CreateInfo(idCmdFirst);
358
                    if (!NativeMethods.InsertMenuItem(hMenu, iMenu, true, ref mii))
359
                    {
360
                        var lastError = Marshal.GetLastWin32Error();
361
                        var lastErrorHR = Marshal.GetHRForLastWin32Error();
362
                        return lastErrorHR;
363
                    }
364
                    if (largestID < menuItem.MenuDisplayId)
365
                        largestID = menuItem.MenuDisplayId;
366
                }
367
            }
368

    
369
            Debug.Write("Adding Separator 1", LogCategories.ShellMenu);
370
            // Add a separator.
371
           /* MENUITEMINFO sep = new MENUITEMINFO();
372
            sep.cbSize = (uint)Marshal.SizeOf(sep);
373
            sep.fMask = MIIM.MIIM_TYPE;
374
            sep.fType = MFT.MFT_SEPARATOR;*/
375
            if (!NativeMethods.InsertMenu(hMenu, (uint)_items.Values.Count + idCmdFirst+1,MF.MF_SEPARATOR|MF.MF_BYPOSITION, 0, String.Empty))
376
            {
377
                Trace.TraceError("Error adding separator 1\r\n{0}", Marshal.GetLastWin32Error());
378
                return Marshal.GetHRForLastWin32Error();
379
            }
380

    
381

    
382

    
383

    
384
            Debug.WriteLine("Menus added", LogCategories.ShellOverlays);
385
            // Return an HRESULT value with the severity set to SEVERITY_SUCCESS. 
386
            // Set the code value to the offset of the largest command identifier 
387
            // that was assigned, plus one (1).
388
            return WinError.MAKE_HRESULT(WinError.SEVERITY_SUCCESS, 0,
389
                largestID + 1);
390
        }
391

    
392
        /// <summary>
393
        /// Carry out the command associated with a shortcut menu item.
394
        /// </summary>
395
        /// <param name="pici">
396
        /// A pointer to a CMINVOKECOMMANDINFO or CMINVOKECOMMANDINFOEX structure 
397
        /// containing information about the command. 
398
        /// </param>
399
        public void InvokeCommand(IntPtr pici)
400
        {
401
            bool isUnicode = false;
402

    
403
            // Determine which structure is being passed in, CMINVOKECOMMANDINFO or 
404
            // CMINVOKECOMMANDINFOEX based on the cbSize member of lpcmi. Although 
405
            // the lpcmi parameter is declared in Shlobj.h as a CMINVOKECOMMANDINFO 
406
            // structure, in practice it often points to a CMINVOKECOMMANDINFOEX 
407
            // structure. This struct is an extended version of CMINVOKECOMMANDINFO 
408
            // and has additional members that allow Unicode strings to be passed.
409
            CMINVOKECOMMANDINFO ici = (CMINVOKECOMMANDINFO)Marshal.PtrToStructure(
410
                pici, typeof(CMINVOKECOMMANDINFO));
411
            CMINVOKECOMMANDINFOEX iciex = new CMINVOKECOMMANDINFOEX();
412
            if (ici.cbSize == Marshal.SizeOf(typeof(CMINVOKECOMMANDINFOEX)))
413
            {
414
                if ((ici.fMask & CMIC.CMIC_MASK_UNICODE) != 0)
415
                {
416
                    isUnicode = true;
417
                    iciex = (CMINVOKECOMMANDINFOEX)Marshal.PtrToStructure(pici,
418
                        typeof(CMINVOKECOMMANDINFOEX));
419
                }
420
            }
421

    
422
            // Determines whether the command is identified by its offset or verb.
423
            // There are two ways to identify commands:
424
            // 
425
            //   1) The command's verb string 
426
            //   2) The command's identifier offset
427
            // 
428
            // If the high-order word of lpcmi->lpVerb (for the ANSI case) or 
429
            // lpcmi->lpVerbW (for the Unicode case) is nonzero, lpVerb or lpVerbW 
430
            // holds a verb string. If the high-order word is zero, the command 
431
            // offset is in the low-order word of lpcmi->lpVerb.
432

    
433
            // For the ANSI case, if the high-order word is not zero, the command's 
434
            // verb string is in lpcmi->lpVerb. 
435
            if (!isUnicode && NativeMethods.HighWord(ici.verb.ToInt32()) != 0)
436
            {
437
                // Is the verb supported by this context menu extension?
438
                string verbAnsi = Marshal.PtrToStringAnsi(ici.verb);
439
                if (_items.ContainsKey(verbAnsi))
440
                {
441
                    _items[verbAnsi].MenuCommand(ici.hwnd);
442
                }
443
                else
444
                {
445
                    // If the verb is not recognized by the context menu handler, it 
446
                    // must return E_FAIL to allow it to be passed on to the other 
447
                    // context menu handlers that might implement that verb.
448
                    Marshal.ThrowExceptionForHR(WinError.E_FAIL);
449
                }
450
            }
451

    
452
                // For the Unicode case, if the high-order word is not zero, the 
453
            // command's verb string is in lpcmi->lpVerbW. 
454
            else if (isUnicode && NativeMethods.HighWord(iciex.verbW.ToInt32()) != 0)
455
            {
456
                // Is the verb supported by this context menu extension?
457
                string verbUTF = Marshal.PtrToStringUni(iciex.verbW);
458
                if (_items.ContainsKey(verbUTF))
459
                {
460
                    _items[verbUTF].MenuCommand(ici.hwnd);
461
                }
462
                else
463
                {
464
                    // If the verb is not recognized by the context menu handler, it 
465
                    // must return E_FAIL to allow it to be passed on to the other 
466
                    // context menu handlers that might implement that verb.
467
                    Marshal.ThrowExceptionForHR(WinError.E_FAIL);
468
                }
469
            }
470

    
471
                // If the command cannot be identified through the verb string, then 
472
            // check the identifier offset.
473
            else
474
            {
475
                // Is the command identifier offset supported by this context menu 
476
                // extension?
477
                int menuID = NativeMethods.LowWord(ici.verb.ToInt32());
478
                var menuItem = _items.FirstOrDefault(item => item.Value.MenuDisplayId == menuID).Value;
479
                if (menuItem != null)
480
                {
481
                    menuItem.MenuCommand(ici.hwnd);
482
                }
483
                else
484
                {
485
                    // If the verb is not recognized by the context menu handler, it 
486
                    // must return E_FAIL to allow it to be passed on to the other 
487
                    // context menu handlers that might implement that verb.
488
                    Marshal.ThrowExceptionForHR(WinError.E_FAIL);
489
                }
490
            }
491
        }
492

    
493
        /// <summary>
494
        /// Get information about a shortcut menu command, including the help string 
495
        /// and the language-independent, or canonical, name for the command.
496
        /// </summary>
497
        /// <param name="idCmd">Menu command identifier offset.</param>
498
        /// <param name="uFlags">
499
        /// Flags specifying the information to return. This parameter can have one 
500
        /// of the following values: GCS_HELPTEXTA, GCS_HELPTEXTW, GCS_VALIDATEA, 
501
        /// GCS_VALIDATEW, GCS_VERBA, GCS_VERBW.
502
        /// </param>
503
        /// <param name="pReserved">Reserved. Must be IntPtr.Zero</param>
504
        /// <param name="pszName">
505
        /// The address of the buffer to receive the null-terminated string being 
506
        /// retrieved.
507
        /// </param>
508
        /// <param name="cchMax">
509
        /// Size of the buffer, in characters, to receive the null-terminated string.
510
        /// </param>
511
        public void GetCommandString(
512
            UIntPtr idCmd,
513
            uint uFlags,
514
            IntPtr pReserved,
515
            StringBuilder pszName,
516
            uint cchMax)
517
        {
518
            uint menuID = idCmd.ToUInt32();
519
            var menuItem = _items.FirstOrDefault(item => item.Value.MenuDisplayId == menuID).Value;
520
            if (menuItem != null)
521
            {
522
                switch ((GCS)uFlags)
523
                {
524
                    case GCS.GCS_VERBW:
525
                        if (menuItem.VerbCanonicalName.Length > cchMax - 1)
526
                        {
527
                            Marshal.ThrowExceptionForHR(WinError.STRSAFE_E_INSUFFICIENT_BUFFER);
528
                        }
529
                        else
530
                        {
531
                            pszName.Clear();
532
                            pszName.Append(menuItem.VerbCanonicalName);
533
                        }
534
                        break;
535

    
536
                    case GCS.GCS_HELPTEXTW:
537
                        if (menuItem.VerbHelpText.Length > cchMax - 1)
538
                        {
539
                            Marshal.ThrowExceptionForHR(WinError.STRSAFE_E_INSUFFICIENT_BUFFER);
540
                        }
541
                        else
542
                        {
543
                            pszName.Clear();
544
                            pszName.Append(menuItem.VerbHelpText);
545
                        }
546
                        break;
547
                }
548
            }
549
        }
550

    
551
        #endregion
552
    }
553
}