Bug 647216: Add ability to handle mouse events in the title bar on OS X. Work started by Markus Stange and Paul O'Shannessy. r=mstange

This commit is contained in:
Josh Aas 2012-10-26 15:42:50 -04:00
parent 69c7d8ae0b
commit 0b248b4c40
5 changed files with 228 additions and 20 deletions

View File

@ -2,12 +2,18 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifdef XP_WIN
#define USE_HITTEST
#elifdef MOZ_WIDGET_COCOA
#define USE_HITTEST
#endif
let EXPORTED_SYMBOLS = [ "WindowDraggingElement" ];
function WindowDraggingElement(elem) {
this._elem = elem;
this._window = elem.ownerDocument.defaultView;
#ifdef XP_WIN
#ifdef USE_HITTEST
if (!this.isPanel())
this._elem.addEventListener("MozMouseHittest", this, false);
else
@ -54,7 +60,7 @@ WindowDraggingElement.prototype = {
},
handleEvent: function(aEvent) {
let isPanel = this.isPanel();
#ifdef XP_WIN
#ifdef USE_HITTEST
if (!isPanel) {
if (this.shouldDrag(aEvent))
aEvent.preventDefault();

View File

@ -183,6 +183,10 @@ typedef NSInteger NSEventGestureAxis;
- (NSEventPhase)momentumPhase;
@end
@protocol EventRedirection
- (NSView*)targetView;
@end
@interface ChildView : NSView<
#ifdef ACCESSIBILITY
mozAccessible,
@ -267,6 +271,8 @@ typedef NSInteger NSEventGestureAxis;
// class initialization
+ (void)initialize;
+ (void)registerViewForDraggedTypes:(NSView*)aView;
// these are sent to the first responder when the window key status changes
- (void)viewsWindowDidBecomeKey;
- (void)viewsWindowDidResignKey;
@ -276,6 +282,8 @@ typedef NSInteger NSEventGestureAxis;
- (void)sendFocusEvent:(uint32_t)eventType;
- (void)updateWindowDraggableStateOnMouseMove:(NSEvent*)theEvent;
- (void)handleMouseMoved:(NSEvent*)aEvent;
- (void)drawRect:(NSRect)aRect inTitlebarContext:(CGContextRef)aContext;

View File

@ -1905,6 +1905,20 @@ NSEvent* gLastDragMouseDownEvent = nil;
}
}
+ (void)registerViewForDraggedTypes:(NSView*)aView
{
[aView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType,
NSStringPboardType,
NSHTMLPboardType,
NSURLPboardType,
NSFilesPromisePboardType,
kWildcardPboardType,
kCorePboardType_url,
kCorePboardType_urld,
kCorePboardType_urln,
nil]];
}
// initWithFrame:geckoChild:
- (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild
{
@ -1952,17 +1966,8 @@ NSEvent* gLastDragMouseDownEvent = nil;
}
// register for things we'll take from other applications
PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("ChildView initWithFrame: registering drag types\n"));
[self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType,
NSStringPboardType,
NSHTMLPboardType,
NSURLPboardType,
NSFilesPromisePboardType,
kWildcardPboardType,
kCorePboardType_url,
kCorePboardType_urld,
kCorePboardType_urln,
nil]];
[ChildView registerViewForDraggedTypes:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowBecameMain:)
name:NSWindowDidBecomeMainNotification
@ -2332,7 +2337,7 @@ NSEvent* gLastDragMouseDownEvent = nil;
- (BOOL)mouseDownCanMoveWindow
{
return NO;
return [[self window] isMovableByWindowBackground];
}
- (void)lockFocus
@ -3278,6 +3283,25 @@ NSEvent* gLastDragMouseDownEvent = nil;
mGeckoChild->DispatchEvent(&event, status);
}
- (void)updateWindowDraggableStateOnMouseMove:(NSEvent*)theEvent
{
if (!theEvent || !mGeckoChild) {
return;
}
nsCocoaWindow* windowWidget = mGeckoChild->GetXULWindowWidget();
if (!windowWidget) {
return;
}
// We assume later on that sending a hit test event won't cause widget destruction.
nsMouseEvent hitTestEvent(true, NS_MOUSE_MOZHITTEST, mGeckoChild, nsMouseEvent::eReal);
[self convertCocoaMouseEvent:theEvent toGeckoEvent:&hitTestEvent];
bool result = mGeckoChild->DispatchWindowEvent(hitTestEvent);
[windowWidget->GetCocoaWindow() setMovableByWindowBackground:result];
}
- (void)handleMouseMoved:(NSEvent*)theEvent
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
@ -4802,6 +4826,11 @@ ChildViewMouseTracker::ViewForEvent(NSEvent* aEvent)
NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, window);
NSView* view = [[[window contentView] superview] hitTest:windowEventLocation];
while([view conformsToProtocol:@protocol(EventRedirection)]) {
view = [(id<EventRedirection>)view targetView];
}
if (![view isKindOfClass:[ChildView class]])
return nil;
@ -4900,7 +4929,7 @@ ChildViewMouseTracker::WindowAcceptsEvent(NSWindow* aWindow, NSEvent* aEvent,
NSWindow *ourWindow = [self window];
NSView *contentView = [ourWindow contentView];
if ([ourWindow isKindOfClass:[ToolbarWindow class]] && (self == contentView))
return NO;
return [ourWindow isMovableByWindowBackground];
return [self nsChildView_NSView_mouseDownCanMoveWindow];
}

