Bug 420491 - Implement HideWindowChrome on Mac OS X by creating a new borderless native window and reparenting the content view. r=smichaud, r=josh

This commit is contained in:
Markus Stange 2009-07-22 10:57:39 +02:00
parent ce92341f20
commit 742bcfcba2
4 changed files with 174 additions and 90 deletions

View File

@ -413,6 +413,7 @@ public:
NS_IMETHOD EndSecureKeyboardInput();
void HidePlugin();
void UpdatePluginPort();
void ResetParent();

View File

@ -776,61 +776,18 @@ void* nsChildView::GetNativeData(PRUint32 aDataType)
break;
case NS_NATIVE_PLUGIN_PORT:
#ifndef NP_NO_QUICKDRAW
case NS_NATIVE_PLUGIN_PORT_QD:
{
mPluginIsCG = PR_FALSE;
mIsPluginView = PR_TRUE;
if ([mView isKindOfClass:[ChildView class]])
[(ChildView*)mView setIsPluginView:YES];
NSWindow* window = [mView nativeWindow];
if (window) {
WindowRef topLevelWindow = (WindowRef)[window windowRef];
if (topLevelWindow) {
mPluginPort.qdPort.port = ::GetWindowPort(topLevelWindow);
NSPoint viewOrigin = [mView convertPoint:NSZeroPoint toView:nil];
NSRect frame = [[window contentView] frame];
viewOrigin.y = frame.size.height - viewOrigin.y;
// need to convert view's origin to window coordinates.
// then, encode as "SetOrigin" ready values.
mPluginPort.qdPort.portx = (PRInt32)-viewOrigin.x;
mPluginPort.qdPort.porty = (PRInt32)-viewOrigin.y;
}
}
retVal = (void*)&mPluginPort;
break;
}
#endif
case NS_NATIVE_PLUGIN_PORT_CG:
{
mPluginIsCG = PR_TRUE;
#ifdef NP_NO_QUICKDRAW
aDataType = NS_NATIVE_PLUGIN_PORT_CG;
#endif
mPluginIsCG = (aDataType == NS_NATIVE_PLUGIN_PORT_CG);
mIsPluginView = PR_TRUE;
if ([mView isKindOfClass:[ChildView class]])
[(ChildView*)mView setIsPluginView:YES];
NSWindow* window = [mView nativeWindow];
if (window) {
// [NSGraphicsContext currentContext] is supposed to "return the
// current graphics context of the current thread." But sometimes
// (when called while mView isn't focused for drawing) it returns a
// graphics context for the wrong window. [window graphicsContext]
// (which "provides the graphics context associated with the window
// for the current thread") seems always to return the "right"
// graphics context. See bug 500130.
mPluginPort.cgPort.context = (CGContextRef)
[[window graphicsContext] graphicsPort];
WindowRef topLevelWindow = (WindowRef)[window windowRef];
mPluginPort.cgPort.window = topLevelWindow;
} else {
mPluginPort.cgPort.context = nil;
mPluginPort.cgPort.window = nil;
}
UpdatePluginPort();
retVal = (void*)&mPluginPort;
break;
}
@ -939,6 +896,46 @@ void nsChildView::HidePlugin()
}
}
void nsChildView::UpdatePluginPort()
{
NS_ASSERTION(mIsPluginView, "UpdatePluginPort called on non-plugin view");
NSWindow* window = [mView nativeWindow];
WindowRef topLevelWindow = window ? (WindowRef)[window windowRef] : nil;
if (mPluginIsCG) {
if (topLevelWindow) {
// [NSGraphicsContext currentContext] is supposed to "return the
// current graphics context of the current thread." But sometimes
// (when called while mView isn't focused for drawing) it returns a
// graphics context for the wrong window. [window graphicsContext]
// (which "provides the graphics context associated with the window
// for the current thread") seems always to return the "right"
// graphics context. See bug 500130.
mPluginPort.cgPort.context = (CGContextRef)
[[window graphicsContext] graphicsPort];
mPluginPort.cgPort.window = topLevelWindow;
} else {
mPluginPort.cgPort.context = nil;
mPluginPort.cgPort.window = nil;
}
} else {
if (topLevelWindow) {
mPluginPort.qdPort.port = ::GetWindowPort(topLevelWindow);
NSPoint viewOrigin = [mView convertPoint:NSZeroPoint toView:nil];
NSRect frame = [[window contentView] frame];
viewOrigin.y = frame.size.height - viewOrigin.y;
// need to convert view's origin to window coordinates.
// then, encode as "SetOrigin" ready values.
mPluginPort.qdPort.portx = (PRInt32)-viewOrigin.x;
mPluginPort.qdPort.porty = (PRInt32)-viewOrigin.y;
} else {
mPluginPort.qdPort.port = nil;
}
}
}
static void HideChildPluginViews(NSView* aView)
{
NSArray* subviews = [aView subviews];
@ -2277,6 +2274,9 @@ NSEvent* gLastDragEvent = nil;
- (void)setNativeWindow:(NSWindow*)aWindow
{
mWindow = aWindow;
if (aWindow && [self isPluginView] && mGeckoChild) {
mGeckoChild->UpdatePluginPort();
}
}
- (void)systemMetricsChanged

View File

@ -214,7 +214,7 @@ public:
NS_IMETHOD PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
nsIWidget *aWidget, PRBool aActivate);
NS_IMETHOD SetSizeMode(PRInt32 aMode);
NS_IMETHOD HideWindowChrome(PRBool aShouldHide);
NS_IMETHOD Resize(PRInt32 aWidth,PRInt32 aHeight, PRBool aRepaint);
NS_IMETHOD Resize(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, PRBool aRepaint);
NS_IMETHOD GetScreenBounds(nsIntRect &aRect);
@ -281,8 +281,9 @@ protected:
nsIToolkit *aToolkit,
nsWidgetInitData *aInitData,
nsNativeWidget aNativeWindow = nsnull);
nsresult CreateNativeWindow(const nsIntRect &aRect,
nsBorderStyle aBorderStyle);
nsresult CreateNativeWindow(const NSRect &aRect,
nsBorderStyle aBorderStyle,
PRBool aRectIsFrameRect);
nsresult CreatePopupContentView(const nsIntRect &aRect,
EVENT_CALLBACK aHandleEventFunction,
nsIDeviceContext *aContext,

View File

@ -124,6 +124,22 @@ nsCocoaWindow::nsCocoaWindow()
}
// Sometimes NSViews are removed from a window or moved to a new window.
// Since our ChildViews have their own mWindow field instead of always using
// [view window], we need to notify them when this happens.
static void SetNativeWindowOnSubviews(NSView *aNativeView, NSWindow *aWin)
{
if (!aNativeView)
return;
if ([aNativeView respondsToSelector:@selector(setNativeWindow:)])
[(NSView<mozView>*)aNativeView setNativeWindow:aWin];
NSArray *immediateSubviews = [aNativeView subviews];
int count = [immediateSubviews count];
for (int i = 0; i < count; ++i)
SetNativeWindowOnSubviews((NSView *)[immediateSubviews objectAtIndex:i], aWin);
}
// Under unusual circumstances, an nsCocoaWindow object can be destroyed
// before the nsChildView objects it contains are destroyed. But this will
// invalidate the (weak) mWindow variable in these nsChildView objects
@ -132,14 +148,7 @@ nsCocoaWindow::nsCocoaWindow()
// resolve bmo bug 479749.
static void TellNativeViewsGoodbye(NSView *aNativeView)
{
if (!aNativeView)
return;
if ([aNativeView respondsToSelector:@selector(setNativeWindow:)])
[(NSView<mozView>*)aNativeView setNativeWindow:nil];
NSArray *immediateSubviews = [aNativeView subviews];
int count = [immediateSubviews count];
for (int i = 0; i < count; ++i)
TellNativeViewsGoodbye((NSView *)[immediateSubviews objectAtIndex:i]);
SetNativeWindowOnSubviews(aNativeView, nil);
}
void nsCocoaWindow::DestroyNativeWindow()
@ -256,7 +265,8 @@ nsresult nsCocoaWindow::StandardCreate(nsIWidget *aParent,
// Create a window if we aren't given one, or if this should be a non-native popup.
if ((mWindowType == eWindowType_popup) ? !UseNativePopupWindows() : !aNativeWindow) {
nsresult rv = CreateNativeWindow(aRect, mBorderStyle);
nsresult rv = CreateNativeWindow(nsCocoaUtils::GeckoRectToCocoaRect(aRect),
mBorderStyle, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
if (mWindowType == eWindowType_popup) {
@ -265,10 +275,9 @@ nsresult nsCocoaWindow::StandardCreate(nsIWidget *aParent,
}
} else {
mWindow = (NSWindow*)aNativeWindow;
[[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
}
[[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
@ -296,8 +305,13 @@ static unsigned int WindowMaskForBorderStyle(nsBorderStyle aBorderStyle)
return mask;
}
nsresult nsCocoaWindow::CreateNativeWindow(const nsIntRect &aRect,
nsBorderStyle aBorderStyle)
// If aRectIsFrameRect, aRect specifies the frame rect of the new window.
// Otherwise, aRect.x/y specify the position of the window's frame relative to
// the bottom of the menubar and aRect.width/height specify the size of the
// content rect.
nsresult nsCocoaWindow::CreateNativeWindow(const NSRect &aRect,
nsBorderStyle aBorderStyle,
PRBool aRectIsFrameRect)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
@ -332,34 +346,39 @@ nsresult nsCocoaWindow::CreateNativeWindow(const nsIntRect &aRect,
return NS_ERROR_FAILURE;
}
/*
* We pass a content area rect to initialize the native Cocoa window. The
* content rect we give is the same size as the size we're given by gecko.
* The origin we're given for non-popup windows is moved down by the height
* of the menu bar so that an origin of (0,100) from gecko puts the window
* 100 pixels below the top of the available desktop area. We also move the
* origin down by the height of a title bar if it exists. This is so the
* origin that gecko gives us for the top-left of the window turns out to
* be the top-left of the window we create. This is how it was done in
* Carbon. If it ought to be different we'll probably need to look at all
* the callers.
*
* Note: This means that if you put a secondary screen on top of your main
* screen and open a window in the top screen, it'll be incorrectly shifted
* down by the height of the menu bar. Same thing would happen in Carbon.
*
* Note: If you pass a rect with 0,0 for an origin, the window ends up in a
* weird place for some reason. This stops that without breaking popups.
*/
NSRect rect = nsCocoaUtils::GeckoRectToCocoaRect(aRect);
NSRect contentRect;
// compensate for difference between frame and content area height (e.g. title bar)
NSRect newWindowFrame = [NSWindow frameRectForContentRect:rect styleMask:features];
if (aRectIsFrameRect) {
contentRect = [NSWindow contentRectForFrameRect:aRect styleMask:features];
} else {
/*
* We pass a content area rect to initialize the native Cocoa window. The
* content rect we give is the same size as the size we're given by gecko.
* The origin we're given for non-popup windows is moved down by the height
* of the menu bar so that an origin of (0,100) from gecko puts the window
* 100 pixels below the top of the available desktop area. We also move the
* origin down by the height of a title bar if it exists. This is so the
* origin that gecko gives us for the top-left of the window turns out to
* be the top-left of the window we create. This is how it was done in
* Carbon. If it ought to be different we'll probably need to look at all
* the callers.
*
* Note: This means that if you put a secondary screen on top of your main
* screen and open a window in the top screen, it'll be incorrectly shifted
* down by the height of the menu bar. Same thing would happen in Carbon.
*
* Note: If you pass a rect with 0,0 for an origin, the window ends up in a
* weird place for some reason. This stops that without breaking popups.
*/
// Compensate for difference between frame and content area height (e.g. title bar).
NSRect newWindowFrame = [NSWindow frameRectForContentRect:aRect styleMask:features];
rect.origin.y -= (newWindowFrame.size.height - rect.size.height);
contentRect = aRect;
contentRect.origin.y -= (newWindowFrame.size.height - aRect.size.height);
if (mWindowType != eWindowType_popup)
rect.origin.y -= ::GetMBarHeight();
if (mWindowType != eWindowType_popup)
contentRect.origin.y -= ::GetMBarHeight();
}
// NSLog(@"Top-level window being created at Cocoa rect: %f, %f, %f, %f\n",
// rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
@ -381,9 +400,16 @@ nsresult nsCocoaWindow::CreateNativeWindow(const nsIntRect &aRect,
windowClass = [BorderlessWindow class];
// Create the window
mWindow = [[windowClass alloc] initWithContentRect:rect styleMask:features
mWindow = [[windowClass alloc] initWithContentRect:contentRect styleMask:features
backing:NSBackingStoreBuffered defer:YES];
// Make sure that the content rect we gave has been honored.
NSRect wantedFrame = [mWindow frameRectForContentRect:contentRect];
if (!NSEqualRects([mWindow frame], wantedFrame)) {
// This can happen when the window is not on the primary screen.
[mWindow setFrame:wantedFrame display:NO];
}
if (mWindowType == eWindowType_invisible) {
[mWindow setLevel:kCGDesktopWindowLevelKey];
} else if (mWindowType == eWindowType_popup) {
@ -399,6 +425,7 @@ nsresult nsCocoaWindow::CreateNativeWindow(const nsIntRect &aRect,
mDelegate = [[WindowDelegate alloc] initWithGeckoWindow:this];
[mWindow setDelegate:mDelegate];
[[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
mWindowMadeHere = PR_TRUE;
return NS_OK;
@ -956,6 +983,61 @@ NS_METHOD nsCocoaWindow::SetSizeMode(PRInt32 aMode)
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
// This has to preserve the window's frame bounds.
// This method requires (as does the Windows impl.) that you call Resize shortly
// after calling HideWindowChrome. See bug 498835 for fixing this.
NS_IMETHODIMP nsCocoaWindow::HideWindowChrome(PRBool aShouldHide)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
if (!mWindowMadeHere ||
(mWindowType != eWindowType_toplevel && mWindowType != eWindowType_dialog))
return NS_ERROR_FAILURE;
BOOL isVisible = [mWindow isVisible];
// Remove child windows.
NSArray* childWindows = [mWindow childWindows];
NSEnumerator* enumerator = [childWindows objectEnumerator];
NSWindow* child = nil;
while ((child = [enumerator nextObject])) {
[mWindow removeChildWindow:child];
}
// Remove the content view.
NSView* contentView = [mWindow contentView];
[contentView retain];
[contentView removeFromSuperviewWithoutNeedingDisplay];
// Recreate the window with the right border style.
NSRect frameRect = [mWindow frame];
DestroyNativeWindow();
nsresult rv = CreateNativeWindow(frameRect, aShouldHide ? eBorderStyle_none : mBorderStyle, PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
// Reparent the content view.
[mWindow setContentView:contentView];
[contentView release];
SetNativeWindowOnSubviews(contentView, mWindow);
// Reparent child windows.
enumerator = [childWindows objectEnumerator];
while ((child = [enumerator nextObject])) {
[mWindow addChildWindow:child ordered:NSWindowAbove];
}
// Show the new window.
if (isVisible) {
rv = Show(PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
NS_IMETHODIMP nsCocoaWindow::Resize(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, PRBool aRepaint)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;