the view to display within the menu
+/// result: a new poppable window menu containing the given view
+///
+/// notes: the window is sized to fit the frame of the view you pass.
+///
+///********************************************************************************************************************
+
++ (GCWindowMenu*) windowMenuWithContentView:(NSView*) view
+{
+ GCWindowMenu* menu = [self windowMenu];
+
+ [menu setMainView:view sizeToFit:YES];
+ return menu;
+}
+
+
+///*********************************************************************************************************************
+///
+/// method: popUpAtPoint:withEvent:forView:
+/// scope: public instance method
+/// overrides:
+/// description: pops up a custom popup menu, tracks it, then hides it again with a fadeout
+///
+/// parameters: the location within the window at which to display the menu (top, left of menu)
+/// the event to start the display with (usually from a mouse down in some view)
+/// result: none
+///
+/// notes:
+///
+///********************************************************************************************************************
+
+- (void) popUpAtPoint:(NSPoint) p withEvent:(NSEvent*) event
+{
+ [[self class] popUpWindowMenu:self atPoint:p withEvent:event];
+}
+
+
+///*********************************************************************************************************************
+///
+/// method: popUpWithEvent:forView:
+/// scope: public instance method
+/// overrides:
+/// description: pops up a custom popup menu, tracks it, then hides it again with a fadeout
+///
+/// parameters: the event to start the display with (usually from a mouse down in some view)
+/// result: none
+///
+/// notes:
+///
+///********************************************************************************************************************
+
+- (void) popUpWithEvent:(NSEvent*) event
+{
+ [[self class] popUpWindowMenu:self withEvent:event];
+}
+
+
+
+///*********************************************************************************************************************
+///
+/// method: initWithContentRect:styleMask:backing:defer:
+/// scope: public instance method
+/// overrides: NSWindow
+/// description: designated initializer.
+///
+/// parameters: <> see NSWindow
+/// result: the window
+///
+/// notes:
+///
+///********************************************************************************************************************
+
+- (id) initWithContentRect:(NSRect) contentRect
+ styleMask:(unsigned int) styleMask
+ backing:(NSBackingStoreType) bufferingType
+ defer:(BOOL) deferCreation
+{
+ if ((self = [super initWithContentRect:contentRect
+ styleMask:styleMask
+ backing:bufferingType
+ defer:deferCreation]) != nil )
+ {
+ [self setLevel:NSPopUpMenuWindowLevel];
+ [self setHasShadow:YES];
+ [self setAlphaValue:0.95];
+ [self setReleasedWhenClosed:YES];
+ [self setFrame:kGCDefaultWindowMenuSize display:NO];
+
+ _mainView = nil;
+ _passFirstClick = YES;
+ _oneShotTracking = YES;
+ }
+
+ return self;
+}
+
+
+
+///*********************************************************************************************************************
+///
+/// method: trackWithEvent:
+/// scope: public instance method
+/// overrides:
+/// description: track the mouse in the menu
+///
+/// parameters: the initial starting event (will usually be a mouse down)
+/// result: none
+///
+/// notes: tracking calls the main view's usual mouseDown/dragged/up methods, and tries to do so as compatibly
+/// as possible with the usual view behaviours.
+///
+///********************************************************************************************************************
+
+- (void) trackWithEvent:(NSEvent*) event
+{
+ // tracks the "menu" by keeping control until a mouse up (or down, if menu 'clicked' into being)
+
+ NSLog(@"starting tracking; initial event = %@", event);
+
+ //[NSEvent startPeriodicEventsAfterDelay:1.0 withPeriod:0.1];
+
+ NSTimeInterval startTime = [event timestamp];
+
+ [self setAcceptsMouseMovedEvents:YES];
+
+ if ( _passFirstClick )
+ {
+ [[self mainView] mouseDown:[self transmogrify:event]];
+
+ // the view may have trapped the mouse down and implemented its own tracking.
+ // Standard NSControls do that for example. In that case we don't want to track
+ // ourselves, so need to detect that and abort.
+
+ if ([[self currentEvent] timestamp] - startTime > 0.25 )
+ return;
+ }
+
+ NSEvent* theEvent;
+ BOOL keepOn = YES;
+ unsigned int mask;
+ BOOL invertedTracking = NO;
+
+ mask = NSLeftMouseUpMask | NSLeftMouseDraggedMask |
+ NSRightMouseUpMask | NSRightMouseDraggedMask |
+ NSAppKitDefinedMask | NSFlagsChangedMask |
+ NSScrollWheelMask;
+
+ while (keepOn)
+ {
+ theEvent = [self transmogrify:[self nextEventMatchingMask:mask]];
+
+ switch ([theEvent type])
+ {
+ case NSMouseMovedMask:
+ [[self mainView] mouseMoved:theEvent];
+ break;
+
+ case NSRightMouseDragged:
+ case NSLeftMouseDragged:
+ [[self mainView] mouseDragged:theEvent];
+ break;
+
+ case NSRightMouseUp:
+ case NSLeftMouseUp:
+ // if this is within a very short time of the mousedown, leave the menu up but track it
+ // using mouse moved and mouse down to end.
+
+ if ([theEvent timestamp] - startTime < 0.25 || !_passFirstClick )
+ {
+ invertedTracking = YES;
+ mask |= ( NSLeftMouseDownMask | NSRightMouseDownMask | NSMouseMovedMask );
+ }
+ else
+ {
+ [[self mainView] mouseUp:theEvent];
+ keepOn = NO;
+ }
+ break;
+
+ case NSRightMouseDown:
+ case NSLeftMouseDown:
+ if ( ! NSPointInRect([theEvent locationInWindow], [[self mainView] frame]))
+ keepOn = NO;
+ else
+ {
+ [[self mainView] mouseDown:theEvent];
+
+ if ( _oneShotTracking )
+ keepOn = NO;
+ }
+ break;
+
+ case NSPeriodic:
+ break;
+
+ case NSFlagsChanged:
+ [[self mainView] flagsChanged:theEvent];
+ break;
+
+ case NSAppKitDefined:
+ if([theEvent subtype] == NSApplicationDeactivatedEventType )
+ keepOn = NO;
+ break;
+
+ case NSScrollWheel:
+ [[self mainView] scrollWheel:theEvent];
+ break;
+
+ default:
+ /* Ignore any other kind of event. */
+ break;
+ }
+ }
+
+ [self discardEventsMatchingMask:NSAnyEventMask beforeEvent:theEvent];
+
+ //[NSEvent stopPeriodicEvents];
+ NSLog(@"tracking ended");
+}
+
+
+
+///*********************************************************************************************************************
+///
+/// method: transmogrify:
+/// scope: private instance method
+/// overrides:
+/// description: convert the event to the local window if necessary
+///
+/// parameters: an event
+/// result: the same event, or a modified version
+///
+/// notes: ensures that events received while tracking are always targetted at the right window
+///
+///********************************************************************************************************************
+
+- (NSEvent*) transmogrify:(NSEvent*) event
+{
+ if(([event window] != self) && [event isMouseEventType])
+ {
+ NSPoint glob = [[event window] convertBaseToScreen:[event locationInWindow]];
+
+ return [NSEvent mouseEventWithType: [event type]
+ location: [self convertScreenToBase:glob]
+ modifierFlags: [event modifierFlags]
+ timestamp: [event timestamp]
+ windowNumber: [self windowNumber]
+ context: [event context]
+ eventNumber: [event eventNumber]
+ clickCount: [event clickCount]
+ pressure: [event pressure]];
+ }
+ else
+ return event;
+}
+
+
+///*********************************************************************************************************************
+///
+/// method: canBecomeMainWindow
+/// scope: public instance method
+/// overrides: NSWindow
+/// description:
+///
+/// parameters: none
+/// result: return YES
+///
+/// notes:
+///
+///********************************************************************************************************************
+
+- (BOOL) canBecomeMainWindow
+{
+ return NO;
+}
+
+
+///*********************************************************************************************************************
+///
+/// method: setMainView:sizeToFit:
+/// scope: public instance method
+/// overrides:
+/// description: sets the pop-up window's content to the given view, and optionally sizes the window to fit
+///
+/// parameters: any view already created to be displayed in the menu
+/// if YES, window is sized to the view's frame. If NO, the window size is not changed
+/// result: none
+///
+/// notes: main view is additionally retained so outlet from NIB may be directly passed in
+///
+///********************************************************************************************************************
+
+- (void) setMainView:(NSView*) aView sizeToFit:(BOOL) stf
+{
+ [aView retain];
+ [_mainView release];
+ _mainView = aView;
+
+ // add as a subview which retains it as well
+
+ [[self contentView] addSubview:aView];
+
+ // if stf, position the view at top, left corner of the window and
+ // make the window the size of the view
+
+ if ( stf )
+ {
+ NSRect fr = [self frameRectForContentRect:NSInsetRect( [aView frame], -kGCMenuContentInset, -kGCMenuContentInset )];
+
+ fr.origin = NSZeroPoint;
+ [aView setFrameOrigin:NSMakePoint( kGCMenuContentInset, kGCMenuContentInset )];
+ [self setFrame:fr display:YES];
+ }
+
+ [_mainView setNeedsDisplay:YES];
+
+ // if the view added is an NSControl, set first click to NO by default
+
+ if ([aView isKindOfClass:[NSControl class]])
+ [self setMainViewWantsFirstClick:NO];
+}
+
+
+///*********************************************************************************************************************
+///
+/// method: mainView
+/// scope: public instance method
+/// overrides:
+/// description: get the main view
+///
+/// parameters: none
+/// result: the main view
+///
+/// notes:
+///
+///********************************************************************************************************************
+
+- (NSView*) mainView
+{
+ return _mainView;
+}
+
+
+///*********************************************************************************************************************
+///
+/// method: setMainViewWantsFirstClick:
+/// scope: public instance method
+/// overrides:
+/// description: sets whether the main view should receive a mouse down on entry to the tracking loop
+///
+/// parameters: YES to get the first click
+/// result: none
+///
+/// notes: normally should be YES (the default). However views such as NSControl derivatives that implement
+/// their own tracking should set NO. If NO, the popup can only be operated by clicking to open, then
+/// clicking and dragging within - the continuous click to open, drag through and release operation
+/// wont work because the control doesn't get a mouse down to start with.
+///
+///********************************************************************************************************************
+
+- (void) setMainViewWantsFirstClick:(BOOL) firstClick
+{
+ _passFirstClick = firstClick;
+}
+
+
+///*********************************************************************************************************************
+///
+/// method: setShouldCloseWhenViewTrackingReturns:
+/// scope: public instance method
+/// overrides:
+/// description: sets whether popup should close or remain visible after main view completes its own tracking
+///
+/// parameters: YES close on return from view tracking, NO to remain visble
+/// result: none
+///
+/// notes: this affects tracking with views that implement their own tracking, such as NSControl. If YES, you
+/// get one shot at the control - after operating it, it will be hidden. If NO, the control may be
+/// changed as often as you want but you must manually click outside the menu to close it.
+///
+///********************************************************************************************************************
+
+- (void) setShouldCloseWhenViewTrackingReturns:(BOOL) cmup
+{
+ _oneShotTracking = cmup;
+}
+
+
+///*********************************************************************************************************************
+///
+/// method: oneShotHasReachedInverse:
+/// scope: public instance method
+/// overrides: NSObject (OneShotDelegate)
+/// description: callback from fade out effect
+///
+/// parameters: goes from 1..0
+/// result: none
+///
+/// notes:
+///
+///********************************************************************************************************************
+
+- (void) oneShotHasReachedInverse:(float) relpos
+{
+ [self setAlphaValue:relpos];
+}
+
+
+///*********************************************************************************************************************
+///
+/// method: oneShotComplete
+/// scope: public instance method
+/// overrides: NSObject (OneShotDelegate)
+/// description: callback from fade out effect
+///
+/// parameters: none
+/// result: none
+///
+/// notes: removes the window from screen - oneshot will then release it
+///
+///********************************************************************************************************************
+
+- (void) oneShotComplete
+{
+ [[self parentWindow] removeChildWindow:self];
+ [self orderOut:self];
+}
+
+
+
+
+
+@end
+
+
+@implementation NSEvent (GCAdditions)
+
+///*********************************************************************************************************************
+///
+/// method: isMouseEventType:
+/// scope: public instance method
+/// overrides:
+/// description: checks event to see if it's any mouse event
+///
+/// parameters: none
+/// result: YES if the event is a mouse event of any kind
+///
+/// notes:
+///
+///********************************************************************************************************************
+
+- (BOOL) isMouseEventType
+{
+ // returns YES if type is any mouse type
+
+ NSEventType t = [self type];
+
+ return ( t == NSLeftMouseDown ||
+ t == NSLeftMouseUp ||
+ t == NSRightMouseDown ||
+ t == NSRightMouseUp ||
+ t == NSLeftMouseDragged ||
+ t == NSRightMouseDragged ||
+ t == NSOtherMouseDown ||
+ t == NSOtherMouseUp ||
+ t == NSOtherMouseDragged );
+}
+
+@end
diff --git a/ThirdParty/GCWindowMenu/PopupButton.h b/ThirdParty/GCWindowMenu/PopupButton.h
new file mode 100644
index 000000000..d9e20412c
--- /dev/null
+++ b/ThirdParty/GCWindowMenu/PopupButton.h
@@ -0,0 +1,19 @@
+//
+// GCPopTestView.h
+// GCWindowMenuTest
+//
+// Created by Graham on Tue Apr 24 2007. Modified from NSView to NSButton by Vincent Spader.
+// Copyright (c) 2007 __MyCompanyName__. All rights reserved.
+//
+
+#import
+
+
+@interface PopupButton : NSButton
+{
+ NSImage* _popButton;
+ IBOutlet NSView *_popView;
+ BOOL _hilited;
+}
+
+@end
diff --git a/ThirdParty/GCWindowMenu/PopupButton.m b/ThirdParty/GCWindowMenu/PopupButton.m
new file mode 100644
index 000000000..7fdd76092
--- /dev/null
+++ b/ThirdParty/GCWindowMenu/PopupButton.m
@@ -0,0 +1,39 @@
+//
+// GCPopTestView.m
+// GCWindowMenuTest
+//
+// Created by Graham on Tue Apr 24 2007.
+// Copyright (c) 2007 __MyCompanyName__. All rights reserved.
+//
+
+#import "PopupButton.h"
+#import "GCWindowMenu.h"
+
+
+@implementation PopupButton
+
+- (void) mouseDown:(NSEvent*) event
+{
+ // create the menu to pop up using the view connected to the _popView outlet
+
+ GCWindowMenu* menu = [GCWindowMenu windowMenuWithContentView:_popView];
+ [menu setShouldCloseWhenViewTrackingReturns:YES];
+
+ // place the menu relative to our window
+ NSPoint p = [self bounds].origin;
+ p.y += [self bounds].size.height;
+ p.x += ([self bounds].size.width - [[[_popView window] contentView] bounds].size.width)/2.0;
+ p = [self convertPoint:p toView:nil];
+
+ // draw self highlighted
+ [self highlight:YES];
+
+ // pop up and track the menu
+ [menu popUpAtPoint:p withEvent:event];
+
+ // unhighlight self
+ [self highlight:NO];
+}
+
+
+@end