View File

@ -18,6 +18,7 @@
class nsCocoaWindow;
class nsChildView;
class nsMenuBarX;
@class ChildView;
// Value copied from BITMAP_MAX_AREA, used in nsNativeThemeCocoa.mm
#define CUIDRAW_MAX_AREA 500000
@ -176,6 +177,7 @@ typedef struct _nsCocoaWindowList {
TitlebarAndBackgroundColor *mColor;
float mUnifiedToolbarHeight;
NSColor *mBackgroundColor;
NSView *mTitlebarView; // strong
}
// Pass nil here to get the default appearance.
- (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive;
@ -186,6 +188,7 @@ typedef struct _nsCocoaWindowList {
- (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect sync:(BOOL)aSync;
- (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect;
- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState;
- (ChildView*)mainChildView;
@end
class nsCocoaWindow : public nsBaseWidget, public nsPIWidgetCocoa

View File

@ -447,6 +447,10 @@ nsresult nsCocoaWindow::CreateNativeWindow(const NSRect &aRect,
[mWindow setContentMinSize:NSMakeSize(60, 60)];
[mWindow disableCursorRects];
// Make sure the window starts out not draggable by the background.
// We will turn it on as necessary.
[mWindow setMovableByWindowBackground:NO];
[[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
mWindowMadeHere = true;
@ -2583,7 +2587,94 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
@end
// This class allows us to have a "unified toolbar" style window. It works like this:
@interface TitlebarMouseHandlingView : NSView<EventRedirection>
{
ToolbarWindow* mWindow; // weak
BOOL mProcessingRightMouseDown;
}
- (id)initWithWindow:(ToolbarWindow*)aWindow;
@end
@implementation TitlebarMouseHandlingView
- (id)initWithWindow:(ToolbarWindow*)aWindow
{
if ((self = [super initWithFrame:[aWindow titlebarRect]])) {
mWindow = aWindow;
[self setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
[ChildView registerViewForDraggedTypes:self];
mProcessingRightMouseDown = NO;
}
return self;
}
- (NSView*)targetView
{
return [mWindow mainChildView];
}
- (BOOL)mouseDownCanMoveWindow
{
return [mWindow isMovableByWindowBackground];
}
// We redirect many types of events to the window's mainChildView simply by
// passing the event object to the respective handler method. We don't need any
// coordinate transformations because event coordinates are relative to the
// window.
// We only need to handle event types whose target NSView is determined by the
// event's position. We don't need to handle key events and NSMouseMoved events
// because those are only sent to the window's first responder. This view
// doesn't override acceptsFirstResponder, so it will never receive those kinds
// of events.
- (void)mouseMoved:(NSEvent*)aEvent { [[self targetView] mouseMoved:aEvent]; }
- (void)mouseDown:(NSEvent*)aEvent { [[self targetView] mouseDown:aEvent]; }
- (void)mouseUp:(NSEvent*)aEvent { [[self targetView] mouseUp:aEvent]; }
- (void)mouseDragged:(NSEvent*)aEvent { [[self targetView] mouseDragged:aEvent]; }
- (void)rightMouseDown:(NSEvent*)aEvent
{
// To avoid recursion...
if (mProcessingRightMouseDown)
return;
mProcessingRightMouseDown = YES;
[[self targetView] rightMouseDown:aEvent];
mProcessingRightMouseDown = NO;
}
- (void)rightMouseUp:(NSEvent*)aEvent { [[self targetView] rightMouseUp:aEvent]; }
- (void)rightMouseDragged:(NSEvent*)aEvent { [[self targetView] rightMouseDragged:aEvent]; }
- (void)otherMouseDown:(NSEvent*)aEvent { [[self targetView] otherMouseDown:aEvent]; }
- (void)otherMouseUp:(NSEvent*)aEvent { [[self targetView] otherMouseUp:aEvent]; }
- (void)otherMouseDragged:(NSEvent*)aEvent { [[self targetView] otherMouseDragged:aEvent]; }
- (void)scrollWheel:(NSEvent*)aEvent { [[self targetView] scrollWheel:aEvent]; }
- (void)swipeWithEvent:(NSEvent*)aEvent { [[self targetView] swipeWithEvent:aEvent]; }
- (void)beginGestureWithEvent:(NSEvent*)aEvent { [[self targetView] beginGestureWithEvent:aEvent]; }
- (void)magnifyWithEvent:(NSEvent*)aEvent { [[self targetView] magnifyWithEvent:aEvent]; }
- (void)rotateWithEvent:(NSEvent*)aEvent { [[self targetView] rotateWithEvent:aEvent]; }
- (void)endGestureWithEvent:(NSEvent*)aEvent { [[self targetView] endGestureWithEvent:aEvent]; }
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{ return [[self targetView] draggingEntered:sender]; }
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{ return [[self targetView] draggingUpdated:sender]; }
- (void)draggingExited:(id <NSDraggingInfo>)sender
{ [[self targetView] draggingExited:sender]; }
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{ return [[self targetView] performDragOperation:sender]; }
- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
{ [[self targetView] draggedImage:anImage endedAt:aPoint operation:operation]; }
- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
{ return [[self targetView] draggingSourceOperationMaskForLocal:isLocal]; }
- (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination
{ return [[self targetView] namesOfPromisedFilesDroppedAtDestination:dropDestination]; }
- (NSMenu*)menuForEvent:(NSEvent*)aEvent
{ return [[self targetView] menuForEvent:aEvent]; }
@end
// This class allows us to exercise control over the window's title bar. This
// allows for a "unified toolbar" look, and for extending the content area into
// the title bar. It works like this:
// 1) We set the window's style to textured.
// 2) Because of this, the background color applies to the entire window, including
// the titlebar area. For normal textured windows, the default pattern is a
@ -2617,6 +2708,11 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
// to the containing window - the other direction doesn't work. That's why the
// toolbar height is cached in the ToolbarWindow but nsNativeThemeCocoa can simply
// query the window for its titlebar height when drawing the toolbar.
@interface ToolbarWindow(Private)
- (void)installTitlebarMouseHandlingView;
- (void)uninstallTitlebarMouseHandlingView;
@end;
@implementation ToolbarWindow
- (id)initWithContentRect:(NSRect)aContentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)aFlag
@ -2651,6 +2747,7 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
[mColor release];
[mBackgroundColor release];
[mTitlebarView release];
[super dealloc];
NS_OBJC_END_TRY_ABORT_BLOCK;
@ -2730,11 +2827,48 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
[self setTitlebarNeedsDisplayInRect:[self titlebarRect] sync:needSyncRedraw];
}
// Extending the content area into the title bar works by redirection of both
// drawing and mouse events.
// The window's NSView hierarchy looks like this:
// - border view ([[window contentView] superview])
// - transparent title bar event redirection view
// - window controls (traffic light buttons)
// - content view ([window contentView], default NSView provided by the window)
// - our main Gecko ChildView ([window mainChildView]), which has an
// OpenGL context attached to it when accelerated
// - possibly more ChildViews for plugins
//
// When the window is in title bar extension mode, the mainChildView covers the
// whole window but is only visible in the content area of the window, because
// it's a subview of the window's contentView and thus clipped to its dimensions.
// This clipping is a good thing because it avoids a few problems. For example,
// if the mainChildView weren't clipped and thus visible in the titlebar, we'd
// have have to do the rounded corner masking and the drawing of the highlight
// line ourselves.
// This would be especially hard in combination with OpenGL acceleration since
// rounded corners would require making the OpenGL context transparent, which
// would bring another set of challenges with it. Having the window controls
// draw on top of an OpenGL context could be hard, too.
//
// So title bar drawing happens in the border view. The border view's drawRect
// method is not under our control, but we can get it to call into our code
// using some tricks, see the TitlebarAndBackgroundColor class below.
// Specifically, we have it call the TitlebarDrawCallback function, which
// draws the contents of mainChildView into the provided CGContext.
// (Even if the ChildView uses OpenGL for rendering, drawing in the title bar
// will happen non-accelerated in that CGContext.)
//
// Mouse event redirection happens via a TitlebarMouseHandlingView which we
// install below.
- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState
{
BOOL stateChanged = ([self drawsContentsIntoWindowFrame] != aState);
[super setDrawsContentsIntoWindowFrame:aState];
if (stateChanged && [[self delegate] isKindOfClass:[WindowDelegate class]]) {
// Here we extend / shrink our mainChildView. We do that by firing a resize
// event which will cause the ChildView to be resized to the rect returned
// by nsCocoaWindow::GetClientBounds. GetClientBounds bases its return
// value on what we return from drawsContentsIntoWindowFrame.
WindowDelegate *windowDelegate = (WindowDelegate *)[self delegate];
nsCocoaWindow *geckoWindow = [windowDelegate geckoWidget];
if (geckoWindow) {
@ -2749,10 +2883,35 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
// we'll send a mouse move event with the correct new position.
ChildViewMouseTracker::ResendLastMouseMoveEvent();
[self setTitlebarNeedsDisplayInRect:[self titlebarRect]];
if (aState) {
[self installTitlebarMouseHandlingView];
} else {
[self uninstallTitlebarMouseHandlingView];
}
}
}
- (void)installTitlebarMouseHandlingView
{
mTitlebarView = [[TitlebarMouseHandlingView alloc] initWithWindow:self];
[[[self contentView] superview] addSubview:mTitlebarView positioned:NSWindowBelow relativeTo:nil];
}
- (void)uninstallTitlebarMouseHandlingView
{
[mTitlebarView removeFromSuperview];
[mTitlebarView release];
mTitlebarView = nil;
}
- (ChildView*)mainChildView
{
NSView* view = [[[self contentView] subviews] lastObject];
if (view && [view isKindOfClass:[ChildView class]])
return (ChildView*)view;
return nil;
}
// Returning YES here makes the setShowsToolbarButton method work even though
// the window doesn't contain an NSToolbar.
- (BOOL)_hasToolbar
@ -2821,6 +2980,9 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
nsCocoaWindow *widget = [(WindowDelegate *)delegate geckoWidget];
if (widget) {
if (type == NSMouseMoved) {
[[self mainChildView] updateWindowDraggableStateOnMouseMove:anEvent];
}
if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window))
return;
if (widget->HasModalDescendents())
@ -2886,8 +3048,8 @@ TitlebarDrawCallback(void* aInfo, CGContextRef aContext)
NSRect titlebarRect = [window titlebarRect];
if ([window drawsContentsIntoWindowFrame]) {
NSView* view = [[[window contentView] subviews] lastObject];
if (!view || ![view isKindOfClass:[ChildView class]])
ChildView* view = [window mainChildView];
if (!view)
return;
// Gecko drawing assumes flippedness, but the current context isn't flipped
@ -2898,7 +3060,7 @@ TitlebarDrawCallback(void* aInfo, CGContextRef aContext)
CGContextTranslateCTM(aContext, 0.0f, -[window frame].size.height);
NSRect flippedTitlebarRect = { NSZeroPoint, titlebarRect.size };
[(ChildView*)view drawRect:flippedTitlebarRect inTitlebarContext:aContext];
[view drawRect:flippedTitlebarRect inTitlebarContext:aContext];
} else {
BOOL isMain = [window isMainWindow];
NSColor *titlebarColor = [window titlebarColorForActiveWindow:isMain];