44using System . Diagnostics . CodeAnalysis ;
55using System . Drawing ;
66using System . Runtime . InteropServices ;
7+ using System . Text ;
78using System . Windows . Forms ;
89using Rubberduck . VBEditor ;
910using Rubberduck . VBEditor . WindowsApi ;
@@ -145,7 +146,7 @@ private void RemoveChildControlsFromExposedControl()
145146 public int /* IOleObject:: */ Close ( [ In ] uint dwSaveOption )
146147 {
147148 _logger . Log ( LogLevel . Trace , "IOleObject::Close() called" ) ;
148- int hr = _userControl . IOleObject . Close ( dwSaveOption ) ;
149+ var hr = _userControl . IOleObject . Close ( dwSaveOption ) ;
149150
150151 // IOleObject::SetClientSite is typically called with pClientSite = null just before calling IOleObject::Close()
151152 // If it didn't, we release all host COM objects here instead,
@@ -386,7 +387,7 @@ public void AddUserControl(UserControl control, IntPtr vbeHwnd)
386387
387388 control . Dock = DockStyle . Fill ;
388389 _userControl . Controls . Add ( control ) ;
389-
390+
390391 AdjustSize ( ) ;
391392 }
392393
@@ -429,14 +430,79 @@ private void AdjustSize()
429430 }
430431 }
431432
433+ private static void ToggleDockable ( IntPtr hWndVBE )
434+ {
435+ NativeMethods . SendMessage ( hWndVBE , 0x1044 , ( IntPtr ) 0xB5 , IntPtr . Zero ) ;
436+ }
437+
432438 [ ComVisible ( false ) ]
433439 public class ParentWindow : SubclassingWindow
434440 {
441+ private readonly Logger _logger = LogManager . GetCurrentClassLogger ( ) ;
442+
443+ private const int MF_BYPOSITION = 0x400 ;
444+
435445 public event SubClassingWindowEventHandler CallBackEvent ;
436446 public delegate void SubClassingWindowEventHandler ( object sender , SubClassingWindowEventArgs e ) ;
437447
438448 private readonly IntPtr _vbeHwnd ;
439449
450+ private IntPtr _containerHwnd ;
451+ private ToolWindowState _windowState ;
452+ private IntPtr _menuHandle ;
453+
454+ private enum ToolWindowState
455+ {
456+ Unknown ,
457+ Docked ,
458+ Floating ,
459+ Undockable
460+ }
461+
462+ private ToolWindowState GetWindowState ( IntPtr containerHwnd )
463+ {
464+ var className = new StringBuilder ( 255 ) ;
465+ if ( NativeMethods . GetClassName ( containerHwnd , className , className . Capacity ) > 0 )
466+ {
467+ switch ( className . ToString ( ) )
468+ {
469+ case "wndclass_desked_gsk" :
470+ return ToolWindowState . Docked ;
471+ case "VBFloatingPalette" :
472+ return ToolWindowState . Floating ;
473+ case "DockingView" :
474+ return ToolWindowState . Undockable ;
475+ }
476+ }
477+
478+ return ToolWindowState . Unknown ;
479+ }
480+
481+ private void DisplayUndockableContextMenu ( IntPtr handle , IntPtr lParam )
482+ {
483+ if ( _menuHandle == IntPtr . Zero )
484+ {
485+ _menuHandle = NativeMethods . CreatePopupMenu ( ) ;
486+
487+ if ( _menuHandle == IntPtr . Zero )
488+ {
489+ _logger . Warn ( "Cannot create menu handle" ) ;
490+ return ;
491+ }
492+
493+ if ( ! NativeMethods . InsertMenu ( _menuHandle , 0 , MF_BYPOSITION , ( UIntPtr ) WM . RUBBERDUCK_UNDOCKABLE_CONTEXT_MENU , "Dockable" + char . MinValue ) )
494+ {
495+ _logger . Warn ( "Failed to insert a menu item for dockable command" ) ;
496+ }
497+ }
498+
499+ var param = new LParam { Value = ( uint ) lParam } ;
500+ if ( ! NativeMethods . TrackPopupMenuEx ( _menuHandle , 0x0 , param . LowWord , param . HighWord , handle , IntPtr . Zero ) )
501+ {
502+ _logger . Warn ( "Failed to set the context menu for undockable tool windows" ) ;
503+ } ;
504+ }
505+
440506 private void OnCallBackEvent ( SubClassingWindowEventArgs e )
441507 {
442508 CallBackEvent ? . Invoke ( this , e ) ;
@@ -452,6 +518,28 @@ public override int SubClassProc(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr
452518 {
453519 switch ( ( uint ) msg )
454520 {
521+ case ( uint ) WM . WINDOWPOSCHANGED :
522+ var containerHwnd = GetParent ( hWnd ) ;
523+ if ( containerHwnd != _containerHwnd )
524+ {
525+ _containerHwnd = containerHwnd ;
526+ _windowState = GetWindowState ( _containerHwnd ) ;
527+ }
528+ break ;
529+ case ( uint ) WM . CONTEXTMENU :
530+ if ( _windowState == ToolWindowState . Undockable )
531+ {
532+ DisplayUndockableContextMenu ( hWnd , lParam ) ;
533+ }
534+ break ;
535+ case ( uint ) WM . COMMAND :
536+ switch ( wParam . ToInt32 ( ) )
537+ {
538+ case ( int ) WM . RUBBERDUCK_UNDOCKABLE_CONTEXT_MENU :
539+ ToggleDockable ( _vbeHwnd ) ;
540+ break ;
541+ }
542+ break ;
455543 case ( uint ) WM . SIZE :
456544 var args = new SubClassingWindowEventArgs ( lParam ) ;
457545 if ( ! _closing ) OnCallBackEvent ( args ) ;
@@ -462,6 +550,15 @@ public override int SubClassProc(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr
462550 case ( uint ) WM . KILLFOCUS :
463551 if ( ! _closing ) User32 . SendMessage ( _vbeHwnd , WM . RUBBERDUCK_CHILD_FOCUS , Hwnd , IntPtr . Zero ) ;
464552 break ;
553+ case ( uint ) WM . DESTROY :
554+ if ( _menuHandle != IntPtr . Zero )
555+ {
556+ if ( ! NativeMethods . DestroyMenu ( _menuHandle ) )
557+ {
558+ _logger . Fatal ( $ "Failed to destroy the menu handle { _menuHandle } ") ;
559+ }
560+ }
561+ break ;
465562 }
466563 return base . SubClassProc ( hWnd , msg , wParam , lParam , uIdSubclass , dwRefData ) ;
467564 }
0 commit comments