michael@0: /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsCocoaWindow.h" michael@0: michael@0: #include "NativeKeyBindings.h" michael@0: #include "TextInputHandler.h" michael@0: #include "nsObjCExceptions.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsWidgetsCID.h" michael@0: #include "nsIRollupListener.h" michael@0: #include "nsChildView.h" michael@0: #include "nsWindowMap.h" michael@0: #include "nsAppShell.h" michael@0: #include "nsIAppShellService.h" michael@0: #include "nsIBaseWindow.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsIXULWindow.h" michael@0: #include "nsToolkit.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsMenuBarX.h" michael@0: #include "nsMenuUtilsX.h" michael@0: #include "nsStyleConsts.h" michael@0: #include "nsNativeThemeColors.h" michael@0: #include "nsChildView.h" michael@0: #include "nsCocoaFeatures.h" michael@0: #include "nsIScreenManager.h" michael@0: #include "nsIWidgetListener.h" michael@0: #include "nsIPresShell.h" michael@0: michael@0: #include "gfxPlatform.h" michael@0: #include "qcms.h" michael@0: michael@0: #include "mozilla/AutoRestore.h" michael@0: #include "mozilla/BasicEvents.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: namespace layers { michael@0: class LayerManager; michael@0: } michael@0: } michael@0: using namespace mozilla::layers; michael@0: using namespace mozilla::widget; michael@0: using namespace mozilla; michael@0: michael@0: // defined in nsAppShell.mm michael@0: extern nsCocoaAppModalWindowList *gCocoaAppModalWindowList; michael@0: michael@0: int32_t gXULModalLevel = 0; michael@0: michael@0: // In principle there should be only one app-modal window at any given time. michael@0: // But sometimes, despite our best efforts, another window appears above the michael@0: // current app-modal window. So we need to keep a linked list of app-modal michael@0: // windows. (A non-sheet window that appears above an app-modal window is michael@0: // also made app-modal.) See nsCocoaWindow::SetModal(). michael@0: nsCocoaWindowList *gGeckoAppModalWindowList = NULL; michael@0: michael@0: // defined in nsMenuBarX.mm michael@0: extern NSMenu* sApplicationMenu; // Application menu shared by all menubars michael@0: michael@0: // defined in nsChildView.mm michael@0: extern BOOL gSomeMenuBarPainted; michael@0: michael@0: extern "C" { michael@0: // CGSPrivate.h michael@0: typedef NSInteger CGSConnection; michael@0: typedef NSInteger CGSWindow; michael@0: typedef NSUInteger CGSWindowFilterRef; michael@0: extern CGSConnection _CGSDefaultConnection(void); michael@0: extern CGError CGSSetWindowShadowAndRimParameters(const CGSConnection cid, CGSWindow wid, float standardDeviation, float density, int offsetX, int offsetY, unsigned int flags); michael@0: extern CGError CGSSetWindowBackgroundBlurRadius(CGSConnection cid, CGSWindow wid, NSUInteger blur); michael@0: } michael@0: michael@0: #define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1" michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(nsCocoaWindow, Inherited, nsPIWidgetCocoa) michael@0: michael@0: // A note on testing to see if your object is a sheet... michael@0: // |mWindowType == eWindowType_sheet| is true if your gecko nsIWidget is a sheet michael@0: // widget - whether or not the sheet is showing. |[mWindow isSheet]| will return michael@0: // true *only when the sheet is actually showing*. Choose your test wisely. michael@0: michael@0: static void RollUpPopups() michael@0: { michael@0: nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); michael@0: NS_ENSURE_TRUE_VOID(rollupListener); michael@0: nsCOMPtr rollupWidget = rollupListener->GetRollupWidget(); michael@0: if (!rollupWidget) michael@0: return; michael@0: rollupListener->Rollup(0, nullptr, nullptr); michael@0: } michael@0: michael@0: nsCocoaWindow::nsCocoaWindow() michael@0: : mParent(nullptr) michael@0: , mWindow(nil) michael@0: , mDelegate(nil) michael@0: , mSheetWindowParent(nil) michael@0: , mPopupContentView(nil) michael@0: , mShadowStyle(NS_STYLE_WINDOW_SHADOW_DEFAULT) michael@0: , mBackingScaleFactor(0.0) michael@0: , mAnimationType(nsIWidget::eGenericWindowAnimation) michael@0: , mWindowMadeHere(false) michael@0: , mSheetNeedsShow(false) michael@0: , mFullScreen(false) michael@0: , mInFullScreenTransition(false) michael@0: , mModal(false) michael@0: , mUsesNativeFullScreen(false) michael@0: , mIsAnimationSuppressed(false) michael@0: , mInReportMoveEvent(false) michael@0: , mInResize(false) michael@0: , mNumModalDescendents(0) michael@0: { michael@0: michael@0: } michael@0: michael@0: void nsCocoaWindow::DestroyNativeWindow() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!mWindow) michael@0: return; michael@0: michael@0: // We want to unhook the delegate here because we don't want events michael@0: // sent to it after this object has been destroyed. michael@0: [mWindow setDelegate:nil]; michael@0: [mWindow close]; michael@0: mWindow = nil; michael@0: [mDelegate autorelease]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: nsCocoaWindow::~nsCocoaWindow() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: // Notify the children that we're gone. Popup windows (e.g. tooltips) can michael@0: // have nsChildView children. 'kid' is an nsChildView object if and only if michael@0: // its 'type' is 'eWindowType_child' or 'eWindowType_plugin'. michael@0: // childView->ResetParent() can change our list of children while it's michael@0: // being iterated, so the way we iterate the list must allow for this. michael@0: for (nsIWidget* kid = mLastChild; kid;) { michael@0: nsWindowType kidType = kid->WindowType(); michael@0: if (kidType == eWindowType_child || kidType == eWindowType_plugin) { michael@0: nsChildView* childView = static_cast(kid); michael@0: kid = kid->GetPrevSibling(); michael@0: childView->ResetParent(); michael@0: } else { michael@0: nsCocoaWindow* childWindow = static_cast(kid); michael@0: childWindow->mParent = nullptr; michael@0: kid = kid->GetPrevSibling(); michael@0: } michael@0: } michael@0: michael@0: if (mWindow && mWindowMadeHere) { michael@0: DestroyNativeWindow(); michael@0: } michael@0: michael@0: NS_IF_RELEASE(mPopupContentView); michael@0: michael@0: // Deal with the possiblity that we're being destroyed while running modal. michael@0: if (mModal) { michael@0: NS_WARNING("Widget destroyed while running modal!"); michael@0: --gXULModalLevel; michael@0: NS_ASSERTION(gXULModalLevel >= 0, "Wierdness setting modality!"); michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // Find the screen that overlaps aRect the most, michael@0: // if none are found default to the mainScreen. michael@0: static NSScreen *FindTargetScreenForRect(const nsIntRect& aRect) michael@0: { michael@0: NSScreen *targetScreen = [NSScreen mainScreen]; michael@0: NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator]; michael@0: int largestIntersectArea = 0; michael@0: while (NSScreen *screen = [screenEnum nextObject]) { michael@0: nsIntRect screenRect(nsCocoaUtils::CocoaRectToGeckoRect([screen visibleFrame])); michael@0: screenRect = screenRect.Intersect(aRect); michael@0: int area = screenRect.width * screenRect.height; michael@0: if (area > largestIntersectArea) { michael@0: largestIntersectArea = area; michael@0: targetScreen = screen; michael@0: } michael@0: } michael@0: return targetScreen; michael@0: } michael@0: michael@0: // fits the rect to the screen that contains the largest area of it, michael@0: // or to aScreen if a screen is passed in michael@0: // NB: this operates with aRect in global display pixels michael@0: static void FitRectToVisibleAreaForScreen(nsIntRect &aRect, NSScreen *aScreen, michael@0: bool aUsesNativeFullScreen) michael@0: { michael@0: if (!aScreen) { michael@0: aScreen = FindTargetScreenForRect(aRect); michael@0: } michael@0: michael@0: nsIntRect screenBounds(nsCocoaUtils::CocoaRectToGeckoRect([aScreen visibleFrame])); michael@0: michael@0: if (aRect.width > screenBounds.width) { michael@0: aRect.width = screenBounds.width; michael@0: } michael@0: if (aRect.height > screenBounds.height) { michael@0: aRect.height = screenBounds.height; michael@0: } michael@0: michael@0: if (aRect.x - screenBounds.x + aRect.width > screenBounds.width) { michael@0: aRect.x += screenBounds.width - (aRect.x - screenBounds.x + aRect.width); michael@0: } michael@0: if (aRect.y - screenBounds.y + aRect.height > screenBounds.height) { michael@0: aRect.y += screenBounds.height - (aRect.y - screenBounds.y + aRect.height); michael@0: } michael@0: michael@0: // If the left/top edge of the window is off the screen in either direction, michael@0: // then set the window to start at the left/top edge of the screen. michael@0: if (aRect.x < screenBounds.x || aRect.x > (screenBounds.x + screenBounds.width)) { michael@0: aRect.x = screenBounds.x; michael@0: } michael@0: if (aRect.y < screenBounds.y || aRect.y > (screenBounds.y + screenBounds.height)) { michael@0: aRect.y = screenBounds.y; michael@0: } michael@0: michael@0: // If aRect is filling the screen and the window supports native (Lion-style) michael@0: // fullscreen mode, reduce aRect's height and shift it down by 22 pixels michael@0: // (nominally the height of the menu bar or of a window's title bar). For michael@0: // some reason this works around bug 740923. Yes, it's a bodacious hack. michael@0: // But until we know more it will have to do. michael@0: if (aUsesNativeFullScreen && aRect.y == 0 && aRect.height == screenBounds.height) { michael@0: aRect.y = 22; michael@0: aRect.height -= 22; michael@0: } michael@0: } michael@0: michael@0: // Some applications like Camino use native popup windows michael@0: // (native context menus, native tooltips) michael@0: static bool UseNativePopupWindows() michael@0: { michael@0: #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS michael@0: return true; michael@0: #else michael@0: return false; michael@0: #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */ michael@0: } michael@0: michael@0: // aRect here is specified in global display pixels michael@0: nsresult nsCocoaWindow::Create(nsIWidget *aParent, michael@0: nsNativeWidget aNativeParent, michael@0: const nsIntRect &aRect, michael@0: nsDeviceContext *aContext, michael@0: nsWidgetInitData *aInitData) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: // Because the hidden window is created outside of an event loop, michael@0: // we have to provide an autorelease pool (see bug 559075). michael@0: nsAutoreleasePool localPool; michael@0: michael@0: nsIntRect newBounds = aRect; michael@0: FitRectToVisibleAreaForScreen(newBounds, nullptr, mUsesNativeFullScreen); michael@0: michael@0: // Set defaults which can be overriden from aInitData in BaseCreate michael@0: mWindowType = eWindowType_toplevel; michael@0: mBorderStyle = eBorderStyle_default; michael@0: michael@0: // Ensure that the toolkit is created. michael@0: nsToolkit::GetToolkit(); michael@0: michael@0: // newBounds is still display (global screen) pixels at this point; michael@0: // fortunately, BaseCreate doesn't actually use it so we don't michael@0: // need to worry about trying to convert it to device pixels michael@0: // when we don't have a window (or dev context, perhaps) yet michael@0: Inherited::BaseCreate(aParent, newBounds, aContext, aInitData); michael@0: michael@0: mParent = aParent; michael@0: michael@0: // Applications that use native popups don't want us to create popup windows. michael@0: if ((mWindowType == eWindowType_popup) && UseNativePopupWindows()) michael@0: return NS_OK; michael@0: michael@0: nsresult rv = michael@0: CreateNativeWindow(nsCocoaUtils::GeckoRectToCocoaRect(newBounds), michael@0: mBorderStyle, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (mWindowType == eWindowType_popup) { michael@0: if (aInitData->mMouseTransparent) { michael@0: [mWindow setIgnoresMouseEvents:YES]; michael@0: } michael@0: // now we can convert newBounds to device pixels for the window we created, michael@0: // as the child view expects a rect expressed in the dev pix of its parent michael@0: double scale = BackingScaleFactor(); michael@0: newBounds.x *= scale; michael@0: newBounds.y *= scale; michael@0: newBounds.width *= scale; michael@0: newBounds.height *= scale; michael@0: return CreatePopupContentView(newBounds, aContext); michael@0: } michael@0: michael@0: mIsAnimationSuppressed = aInitData->mIsAnimationSuppressed; michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: static unsigned int WindowMaskForBorderStyle(nsBorderStyle aBorderStyle) michael@0: { michael@0: bool allOrDefault = (aBorderStyle == eBorderStyle_all || michael@0: aBorderStyle == eBorderStyle_default); michael@0: michael@0: /* Apple's docs on NSWindow styles say that "a window's style mask should michael@0: * include NSTitledWindowMask if it includes any of the others [besides michael@0: * NSBorderlessWindowMask]". This implies that a borderless window michael@0: * shouldn't have any other styles than NSBorderlessWindowMask. michael@0: */ michael@0: if (!allOrDefault && !(aBorderStyle & eBorderStyle_title)) michael@0: return NSBorderlessWindowMask; michael@0: michael@0: unsigned int mask = NSTitledWindowMask; michael@0: if (allOrDefault || aBorderStyle & eBorderStyle_close) michael@0: mask |= NSClosableWindowMask; michael@0: if (allOrDefault || aBorderStyle & eBorderStyle_minimize) michael@0: mask |= NSMiniaturizableWindowMask; michael@0: if (allOrDefault || aBorderStyle & eBorderStyle_resizeh) michael@0: mask |= NSResizableWindowMask; michael@0: michael@0: return mask; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::ReparentNativeWidget(nsIWidget* aNewParent) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: // If aRectIsFrameRect, aRect specifies the frame rect of the new window. michael@0: // Otherwise, aRect.x/y specify the position of the window's frame relative to michael@0: // the bottom of the menubar and aRect.width/height specify the size of the michael@0: // content rect. michael@0: nsresult nsCocoaWindow::CreateNativeWindow(const NSRect &aRect, michael@0: nsBorderStyle aBorderStyle, michael@0: bool aRectIsFrameRect) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: // We default to NSBorderlessWindowMask, add features if needed. michael@0: unsigned int features = NSBorderlessWindowMask; michael@0: michael@0: // Configure the window we will create based on the window type. michael@0: switch (mWindowType) michael@0: { michael@0: case eWindowType_invisible: michael@0: case eWindowType_child: michael@0: case eWindowType_plugin: michael@0: break; michael@0: case eWindowType_popup: michael@0: if (aBorderStyle != eBorderStyle_default && mBorderStyle & eBorderStyle_title) { michael@0: features |= NSTitledWindowMask; michael@0: if (aBorderStyle & eBorderStyle_close) { michael@0: features |= NSClosableWindowMask; michael@0: } michael@0: } michael@0: break; michael@0: case eWindowType_toplevel: michael@0: case eWindowType_dialog: michael@0: features = WindowMaskForBorderStyle(aBorderStyle); michael@0: break; michael@0: case eWindowType_sheet: michael@0: if (mParent->WindowType() != eWindowType_invisible && michael@0: aBorderStyle & eBorderStyle_resizeh) { michael@0: features = NSResizableWindowMask; michael@0: } michael@0: else { michael@0: features = NSMiniaturizableWindowMask; michael@0: } michael@0: features |= NSTitledWindowMask; michael@0: break; michael@0: default: michael@0: NS_ERROR("Unhandled window type!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NSRect contentRect; michael@0: michael@0: if (aRectIsFrameRect) { michael@0: contentRect = [NSWindow contentRectForFrameRect:aRect styleMask:features]; michael@0: } else { michael@0: /* michael@0: * We pass a content area rect to initialize the native Cocoa window. The michael@0: * content rect we give is the same size as the size we're given by gecko. michael@0: * The origin we're given for non-popup windows is moved down by the height michael@0: * of the menu bar so that an origin of (0,100) from gecko puts the window michael@0: * 100 pixels below the top of the available desktop area. We also move the michael@0: * origin down by the height of a title bar if it exists. This is so the michael@0: * origin that gecko gives us for the top-left of the window turns out to michael@0: * be the top-left of the window we create. This is how it was done in michael@0: * Carbon. If it ought to be different we'll probably need to look at all michael@0: * the callers. michael@0: * michael@0: * Note: This means that if you put a secondary screen on top of your main michael@0: * screen and open a window in the top screen, it'll be incorrectly shifted michael@0: * down by the height of the menu bar. Same thing would happen in Carbon. michael@0: * michael@0: * Note: If you pass a rect with 0,0 for an origin, the window ends up in a michael@0: * weird place for some reason. This stops that without breaking popups. michael@0: */ michael@0: // Compensate for difference between frame and content area height (e.g. title bar). michael@0: NSRect newWindowFrame = [NSWindow frameRectForContentRect:aRect styleMask:features]; michael@0: michael@0: contentRect = aRect; michael@0: contentRect.origin.y -= (newWindowFrame.size.height - aRect.size.height); michael@0: michael@0: if (mWindowType != eWindowType_popup) michael@0: contentRect.origin.y -= [[NSApp mainMenu] menuBarHeight]; michael@0: } michael@0: michael@0: // NSLog(@"Top-level window being created at Cocoa rect: %f, %f, %f, %f\n", michael@0: // rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); michael@0: michael@0: Class windowClass = [BaseWindow class]; michael@0: // If we have a titlebar on a top-level window, we want to be able to control the michael@0: // titlebar color (for unified windows), so use the special ToolbarWindow class. michael@0: // Note that we need to check the window type because we mark sheets as michael@0: // having titlebars. michael@0: if ((mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) && michael@0: (features & NSTitledWindowMask)) michael@0: windowClass = [ToolbarWindow class]; michael@0: // If we're a popup window we need to use the PopupWindow class. michael@0: else if (mWindowType == eWindowType_popup) michael@0: windowClass = [PopupWindow class]; michael@0: // If we're a non-popup borderless window we need to use the michael@0: // BorderlessWindow class. michael@0: else if (features == NSBorderlessWindowMask) michael@0: windowClass = [BorderlessWindow class]; michael@0: michael@0: // Create the window michael@0: mWindow = [[windowClass alloc] initWithContentRect:contentRect styleMask:features michael@0: backing:NSBackingStoreBuffered defer:YES]; michael@0: michael@0: // setup our notification delegate. Note that setDelegate: does NOT retain. michael@0: mDelegate = [[WindowDelegate alloc] initWithGeckoWindow:this]; michael@0: [mWindow setDelegate:mDelegate]; michael@0: michael@0: // Make sure that the content rect we gave has been honored. michael@0: NSRect wantedFrame = [mWindow frameRectForContentRect:contentRect]; michael@0: if (!NSEqualRects([mWindow frame], wantedFrame)) { michael@0: // This can happen when the window is not on the primary screen. michael@0: [mWindow setFrame:wantedFrame display:NO]; michael@0: } michael@0: UpdateBounds(); michael@0: michael@0: if (mWindowType == eWindowType_invisible) { michael@0: [mWindow setLevel:kCGDesktopWindowLevelKey]; michael@0: } else if (mWindowType == eWindowType_popup) { michael@0: SetPopupWindowLevel(); michael@0: [mWindow setHasShadow:YES]; michael@0: } michael@0: michael@0: [mWindow setBackgroundColor:[NSColor clearColor]]; michael@0: #ifdef MOZ_B2G michael@0: // In B2G, we don't create popups and we need OMTC to work (because out of michael@0: // process compositing depends on it). Therefore, we don't need our windows michael@0: // to be transparent. michael@0: [mWindow setOpaque:YES]; michael@0: #else michael@0: [mWindow setOpaque:NO]; michael@0: #endif michael@0: [mWindow setContentMinSize:NSMakeSize(60, 60)]; michael@0: [mWindow disableCursorRects]; michael@0: michael@0: // Make sure the window starts out not draggable by the background. michael@0: // We will turn it on as necessary. michael@0: [mWindow setMovableByWindowBackground:NO]; michael@0: michael@0: [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow]; michael@0: mWindowMadeHere = true; michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::CreatePopupContentView(const nsIntRect &aRect, michael@0: nsDeviceContext *aContext) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: // We need to make our content view a ChildView. michael@0: mPopupContentView = new nsChildView(); michael@0: if (!mPopupContentView) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NS_ADDREF(mPopupContentView); michael@0: michael@0: nsIWidget* thisAsWidget = static_cast(this); michael@0: mPopupContentView->Create(thisAsWidget, nullptr, aRect, aContext, nullptr); michael@0: michael@0: ChildView* newContentView = (ChildView*)mPopupContentView->GetNativeData(NS_NATIVE_WIDGET); michael@0: [mWindow setContentView:newContentView]; michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::Destroy() michael@0: { michael@0: // If we don't hide here we run into problems with panels, this is not ideal. michael@0: // (Bug 891424) michael@0: Show(false); michael@0: michael@0: if (mPopupContentView) michael@0: mPopupContentView->Destroy(); michael@0: michael@0: nsBaseWidget::Destroy(); michael@0: // nsBaseWidget::Destroy() calls GetParent()->RemoveChild(this). But we michael@0: // don't implement GetParent(), so we need to do the equivalent here. michael@0: if (mParent) { michael@0: mParent->RemoveChild(this); michael@0: } michael@0: nsBaseWidget::OnDestroy(); michael@0: michael@0: if (mFullScreen) { michael@0: // On Lion we don't have to mess with the OS chrome when in Full Screen michael@0: // mode. But we do have to destroy the native window here (and not wait michael@0: // for that to happen in our destructor). We don't switch away from the michael@0: // native window's space until the window is destroyed, and otherwise this michael@0: // might not happen for several seconds (because at least one object michael@0: // holding a reference to ourselves is usually waiting to be garbage- michael@0: // collected). See bug 757618. michael@0: if (mUsesNativeFullScreen) { michael@0: DestroyNativeWindow(); michael@0: } else if (mWindow) { michael@0: nsCocoaUtils::HideOSChromeOnScreen(false, [mWindow screen]); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIWidget* nsCocoaWindow::GetSheetWindowParent(void) michael@0: { michael@0: if (mWindowType != eWindowType_sheet) michael@0: return nullptr; michael@0: nsCocoaWindow *parent = static_cast(mParent); michael@0: while (parent && (parent->mWindowType == eWindowType_sheet)) michael@0: parent = static_cast(parent->mParent); michael@0: return parent; michael@0: } michael@0: michael@0: void* nsCocoaWindow::GetNativeData(uint32_t aDataType) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL; michael@0: michael@0: void* retVal = nullptr; michael@0: michael@0: switch (aDataType) { michael@0: // to emulate how windows works, we always have to return a NSView michael@0: // for NS_NATIVE_WIDGET michael@0: case NS_NATIVE_WIDGET: michael@0: case NS_NATIVE_DISPLAY: michael@0: retVal = [mWindow contentView]; michael@0: break; michael@0: michael@0: case NS_NATIVE_WINDOW: michael@0: retVal = mWindow; michael@0: break; michael@0: michael@0: case NS_NATIVE_GRAPHIC: michael@0: // There isn't anything that makes sense to return here, michael@0: // and it doesn't matter so just return nullptr. michael@0: NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a top-level window!"); michael@0: break; michael@0: } michael@0: michael@0: return retVal; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL; michael@0: } michael@0: michael@0: bool nsCocoaWindow::IsVisible() const michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: return (mWindow && ([mWindow isVisibleOrBeingShown] || mSheetNeedsShow)); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::SetModal(bool aState) michael@0: { michael@0: if (!mWindow) michael@0: return NS_OK; michael@0: michael@0: // This is used during startup (outside the event loop) when creating michael@0: // the add-ons compatibility checking dialog and the profile manager UI; michael@0: // therefore, it needs to provide an autorelease pool to avoid cocoa michael@0: // objects leaking. michael@0: nsAutoreleasePool localPool; michael@0: michael@0: mModal = aState; michael@0: nsCocoaWindow *aParent = static_cast(mParent); michael@0: if (aState) { michael@0: ++gXULModalLevel; michael@0: if (gCocoaAppModalWindowList) michael@0: gCocoaAppModalWindowList->PushGecko(mWindow, this); michael@0: // When a non-sheet window gets "set modal", make the window(s) that it michael@0: // appears over behave as they should. We can't rely on native methods to michael@0: // do this, for the following reason: The OS runs modal non-sheet windows michael@0: // in an event loop (using [NSApplication runModalForWindow:] or similar michael@0: // methods) that's incompatible with the modal event loop in nsXULWindow:: michael@0: // ShowModal() (each of these event loops is "exclusive", and can't run at michael@0: // the same time as other (similar) event loops). michael@0: if (mWindowType != eWindowType_sheet) { michael@0: while (aParent) { michael@0: if (aParent->mNumModalDescendents++ == 0) { michael@0: NSWindow *aWindow = aParent->GetCocoaWindow(); michael@0: if (aParent->mWindowType != eWindowType_invisible) { michael@0: [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:NO]; michael@0: [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:NO]; michael@0: [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:NO]; michael@0: } michael@0: } michael@0: aParent = static_cast(aParent->mParent); michael@0: } michael@0: [mWindow setLevel:NSModalPanelWindowLevel]; michael@0: nsCocoaWindowList *windowList = new nsCocoaWindowList; michael@0: if (windowList) { michael@0: windowList->window = this; // Don't ADDREF michael@0: windowList->prev = gGeckoAppModalWindowList; michael@0: gGeckoAppModalWindowList = windowList; michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: --gXULModalLevel; michael@0: NS_ASSERTION(gXULModalLevel >= 0, "Mismatched call to nsCocoaWindow::SetModal(false)!"); michael@0: if (gCocoaAppModalWindowList) michael@0: gCocoaAppModalWindowList->PopGecko(mWindow, this); michael@0: if (mWindowType != eWindowType_sheet) { michael@0: while (aParent) { michael@0: if (--aParent->mNumModalDescendents == 0) { michael@0: NSWindow *aWindow = aParent->GetCocoaWindow(); michael@0: if (aParent->mWindowType != eWindowType_invisible) { michael@0: [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:YES]; michael@0: [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:YES]; michael@0: [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:YES]; michael@0: } michael@0: } michael@0: NS_ASSERTION(aParent->mNumModalDescendents >= 0, "Widget hierarchy changed while modal!"); michael@0: aParent = static_cast(aParent->mParent); michael@0: } michael@0: if (gGeckoAppModalWindowList) { michael@0: NS_ASSERTION(gGeckoAppModalWindowList->window == this, "Widget hierarchy changed while modal!"); michael@0: nsCocoaWindowList *saved = gGeckoAppModalWindowList; michael@0: gGeckoAppModalWindowList = gGeckoAppModalWindowList->prev; michael@0: delete saved; // "window" not ADDREFed michael@0: } michael@0: if (mWindowType == eWindowType_popup) michael@0: SetPopupWindowLevel(); michael@0: else michael@0: [mWindow setLevel:NSNormalWindowLevel]; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Hide or show this window michael@0: NS_IMETHODIMP nsCocoaWindow::Show(bool bState) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: if (!mWindow) michael@0: return NS_OK; michael@0: michael@0: // We need to re-execute sometimes in order to bring already-visible michael@0: // windows forward. michael@0: if (!mSheetNeedsShow && !bState && ![mWindow isVisible]) michael@0: return NS_OK; michael@0: michael@0: // Protect against re-entering. michael@0: if (bState && [mWindow isBeingShown]) michael@0: return NS_OK; michael@0: michael@0: [mWindow setBeingShown:bState]; michael@0: michael@0: nsIWidget* parentWidget = mParent; michael@0: nsCOMPtr piParentWidget(do_QueryInterface(parentWidget)); michael@0: NSWindow* nativeParentWindow = (parentWidget) ? michael@0: (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW) : nil; michael@0: michael@0: if (bState && !mBounds.IsEmpty()) { michael@0: if (mPopupContentView) { michael@0: // Ensure our content view is visible. We never need to hide it. michael@0: mPopupContentView->Show(true); michael@0: } michael@0: michael@0: if (mWindowType == eWindowType_sheet) { michael@0: // bail if no parent window (its basically what we do in Carbon) michael@0: if (!nativeParentWindow || !piParentWidget) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NSWindow* topNonSheetWindow = nativeParentWindow; michael@0: michael@0: // If this sheet is the child of another sheet, hide the parent so that michael@0: // this sheet can be displayed. Leave the parent mSheetNeedsShow alone, michael@0: // that is only used to handle sibling sheet contention. The parent will michael@0: // return once there are no more child sheets. michael@0: bool parentIsSheet = false; michael@0: if (NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) && michael@0: parentIsSheet) { michael@0: piParentWidget->GetSheetWindowParent(&topNonSheetWindow); michael@0: [NSApp endSheet:nativeParentWindow]; michael@0: } michael@0: michael@0: nsCocoaWindow* sheetShown = nullptr; michael@0: if (NS_SUCCEEDED(piParentWidget->GetChildSheet(true, &sheetShown)) && michael@0: (!sheetShown || sheetShown == this)) { michael@0: // If this sheet is already the sheet actually being shown, don't michael@0: // tell it to show again. Otherwise the number of calls to michael@0: // [NSApp beginSheet...] won't match up with [NSApp endSheet...]. michael@0: if (![mWindow isVisible]) { michael@0: mSheetNeedsShow = false; michael@0: mSheetWindowParent = topNonSheetWindow; michael@0: // Only set contextInfo if our parent isn't a sheet. michael@0: NSWindow* contextInfo = parentIsSheet ? nil : mSheetWindowParent; michael@0: [TopLevelWindowData deactivateInWindow:mSheetWindowParent]; michael@0: [NSApp beginSheet:mWindow michael@0: modalForWindow:mSheetWindowParent michael@0: modalDelegate:mDelegate michael@0: didEndSelector:@selector(didEndSheet:returnCode:contextInfo:) michael@0: contextInfo:contextInfo]; michael@0: [TopLevelWindowData activateInWindow:mWindow]; michael@0: SendSetZLevelEvent(); michael@0: } michael@0: } michael@0: else { michael@0: // A sibling of this sheet is active, don't show this sheet yet. michael@0: // When the active sheet hides, its brothers and sisters that have michael@0: // mSheetNeedsShow set will have their opportunities to display. michael@0: mSheetNeedsShow = true; michael@0: } michael@0: } michael@0: else if (mWindowType == eWindowType_popup) { michael@0: // If a popup window is shown after being hidden, it needs to be "reset" michael@0: // for it to receive any mouse events aside from mouse-moved events michael@0: // (because it was removed from the "window cache" when it was hidden michael@0: // -- see below). Setting the window number to -1 and then back to its michael@0: // original value seems to accomplish this. The idea was "borrowed" michael@0: // from the Java Embedding Plugin. michael@0: NSInteger windowNumber = [mWindow windowNumber]; michael@0: [mWindow _setWindowNumber:-1]; michael@0: [mWindow _setWindowNumber:windowNumber]; michael@0: // For reasons that aren't yet clear, calls to [NSWindow orderFront:] or michael@0: // [NSWindow makeKeyAndOrderFront:] can sometimes trigger "Error (1000) michael@0: // creating CGSWindow", which in turn triggers an internal inconsistency michael@0: // NSException. These errors shouldn't be fatal. So we need to wrap michael@0: // calls to ...orderFront: in LOGONLY blocks. See bmo bug 470864. michael@0: NS_OBJC_BEGIN_TRY_LOGONLY_BLOCK; michael@0: [[mWindow contentView] setNeedsDisplay:YES]; michael@0: [mWindow orderFront:nil]; michael@0: NS_OBJC_END_TRY_LOGONLY_BLOCK; michael@0: SendSetZLevelEvent(); michael@0: AdjustWindowShadow(); michael@0: SetWindowBackgroundBlur(); michael@0: // If our popup window is a non-native context menu, tell the OS (and michael@0: // other programs) that a menu has opened. This is how the OS knows to michael@0: // close other programs' context menus when ours open. michael@0: if ([mWindow isKindOfClass:[PopupWindow class]] && michael@0: [(PopupWindow*) mWindow isContextMenu]) { michael@0: [[NSDistributedNotificationCenter defaultCenter] michael@0: postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification" michael@0: object:@"org.mozilla.gecko.PopupWindow"]; michael@0: } michael@0: michael@0: // If a parent window was supplied and this is a popup at the parent michael@0: // level, set its child window. This will cause the child window to michael@0: // appear above the parent and move when the parent does. Setting this michael@0: // needs to happen after the _setWindowNumber calls above, otherwise the michael@0: // window doesn't focus properly. michael@0: if (nativeParentWindow && mPopupLevel == ePopupLevelParent) michael@0: [nativeParentWindow addChildWindow:mWindow michael@0: ordered:NSWindowAbove]; michael@0: } michael@0: else { michael@0: NS_OBJC_BEGIN_TRY_LOGONLY_BLOCK; michael@0: if (mWindowType == eWindowType_toplevel && michael@0: [mWindow respondsToSelector:@selector(setAnimationBehavior:)]) { michael@0: NSWindowAnimationBehavior behavior; michael@0: if (mIsAnimationSuppressed) { michael@0: behavior = NSWindowAnimationBehaviorNone; michael@0: } else { michael@0: switch (mAnimationType) { michael@0: case nsIWidget::eDocumentWindowAnimation: michael@0: behavior = NSWindowAnimationBehaviorDocumentWindow; michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("unexpected mAnimationType value"); michael@0: // fall through michael@0: case nsIWidget::eGenericWindowAnimation: michael@0: behavior = NSWindowAnimationBehaviorDefault; michael@0: break; michael@0: } michael@0: } michael@0: [mWindow setAnimationBehavior:behavior]; michael@0: } michael@0: [mWindow makeKeyAndOrderFront:nil]; michael@0: NS_OBJC_END_TRY_LOGONLY_BLOCK; michael@0: SendSetZLevelEvent(); michael@0: } michael@0: } michael@0: else { michael@0: // roll up any popups if a top-level window is going away michael@0: if (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) michael@0: RollUpPopups(); michael@0: michael@0: // now get rid of the window/sheet michael@0: if (mWindowType == eWindowType_sheet) { michael@0: if (mSheetNeedsShow) { michael@0: // This is an attempt to hide a sheet that never had a chance to michael@0: // be shown. There's nothing to do other than make sure that it michael@0: // won't show. michael@0: mSheetNeedsShow = false; michael@0: } michael@0: else { michael@0: // get sheet's parent *before* hiding the sheet (which breaks the linkage) michael@0: NSWindow* sheetParent = mSheetWindowParent; michael@0: michael@0: // hide the sheet michael@0: [NSApp endSheet:mWindow]; michael@0: michael@0: [TopLevelWindowData deactivateInWindow:mWindow]; michael@0: michael@0: nsCocoaWindow* siblingSheetToShow = nullptr; michael@0: bool parentIsSheet = false; michael@0: michael@0: if (nativeParentWindow && piParentWidget && michael@0: NS_SUCCEEDED(piParentWidget->GetChildSheet(false, &siblingSheetToShow)) && michael@0: siblingSheetToShow) { michael@0: // First, give sibling sheets an opportunity to show. michael@0: siblingSheetToShow->Show(true); michael@0: } michael@0: else if (nativeParentWindow && piParentWidget && michael@0: NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) && michael@0: parentIsSheet) { michael@0: // Only set contextInfo if the parent of the parent sheet we're about michael@0: // to restore isn't itself a sheet. michael@0: NSWindow* contextInfo = sheetParent; michael@0: nsIWidget* grandparentWidget = nil; michael@0: if (NS_SUCCEEDED(piParentWidget->GetRealParent(&grandparentWidget)) && grandparentWidget) { michael@0: nsCOMPtr piGrandparentWidget(do_QueryInterface(grandparentWidget)); michael@0: bool grandparentIsSheet = false; michael@0: if (piGrandparentWidget && NS_SUCCEEDED(piGrandparentWidget->GetIsSheet(&grandparentIsSheet)) && michael@0: grandparentIsSheet) { michael@0: contextInfo = nil; michael@0: } michael@0: } michael@0: // If there are no sibling sheets, but the parent is a sheet, restore michael@0: // it. It wasn't sent any deactivate events when it was hidden, so michael@0: // don't call through Show, just let the OS put it back up. michael@0: [NSApp beginSheet:nativeParentWindow michael@0: modalForWindow:sheetParent michael@0: modalDelegate:[nativeParentWindow delegate] michael@0: didEndSelector:@selector(didEndSheet:returnCode:contextInfo:) michael@0: contextInfo:contextInfo]; michael@0: } michael@0: else { michael@0: // Sheet, that was hard. No more siblings or parents, going back michael@0: // to a real window. michael@0: NS_OBJC_BEGIN_TRY_LOGONLY_BLOCK; michael@0: [sheetParent makeKeyAndOrderFront:nil]; michael@0: NS_OBJC_END_TRY_LOGONLY_BLOCK; michael@0: } michael@0: SendSetZLevelEvent(); michael@0: } michael@0: } michael@0: else { michael@0: // If the window is a popup window with a parent window we need to michael@0: // unhook it here before ordering it out. When you order out the child michael@0: // of a window it hides the parent window. michael@0: if (mWindowType == eWindowType_popup && nativeParentWindow) michael@0: [nativeParentWindow removeChildWindow:mWindow]; michael@0: michael@0: [mWindow orderOut:nil]; michael@0: // Unless it's explicitly removed from NSApp's "window cache", a popup michael@0: // window will keep receiving mouse-moved events even after it's been michael@0: // "ordered out" (instead of the browser window that was underneath it, michael@0: // until you click on that window). This is bmo bug 378645, but it's michael@0: // surely an Apple bug. The "window cache" is an undocumented subsystem, michael@0: // all of whose methods are included in the NSWindowCache category of michael@0: // the NSApplication class (in header files generated using class-dump). michael@0: // This workaround was "borrowed" from the Java Embedding Plugin (which michael@0: // uses it for a different purpose). michael@0: if (mWindowType == eWindowType_popup) michael@0: [NSApp _removeWindowFromCache:mWindow]; michael@0: michael@0: // If our popup window is a non-native context menu, tell the OS (and michael@0: // other programs) that a menu has closed. michael@0: if ([mWindow isKindOfClass:[PopupWindow class]] && michael@0: [(PopupWindow*) mWindow isContextMenu]) { michael@0: [[NSDistributedNotificationCenter defaultCenter] michael@0: postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" michael@0: object:@"org.mozilla.gecko.PopupWindow"]; michael@0: } michael@0: } michael@0: } michael@0: michael@0: [mWindow setBeingShown:NO]; michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: struct ShadowParams { michael@0: float standardDeviation; michael@0: float density; michael@0: int offsetX; michael@0: int offsetY; michael@0: unsigned int flags; michael@0: }; michael@0: michael@0: // These numbers have been determined by looking at the results of michael@0: // CGSGetWindowShadowAndRimParameters for native window types. michael@0: static const ShadowParams kWindowShadowParameters[] = { michael@0: { 0.0f, 0.0f, 0, 0, 0 }, // none michael@0: { 8.0f, 0.5f, 0, 6, 1 }, // default michael@0: { 10.0f, 0.44f, 0, 10, 512 }, // menu michael@0: { 8.0f, 0.5f, 0, 6, 1 }, // tooltip michael@0: { 4.0f, 0.6f, 0, 4, 512 } // sheet michael@0: }; michael@0: michael@0: // This method will adjust the window shadow style for popup windows after michael@0: // they have been made visible. Before they're visible, their window number michael@0: // might be -1, which is not useful. michael@0: // We won't attempt to change the shadow for windows that can acquire key state michael@0: // since OS X will reset the shadow whenever that happens. michael@0: void michael@0: nsCocoaWindow::AdjustWindowShadow() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!mWindow || ![mWindow isVisible] || ![mWindow hasShadow] || michael@0: [mWindow canBecomeKeyWindow] || [mWindow windowNumber] == -1) michael@0: return; michael@0: michael@0: const ShadowParams& params = kWindowShadowParameters[mShadowStyle]; michael@0: CGSConnection cid = _CGSDefaultConnection(); michael@0: CGSSetWindowShadowAndRimParameters(cid, [mWindow windowNumber], michael@0: params.standardDeviation, params.density, michael@0: params.offsetX, params.offsetY, michael@0: params.flags); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: static const NSUInteger kWindowBackgroundBlurRadius = 4; michael@0: michael@0: void michael@0: nsCocoaWindow::SetWindowBackgroundBlur() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!mWindow || ![mWindow isVisible] || [mWindow windowNumber] == -1) michael@0: return; michael@0: michael@0: // Only blur the background of menus and fake sheets. michael@0: if (mShadowStyle != NS_STYLE_WINDOW_SHADOW_MENU && michael@0: mShadowStyle != NS_STYLE_WINDOW_SHADOW_SHEET) michael@0: return; michael@0: michael@0: CGSConnection cid = _CGSDefaultConnection(); michael@0: CGSSetWindowBackgroundBlurRadius(cid, [mWindow windowNumber], kWindowBackgroundBlurRadius); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: nsresult michael@0: nsCocoaWindow::ConfigureChildren(const nsTArray& aConfigurations) michael@0: { michael@0: if (mPopupContentView) { michael@0: mPopupContentView->ConfigureChildren(aConfigurations); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: LayerManager* michael@0: nsCocoaWindow::GetLayerManager(PLayerTransactionChild* aShadowManager, michael@0: LayersBackend aBackendHint, michael@0: LayerManagerPersistence aPersistence, michael@0: bool* aAllowRetaining) michael@0: { michael@0: if (mPopupContentView) { michael@0: return mPopupContentView->GetLayerManager(aShadowManager, michael@0: aBackendHint, michael@0: aPersistence, michael@0: aAllowRetaining); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: nsTransparencyMode nsCocoaWindow::GetTransparencyMode() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: return (!mWindow || [mWindow isOpaque]) ? eTransparencyOpaque : eTransparencyTransparent; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(eTransparencyOpaque); michael@0: } michael@0: michael@0: // This is called from nsMenuPopupFrame when making a popup transparent. michael@0: // For other window types, nsChildView::SetTransparencyMode is used. michael@0: void nsCocoaWindow::SetTransparencyMode(nsTransparencyMode aMode) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!mWindow) michael@0: return; michael@0: michael@0: BOOL isTransparent = aMode == eTransparencyTransparent; michael@0: BOOL currentTransparency = ![mWindow isOpaque]; michael@0: if (isTransparent != currentTransparency) { michael@0: [mWindow setOpaque:!isTransparent]; michael@0: [mWindow setBackgroundColor:(isTransparent ? [NSColor clearColor] : [NSColor whiteColor])]; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::Enable(bool aState) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool nsCocoaWindow::IsEnabled() const michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: #define kWindowPositionSlop 20 michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::ConstrainPosition(bool aAllowSlop, michael@0: int32_t *aX, int32_t *aY) michael@0: { michael@0: if (!mWindow || ![mWindow screen]) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIntRect screenBounds; michael@0: michael@0: int32_t width, height; michael@0: michael@0: NSRect frame = [mWindow frame]; michael@0: michael@0: // zero size rects confuse the screen manager michael@0: width = std::max(frame.size.width, 1); michael@0: height = std::max(frame.size.height, 1); michael@0: michael@0: nsCOMPtr screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1"); michael@0: if (screenMgr) { michael@0: nsCOMPtr screen; michael@0: screenMgr->ScreenForRect(*aX, *aY, width, height, getter_AddRefs(screen)); michael@0: michael@0: if (screen) { michael@0: screen->GetRectDisplayPix(&(screenBounds.x), &(screenBounds.y), michael@0: &(screenBounds.width), &(screenBounds.height)); michael@0: } michael@0: } michael@0: michael@0: if (aAllowSlop) { michael@0: if (*aX < screenBounds.x - width + kWindowPositionSlop) { michael@0: *aX = screenBounds.x - width + kWindowPositionSlop; michael@0: } else if (*aX >= screenBounds.x + screenBounds.width - kWindowPositionSlop) { michael@0: *aX = screenBounds.x + screenBounds.width - kWindowPositionSlop; michael@0: } michael@0: michael@0: if (*aY < screenBounds.y - height + kWindowPositionSlop) { michael@0: *aY = screenBounds.y - height + kWindowPositionSlop; michael@0: } else if (*aY >= screenBounds.y + screenBounds.height - kWindowPositionSlop) { michael@0: *aY = screenBounds.y + screenBounds.height - kWindowPositionSlop; michael@0: } michael@0: } else { michael@0: if (*aX < screenBounds.x) { michael@0: *aX = screenBounds.x; michael@0: } else if (*aX >= screenBounds.x + screenBounds.width - width) { michael@0: *aX = screenBounds.x + screenBounds.width - width; michael@0: } michael@0: michael@0: if (*aY < screenBounds.y) { michael@0: *aY = screenBounds.y; michael@0: } else if (*aY >= screenBounds.y + screenBounds.height - height) { michael@0: *aY = screenBounds.y + screenBounds.height - height; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsCocoaWindow::SetSizeConstraints(const SizeConstraints& aConstraints) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: // Popups can be smaller than (60, 60) michael@0: NSRect rect = michael@0: (mWindowType == eWindowType_popup) ? NSZeroRect : NSMakeRect(0.0, 0.0, 60, 60); michael@0: rect = [mWindow frameRectForContentRect:rect]; michael@0: michael@0: CGFloat scaleFactor = BackingScaleFactor(); michael@0: michael@0: SizeConstraints c = aConstraints; michael@0: c.mMinSize.width = michael@0: std::max(nsCocoaUtils::CocoaPointsToDevPixels(rect.size.width, scaleFactor), michael@0: c.mMinSize.width); michael@0: c.mMinSize.height = michael@0: std::max(nsCocoaUtils::CocoaPointsToDevPixels(rect.size.height, scaleFactor), michael@0: c.mMinSize.height); michael@0: michael@0: NSSize minSize = { michael@0: nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.width, scaleFactor), michael@0: nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.height, scaleFactor) michael@0: }; michael@0: [mWindow setMinSize:minSize]; michael@0: michael@0: NSSize maxSize = { michael@0: c.mMaxSize.width == NS_MAXSIZE ? michael@0: FLT_MAX : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.width, scaleFactor), michael@0: c.mMaxSize.height == NS_MAXSIZE ? michael@0: FLT_MAX : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.height, scaleFactor) michael@0: }; michael@0: [mWindow setMaxSize:maxSize]; michael@0: michael@0: nsBaseWidget::SetSizeConstraints(c); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // Coordinates are global display pixels michael@0: NS_IMETHODIMP nsCocoaWindow::Move(double aX, double aY) michael@0: { michael@0: if (!mWindow) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // The point we have is in Gecko coordinates (origin top-left). Convert michael@0: // it to Cocoa ones (origin bottom-left). michael@0: NSPoint coord = { michael@0: static_cast(aX), michael@0: static_cast(nsCocoaUtils::FlippedScreenY(NSToIntRound(aY))) michael@0: }; michael@0: michael@0: NSRect frame = [mWindow frame]; michael@0: if (frame.origin.x != coord.x || michael@0: frame.origin.y + frame.size.height != coord.y) { michael@0: [mWindow setFrameTopLeftPoint:coord]; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Position the window behind the given window michael@0: NS_METHOD nsCocoaWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement, michael@0: nsIWidget *aWidget, bool aActivate) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_METHOD nsCocoaWindow::SetSizeMode(int32_t aMode) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: if (!mWindow) michael@0: return NS_OK; michael@0: michael@0: // mSizeMode will be updated in DispatchSizeModeEvent, which will be called michael@0: // from a delegate method that handles the state change during one of the michael@0: // calls below. michael@0: nsSizeMode previousMode = mSizeMode; michael@0: michael@0: if (aMode == nsSizeMode_Normal) { michael@0: if ([mWindow isMiniaturized]) michael@0: [mWindow deminiaturize:nil]; michael@0: else if (previousMode == nsSizeMode_Maximized && [mWindow isZoomed]) michael@0: [mWindow zoom:nil]; michael@0: } michael@0: else if (aMode == nsSizeMode_Minimized) { michael@0: if (![mWindow isMiniaturized]) michael@0: [mWindow miniaturize:nil]; michael@0: } michael@0: else if (aMode == nsSizeMode_Maximized) { michael@0: if ([mWindow isMiniaturized]) michael@0: [mWindow deminiaturize:nil]; michael@0: if (![mWindow isZoomed]) michael@0: [mWindow zoom:nil]; michael@0: } michael@0: else if (aMode == nsSizeMode_Fullscreen) { michael@0: if (!mFullScreen) michael@0: MakeFullScreen(true); michael@0: } michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: // This has to preserve the window's frame bounds. michael@0: // This method requires (as does the Windows impl.) that you call Resize shortly michael@0: // after calling HideWindowChrome. See bug 498835 for fixing this. michael@0: NS_IMETHODIMP nsCocoaWindow::HideWindowChrome(bool aShouldHide) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: if (!mWindow || !mWindowMadeHere || michael@0: (mWindowType != eWindowType_toplevel && mWindowType != eWindowType_dialog)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: BOOL isVisible = [mWindow isVisible]; michael@0: michael@0: // Remove child windows. michael@0: NSArray* childWindows = [mWindow childWindows]; michael@0: NSEnumerator* enumerator = [childWindows objectEnumerator]; michael@0: NSWindow* child = nil; michael@0: while ((child = [enumerator nextObject])) { michael@0: [mWindow removeChildWindow:child]; michael@0: } michael@0: michael@0: // Remove the content view. michael@0: NSView* contentView = [mWindow contentView]; michael@0: [contentView retain]; michael@0: [contentView removeFromSuperviewWithoutNeedingDisplay]; michael@0: michael@0: // Save state (like window title). michael@0: NSMutableDictionary* state = [mWindow exportState]; michael@0: michael@0: // Recreate the window with the right border style. michael@0: NSRect frameRect = [mWindow frame]; michael@0: DestroyNativeWindow(); michael@0: nsresult rv = CreateNativeWindow(frameRect, aShouldHide ? eBorderStyle_none : mBorderStyle, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Re-import state. michael@0: [mWindow importState:state]; michael@0: michael@0: // Reparent the content view. michael@0: [mWindow setContentView:contentView]; michael@0: [contentView release]; michael@0: michael@0: // Reparent child windows. michael@0: enumerator = [childWindows objectEnumerator]; michael@0: while ((child = [enumerator nextObject])) { michael@0: [mWindow addChildWindow:child ordered:NSWindowAbove]; michael@0: } michael@0: michael@0: // Show the new window. michael@0: if (isVisible) { michael@0: rv = Show(true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: void nsCocoaWindow::EnteredFullScreen(bool aFullScreen) michael@0: { michael@0: mInFullScreenTransition = false; michael@0: mFullScreen = aFullScreen; michael@0: DispatchSizeModeEvent(); michael@0: } michael@0: michael@0: NS_METHOD nsCocoaWindow::MakeFullScreen(bool aFullScreen) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: if (!mWindow) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We will call into MakeFullScreen redundantly when entering/exiting michael@0: // fullscreen mode via OS X controls. When that happens we should just handle michael@0: // it gracefully - no need to ASSERT. michael@0: if (mFullScreen == aFullScreen) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If we're using native fullscreen mode and our native window is invisible, michael@0: // our attempt to go into fullscreen mode will fail with an assertion in michael@0: // system code, without [WindowDelegate windowDidFailToEnterFullScreen:] michael@0: // ever getting called. To pre-empt this we bail here. See bug 752294. michael@0: if (mUsesNativeFullScreen && aFullScreen && ![mWindow isVisible]) { michael@0: EnteredFullScreen(false); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mInFullScreenTransition = true; michael@0: michael@0: if (mUsesNativeFullScreen) { michael@0: // Calling toggleFullScreen will result in windowDid(FailTo)?(Enter|Exit)FullScreen michael@0: // to be called from the OS. We will call EnteredFullScreen from those methods, michael@0: // where mFullScreen will be set and a sizemode event will be dispatched. michael@0: [mWindow toggleFullScreen:nil]; michael@0: } else { michael@0: NSDisableScreenUpdates(); michael@0: // The order here matters. When we exit full screen mode, we need to show the michael@0: // Dock first, otherwise the newly-created window won't have its minimize michael@0: // button enabled. See bug 526282. michael@0: nsCocoaUtils::HideOSChromeOnScreen(aFullScreen, [mWindow screen]); michael@0: nsresult rv = nsBaseWidget::MakeFullScreen(aFullScreen); michael@0: NSEnableScreenUpdates(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: EnteredFullScreen(aFullScreen); michael@0: } michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: // Coordinates are global display pixels michael@0: nsresult nsCocoaWindow::DoResize(double aX, double aY, michael@0: double aWidth, double aHeight, michael@0: bool aRepaint, michael@0: bool aConstrainToCurrentScreen) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: if (!mWindow || mInResize) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: AutoRestore reentrantResizeGuard(mInResize); michael@0: mInResize = true; michael@0: michael@0: // ConstrainSize operates in device pixels, so we need to convert using michael@0: // the backing scale factor here michael@0: CGFloat scale = BackingScaleFactor(); michael@0: int32_t width = NSToIntRound(aWidth * scale); michael@0: int32_t height = NSToIntRound(aHeight * scale); michael@0: ConstrainSize(&width, &height); michael@0: michael@0: nsIntRect newBounds(NSToIntRound(aX), NSToIntRound(aY), michael@0: NSToIntRound(width / scale), michael@0: NSToIntRound(height / scale)); michael@0: michael@0: // constrain to the screen that contains the largest area of the new rect michael@0: FitRectToVisibleAreaForScreen(newBounds, michael@0: aConstrainToCurrentScreen ? michael@0: [mWindow screen] : nullptr, michael@0: mUsesNativeFullScreen); michael@0: michael@0: // convert requested bounds into Cocoa coordinate system michael@0: NSRect newFrame = nsCocoaUtils::GeckoRectToCocoaRect(newBounds); michael@0: michael@0: NSRect frame = [mWindow frame]; michael@0: BOOL isMoving = newFrame.origin.x != frame.origin.x || michael@0: newFrame.origin.y != frame.origin.y; michael@0: BOOL isResizing = newFrame.size.width != frame.size.width || michael@0: newFrame.size.height != frame.size.height; michael@0: michael@0: if (!isMoving && !isResizing) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We ignore aRepaint -- we have to call display:YES, otherwise the michael@0: // title bar doesn't immediately get repainted and is displayed in michael@0: // the wrong place, leading to a visual jump. michael@0: [mWindow setFrame:newFrame display:YES]; michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: // Coordinates are global display pixels michael@0: NS_IMETHODIMP nsCocoaWindow::Resize(double aX, double aY, michael@0: double aWidth, double aHeight, michael@0: bool aRepaint) michael@0: { michael@0: return DoResize(aX, aY, aWidth, aHeight, aRepaint, false); michael@0: } michael@0: michael@0: // Coordinates are global display pixels michael@0: NS_IMETHODIMP nsCocoaWindow::Resize(double aWidth, double aHeight, bool aRepaint) michael@0: { michael@0: double invScale = 1.0 / GetDefaultScale().scale; michael@0: return DoResize(mBounds.x * invScale, mBounds.y * invScale, michael@0: aWidth, aHeight, aRepaint, true); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::GetClientBounds(nsIntRect &aRect) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: CGFloat scaleFactor = BackingScaleFactor(); michael@0: if (!mWindow) { michael@0: aRect = nsCocoaUtils::CocoaRectToGeckoRectDevPix(NSZeroRect, scaleFactor); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NSRect r; michael@0: if ([mWindow isKindOfClass:[ToolbarWindow class]] && michael@0: [(ToolbarWindow*)mWindow drawsContentsIntoWindowFrame]) { michael@0: r = [mWindow frame]; michael@0: } else { michael@0: r = [mWindow contentRectForFrameRect:[mWindow frame]]; michael@0: } michael@0: michael@0: aRect = nsCocoaUtils::CocoaRectToGeckoRectDevPix(r, scaleFactor); michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: void michael@0: nsCocoaWindow::UpdateBounds() michael@0: { michael@0: NSRect frame = NSZeroRect; michael@0: if (mWindow) { michael@0: frame = [mWindow frame]; michael@0: } michael@0: mBounds = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor()); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::GetScreenBounds(nsIntRect &aRect) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: #ifdef DEBUG michael@0: nsIntRect r = nsCocoaUtils::CocoaRectToGeckoRectDevPix([mWindow frame], BackingScaleFactor()); michael@0: NS_ASSERTION(mWindow && mBounds == r, "mBounds out of sync!"); michael@0: #endif michael@0: michael@0: aRect = mBounds; michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: double michael@0: nsCocoaWindow::GetDefaultScaleInternal() michael@0: { michael@0: return BackingScaleFactor(); michael@0: } michael@0: michael@0: static CGFloat michael@0: GetBackingScaleFactor(NSWindow* aWindow) michael@0: { michael@0: NSRect frame = [aWindow frame]; michael@0: if (frame.size.width > 0 && frame.size.height > 0) { michael@0: return nsCocoaUtils::GetBackingScaleFactor(aWindow); michael@0: } michael@0: michael@0: // For windows with zero width or height, the backingScaleFactor method michael@0: // is broken - it will always return 2 on a retina macbook, even when michael@0: // the window position implies it's on a non-hidpi external display michael@0: // (to the extent that a zero-area window can be said to be "on" a michael@0: // display at all!) michael@0: // And to make matters worse, Cocoa even fires a michael@0: // windowDidChangeBackingProperties notification with the michael@0: // NSBackingPropertyOldScaleFactorKey key when a window on an michael@0: // external display is resized to/from zero height, even though it hasn't michael@0: // really changed screens. michael@0: michael@0: // This causes us to handle popup window sizing incorrectly when the michael@0: // popup is resized to zero height (bug 820327) - nsXULPopupManager michael@0: // becomes (incorrectly) convinced the popup has been explicitly forced michael@0: // to a non-default size and needs to have size attributes attached. michael@0: michael@0: // Workaround: instead of asking the window, we'll find the screen it is on michael@0: // and ask that for *its* backing scale factor. michael@0: michael@0: // (See bug 853252 and additional comments in windowDidChangeScreen: below michael@0: // for further complications this causes.) michael@0: michael@0: // First, expand the rect so that it actually has a measurable area, michael@0: // for FindTargetScreenForRect to use. michael@0: if (frame.size.width == 0) { michael@0: frame.size.width = 1; michael@0: } michael@0: if (frame.size.height == 0) { michael@0: frame.size.height = 1; michael@0: } michael@0: michael@0: // Then identify the screen it belongs to, and return its scale factor. michael@0: NSScreen *screen = michael@0: FindTargetScreenForRect(nsCocoaUtils::CocoaRectToGeckoRect(frame)); michael@0: return nsCocoaUtils::GetBackingScaleFactor(screen); michael@0: } michael@0: michael@0: CGFloat michael@0: nsCocoaWindow::BackingScaleFactor() michael@0: { michael@0: if (mBackingScaleFactor > 0.0) { michael@0: return mBackingScaleFactor; michael@0: } michael@0: if (!mWindow) { michael@0: return 1.0; michael@0: } michael@0: mBackingScaleFactor = GetBackingScaleFactor(mWindow); michael@0: return mBackingScaleFactor; michael@0: } michael@0: michael@0: void michael@0: nsCocoaWindow::BackingScaleFactorChanged() michael@0: { michael@0: CGFloat newScale = GetBackingScaleFactor(mWindow); michael@0: michael@0: // ignore notification if it hasn't really changed (or maybe we have michael@0: // disabled HiDPI mode via prefs) michael@0: if (mBackingScaleFactor == newScale) { michael@0: return; michael@0: } michael@0: michael@0: if (mBackingScaleFactor > 0.0) { michael@0: // convert size constraints to the new device pixel coordinate space michael@0: double scaleFactor = newScale / mBackingScaleFactor; michael@0: mSizeConstraints.mMinSize.width = michael@0: NSToIntRound(mSizeConstraints.mMinSize.width * scaleFactor); michael@0: mSizeConstraints.mMinSize.height = michael@0: NSToIntRound(mSizeConstraints.mMinSize.height * scaleFactor); michael@0: if (mSizeConstraints.mMaxSize.width < NS_MAXSIZE) { michael@0: mSizeConstraints.mMaxSize.width = michael@0: std::min(NS_MAXSIZE, michael@0: NSToIntRound(mSizeConstraints.mMaxSize.width * scaleFactor)); michael@0: } michael@0: if (mSizeConstraints.mMaxSize.height < NS_MAXSIZE) { michael@0: mSizeConstraints.mMaxSize.height = michael@0: std::min(NS_MAXSIZE, michael@0: NSToIntRound(mSizeConstraints.mMaxSize.height * scaleFactor)); michael@0: } michael@0: } michael@0: michael@0: mBackingScaleFactor = newScale; michael@0: michael@0: if (!mWidgetListener || mWidgetListener->GetXULWindow()) { michael@0: return; michael@0: } michael@0: michael@0: nsIPresShell* presShell = mWidgetListener->GetPresShell(); michael@0: if (presShell) { michael@0: presShell->BackingScaleFactorChanged(); michael@0: } michael@0: } michael@0: michael@0: int32_t michael@0: nsCocoaWindow::RoundsWidgetCoordinatesTo() michael@0: { michael@0: if (BackingScaleFactor() == 2.0) { michael@0: return 2; michael@0: } michael@0: return 1; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::SetCursor(nsCursor aCursor) michael@0: { michael@0: if (mPopupContentView) michael@0: return mPopupContentView->SetCursor(aCursor); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::SetCursor(imgIContainer* aCursor, michael@0: uint32_t aHotspotX, uint32_t aHotspotY) michael@0: { michael@0: if (mPopupContentView) michael@0: return mPopupContentView->SetCursor(aCursor, aHotspotX, aHotspotY); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::SetTitle(const nsAString& aTitle) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: if (!mWindow) michael@0: return NS_OK; michael@0: michael@0: const nsString& strTitle = PromiseFlatString(aTitle); michael@0: NSString* title = [NSString stringWithCharacters:reinterpret_cast(strTitle.get()) michael@0: length:strTitle.Length()]; michael@0: michael@0: if ([mWindow drawsContentsIntoWindowFrame] && ![mWindow wantsTitleDrawn]) { michael@0: // Don't cause invalidations. michael@0: [mWindow disableSetNeedsDisplay]; michael@0: [mWindow setTitle:title]; michael@0: [mWindow enableSetNeedsDisplay]; michael@0: } else { michael@0: [mWindow setTitle:title]; michael@0: } michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::Invalidate(const nsIntRect & aRect) michael@0: { michael@0: if (mPopupContentView) { michael@0: return mPopupContentView->Invalidate(aRect); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Pass notification of some drag event to Gecko michael@0: // michael@0: // The drag manager has let us know that something related to a drag has michael@0: // occurred in this window. It could be any number of things, ranging from michael@0: // a drop, to a drag enter/leave, or a drag over event. The actual event michael@0: // is passed in |aMessage| and is passed along to our event hanlder so Gecko michael@0: // knows about it. michael@0: bool nsCocoaWindow::DragEvent(unsigned int aMessage, Point aMouseGlobal, UInt16 aKeyModifiers) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::SendSetZLevelEvent() michael@0: { michael@0: nsWindowZ placement = nsWindowZTop; michael@0: nsIWidget* actualBelow; michael@0: if (mWidgetListener) michael@0: mWidgetListener->ZLevelChanged(true, &placement, nullptr, &actualBelow); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::GetChildSheet(bool aShown, nsCocoaWindow** _retval) michael@0: { michael@0: nsIWidget* child = GetFirstChild(); michael@0: michael@0: while (child) { michael@0: if (child->WindowType() == eWindowType_sheet) { michael@0: // if it's a sheet, it must be an nsCocoaWindow michael@0: nsCocoaWindow* cocoaWindow = static_cast(child); michael@0: if (cocoaWindow->mWindow && michael@0: ((aShown && [cocoaWindow->mWindow isVisible]) || michael@0: (!aShown && cocoaWindow->mSheetNeedsShow))) { michael@0: *_retval = cocoaWindow; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: child = child->GetNextSibling(); michael@0: } michael@0: michael@0: *_retval = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::GetRealParent(nsIWidget** parent) michael@0: { michael@0: *parent = mParent; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::GetIsSheet(bool* isSheet) michael@0: { michael@0: mWindowType == eWindowType_sheet ? *isSheet = true : *isSheet = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::GetSheetWindowParent(NSWindow** sheetWindowParent) michael@0: { michael@0: *sheetWindowParent = mSheetWindowParent; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Invokes callback and ProcessEvent methods on Event Listener object michael@0: NS_IMETHODIMP michael@0: nsCocoaWindow::DispatchEvent(WidgetGUIEvent* event, nsEventStatus& aStatus) michael@0: { michael@0: aStatus = nsEventStatus_eIgnore; michael@0: michael@0: nsIWidget* aWidget = event->widget; michael@0: NS_IF_ADDREF(aWidget); michael@0: michael@0: if (mWidgetListener) michael@0: aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents); michael@0: michael@0: NS_IF_RELEASE(aWidget); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // aFullScreen should be the window's mFullScreen. We don't have access to that michael@0: // from here, so we need to pass it in. mFullScreen should be the canonical michael@0: // indicator that a window is currently full screen and it makes sense to keep michael@0: // all sizemode logic here. michael@0: static nsSizeMode michael@0: GetWindowSizeMode(NSWindow* aWindow, bool aFullScreen) { michael@0: if (aFullScreen) michael@0: return nsSizeMode_Fullscreen; michael@0: if ([aWindow isMiniaturized]) michael@0: return nsSizeMode_Minimized; michael@0: if (([aWindow styleMask] & NSResizableWindowMask) && [aWindow isZoomed]) michael@0: return nsSizeMode_Maximized; michael@0: return nsSizeMode_Normal; michael@0: } michael@0: michael@0: void michael@0: nsCocoaWindow::ReportMoveEvent() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: // Prevent recursion, which can become infinite (see bug 708278). This michael@0: // can happen when the call to [NSWindow setFrameTopLeftPoint:] in michael@0: // nsCocoaWindow::Move() triggers an immediate NSWindowDidMove notification michael@0: // (and a call to [WindowDelegate windowDidMove:]). michael@0: if (mInReportMoveEvent) { michael@0: return; michael@0: } michael@0: mInReportMoveEvent = true; michael@0: michael@0: UpdateBounds(); michael@0: michael@0: // Dispatch the move event to Gecko michael@0: NotifyWindowMoved(mBounds.x, mBounds.y); michael@0: michael@0: mInReportMoveEvent = false; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: nsCocoaWindow::DispatchSizeModeEvent() michael@0: { michael@0: if (!mWindow) { michael@0: return; michael@0: } michael@0: michael@0: nsSizeMode newMode = GetWindowSizeMode(mWindow, mFullScreen); michael@0: michael@0: // Don't dispatch a sizemode event if: michael@0: // 1. the window is transitioning to fullscreen michael@0: // 2. the new sizemode is the same as the current sizemode michael@0: if (mInFullScreenTransition || mSizeMode == newMode) { michael@0: return; michael@0: } michael@0: michael@0: mSizeMode = newMode; michael@0: if (mWidgetListener) { michael@0: mWidgetListener->SizeModeChanged(newMode); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsCocoaWindow::ReportSizeEvent() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: UpdateBounds(); michael@0: michael@0: if (mWidgetListener) { michael@0: nsIntRect innerBounds; michael@0: GetClientBounds(innerBounds); michael@0: mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height); michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void nsCocoaWindow::SetMenuBar(nsMenuBarX *aMenuBar) michael@0: { michael@0: if (mMenuBar) michael@0: mMenuBar->SetParent(nullptr); michael@0: if (!mWindow) { michael@0: mMenuBar = nullptr; michael@0: return; michael@0: } michael@0: mMenuBar = aMenuBar; michael@0: michael@0: // Only paint for active windows, or paint the hidden window menu bar if no michael@0: // other menu bar has been painted yet so that some reasonable menu bar is michael@0: // displayed when the app starts up. michael@0: id windowDelegate = [mWindow delegate]; michael@0: if (mMenuBar && michael@0: ((!gSomeMenuBarPainted && nsMenuUtilsX::GetHiddenWindowMenuBar() == mMenuBar) || michael@0: (windowDelegate && [windowDelegate toplevelActiveState]))) michael@0: mMenuBar->Paint(); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::SetFocus(bool aState) michael@0: { michael@0: if (!mWindow) michael@0: return NS_OK; michael@0: michael@0: if (mPopupContentView) { michael@0: mPopupContentView->SetFocus(aState); michael@0: } michael@0: else if (aState && ([mWindow isVisible] || [mWindow isMiniaturized])) { michael@0: [mWindow makeKeyAndOrderFront:nil]; michael@0: SendSetZLevelEvent(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIntPoint nsCocoaWindow::WidgetToScreenOffset() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: NSRect rect = NSZeroRect; michael@0: nsIntRect r; michael@0: if (mWindow) { michael@0: rect = [mWindow contentRectForFrameRect:[mWindow frame]]; michael@0: } michael@0: r = nsCocoaUtils::CocoaRectToGeckoRectDevPix(rect, BackingScaleFactor()); michael@0: michael@0: return r.TopLeft(); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsIntPoint(0,0)); michael@0: } michael@0: michael@0: nsIntPoint nsCocoaWindow::GetClientOffset() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: nsIntRect clientRect; michael@0: GetClientBounds(clientRect); michael@0: michael@0: return clientRect.TopLeft() - mBounds.TopLeft(); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsIntPoint(0, 0)); michael@0: } michael@0: michael@0: nsIntSize nsCocoaWindow::ClientToWindowSize(const nsIntSize& aClientSize) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: if (!mWindow) michael@0: return nsIntSize(0, 0); michael@0: michael@0: CGFloat backingScale = BackingScaleFactor(); michael@0: nsIntRect r(0, 0, aClientSize.width, aClientSize.height); michael@0: NSRect rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, backingScale); michael@0: michael@0: NSRect inflatedRect = [mWindow frameRectForContentRect:rect]; michael@0: return nsCocoaUtils::CocoaRectToGeckoRectDevPix(inflatedRect, backingScale).Size(); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsIntSize(0,0)); michael@0: } michael@0: michael@0: nsMenuBarX* nsCocoaWindow::GetMenuBar() michael@0: { michael@0: return mMenuBar; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::CaptureRollupEvents(nsIRollupListener* aListener, bool aDoCapture) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: gRollupListener = nullptr; michael@0: michael@0: if (aDoCapture) { michael@0: if (![NSApp isActive]) { michael@0: // We need to capture mouse event if we aren't michael@0: // the active application. We only set this up when needed michael@0: // because they cause spurious mouse event after crash michael@0: // and gdb sessions. See bug 699538. michael@0: nsToolkit::GetToolkit()->RegisterForAllProcessMouseEvents(); michael@0: } michael@0: gRollupListener = aListener; michael@0: michael@0: // Sometimes more than one popup window can be visible at the same time michael@0: // (e.g. nested non-native context menus, or the test case (attachment michael@0: // 276885) for bmo bug 392389, which displays a non-native combo-box in a michael@0: // non-native popup window). In these cases the "active" popup window should michael@0: // be the topmost -- the (nested) context menu the mouse is currently over, michael@0: // or the combo-box's drop-down list (when it's displayed). But (among michael@0: // windows that have the same "level") OS X makes topmost the window that michael@0: // last received a mouse-down event, which may be incorrect (in the combo- michael@0: // box case, it makes topmost the window containing the combo-box). So michael@0: // here we fiddle with a non-native popup window's level to make sure the michael@0: // "active" one is always above any other non-native popup windows that michael@0: // may be visible. michael@0: if (mWindow && (mWindowType == eWindowType_popup)) michael@0: SetPopupWindowLevel(); michael@0: } else { michael@0: nsToolkit::GetToolkit()->UnregisterAllProcessMouseEventHandlers(); michael@0: michael@0: // XXXndeakin this doesn't make sense. michael@0: // Why is the new window assumed to be a modal panel? michael@0: if (mWindow && (mWindowType == eWindowType_popup)) michael@0: [mWindow setLevel:NSModalPanelWindowLevel]; michael@0: } michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::GetAttention(int32_t aCycleCount) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: [NSApp requestUserAttention:NSInformationalRequest]; michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: bool michael@0: nsCocoaWindow::HasPendingInputEvent() michael@0: { michael@0: return nsChildView::DoHasPendingInputEvent(); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::SetWindowShadowStyle(int32_t aStyle) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: if (!mWindow) michael@0: return NS_OK; michael@0: michael@0: mShadowStyle = aStyle; michael@0: [mWindow setHasShadow:(aStyle != NS_STYLE_WINDOW_SHADOW_NONE)]; michael@0: AdjustWindowShadow(); michael@0: SetWindowBackgroundBlur(); michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: void nsCocoaWindow::SetShowsToolbarButton(bool aShow) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (mWindow) michael@0: [mWindow setShowsToolbarButton:aShow]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void nsCocoaWindow::SetShowsFullScreenButton(bool aShow) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!mWindow || ![mWindow respondsToSelector:@selector(toggleFullScreen:)] || michael@0: mUsesNativeFullScreen == aShow) { michael@0: return; michael@0: } michael@0: michael@0: // If the window is currently in fullscreen mode, then we're going to michael@0: // transition out first, then set the collection behavior & toggle michael@0: // mUsesNativeFullScreen, then transtion back into fullscreen mode. This michael@0: // prevents us from getting into a conflicting state with MakeFullScreen michael@0: // where mUsesNativeFullScreen would lead us down the wrong path. michael@0: bool wasFullScreen = mFullScreen; michael@0: michael@0: if (wasFullScreen) { michael@0: MakeFullScreen(false); michael@0: } michael@0: michael@0: NSWindowCollectionBehavior newBehavior = [mWindow collectionBehavior]; michael@0: if (aShow) { michael@0: newBehavior |= NSWindowCollectionBehaviorFullScreenPrimary; michael@0: } else { michael@0: newBehavior &= ~NSWindowCollectionBehaviorFullScreenPrimary; michael@0: } michael@0: [mWindow setCollectionBehavior:newBehavior]; michael@0: mUsesNativeFullScreen = aShow; michael@0: michael@0: if (wasFullScreen) { michael@0: MakeFullScreen(true); michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void nsCocoaWindow::SetWindowAnimationType(nsIWidget::WindowAnimationType aType) michael@0: { michael@0: mAnimationType = aType; michael@0: } michael@0: michael@0: void michael@0: nsCocoaWindow::SetDrawsTitle(bool aDrawTitle) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: [mWindow setWantsTitleDrawn:aDrawTitle]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::SetNonClientMargins(nsIntMargin &margins) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: SetDrawsInTitlebar(margins.top == 0); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::SetWindowTitlebarColor(nscolor aColor, bool aActive) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: if (!mWindow) michael@0: return NS_OK; michael@0: michael@0: // If they pass a color with a complete transparent alpha component, use the michael@0: // native titlebar appearance. michael@0: if (NS_GET_A(aColor) == 0) { michael@0: [mWindow setTitlebarColor:nil forActiveWindow:(BOOL)aActive]; michael@0: } else { michael@0: // Transform from sRGBA to monitor RGBA. This seems like it would make trying michael@0: // to match the system appearance lame, so probably we just shouldn't color michael@0: // correct chrome. michael@0: if (gfxPlatform::GetCMSMode() == eCMSMode_All) { michael@0: qcms_transform *transform = gfxPlatform::GetCMSRGBATransform(); michael@0: if (transform) { michael@0: uint8_t color[3]; michael@0: color[0] = NS_GET_R(aColor); michael@0: color[1] = NS_GET_G(aColor); michael@0: color[2] = NS_GET_B(aColor); michael@0: qcms_transform_data(transform, color, color, 1); michael@0: aColor = NS_RGB(color[0], color[1], color[2]); michael@0: } michael@0: } michael@0: michael@0: [mWindow setTitlebarColor:[NSColor colorWithDeviceRed:NS_GET_R(aColor)/255.0 michael@0: green:NS_GET_G(aColor)/255.0 michael@0: blue:NS_GET_B(aColor)/255.0 michael@0: alpha:NS_GET_A(aColor)/255.0] michael@0: forActiveWindow:(BOOL)aActive]; michael@0: } michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: void nsCocoaWindow::SetDrawsInTitlebar(bool aState) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (mWindow) michael@0: [mWindow setDrawsContentsIntoWindowFrame:aState]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseEvent(nsIntPoint aPoint, michael@0: uint32_t aNativeMessage, michael@0: uint32_t aModifierFlags) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: if (mPopupContentView) michael@0: return mPopupContentView->SynthesizeNativeMouseEvent(aPoint, aNativeMessage, michael@0: aModifierFlags); michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: gfxASurface* nsCocoaWindow::GetThebesSurface() michael@0: { michael@0: if (mPopupContentView) michael@0: return mPopupContentView->GetThebesSurface(); michael@0: return nullptr; michael@0: } michael@0: michael@0: void nsCocoaWindow::SetPopupWindowLevel() michael@0: { michael@0: if (!mWindow) michael@0: return; michael@0: michael@0: // Floating popups are at the floating level and hide when the window is michael@0: // deactivated. michael@0: if (mPopupLevel == ePopupLevelFloating) { michael@0: [mWindow setLevel:NSFloatingWindowLevel]; michael@0: [mWindow setHidesOnDeactivate:YES]; michael@0: } michael@0: else { michael@0: // Otherwise, this is a top-level or parent popup. Parent popups always michael@0: // appear just above their parent and essentially ignore the level. michael@0: [mWindow setLevel:NSPopUpMenuWindowLevel]; michael@0: [mWindow setHidesOnDeactivate:NO]; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCocoaWindow::NotifyIME(const IMENotification& aIMENotification) michael@0: { michael@0: switch (aIMENotification.mMessage) { michael@0: case NOTIFY_IME_OF_FOCUS: michael@0: if (mInputContext.IsPasswordEditor()) { michael@0: TextInputHandler::EnableSecureEventInput(); michael@0: } michael@0: return NS_OK; michael@0: case NOTIFY_IME_OF_BLUR: michael@0: // When we're going to be deactive, we must disable the secure event input michael@0: // mode, see the Carbon Event Manager Reference. michael@0: TextInputHandler::EnsureSecureEventInputDisabled(); michael@0: return NS_OK; michael@0: default: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: nsCocoaWindow::SetInputContext(const InputContext& aContext, michael@0: const InputContextAction& aAction) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (mWindow && michael@0: [mWindow firstResponder] == mWindow && michael@0: [mWindow isKeyWindow] && michael@0: [[NSApplication sharedApplication] isActive]) { michael@0: if (aContext.IsPasswordEditor()) { michael@0: TextInputHandler::EnableSecureEventInput(); michael@0: } else { michael@0: TextInputHandler::EnsureSecureEventInputDisabled(); michael@0: } michael@0: } michael@0: mInputContext = aContext; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(bool) michael@0: nsCocoaWindow::ExecuteNativeKeyBinding(NativeKeyBindingsType aType, michael@0: const WidgetKeyboardEvent& aEvent, michael@0: DoCommandCallback aCallback, michael@0: void* aCallbackData) michael@0: { michael@0: NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType); michael@0: return keyBindings->Execute(aEvent, aCallback, aCallbackData); michael@0: } michael@0: michael@0: michael@0: @implementation WindowDelegate michael@0: michael@0: // We try to find a gecko menu bar to paint. If one does not exist, just paint michael@0: // the application menu by itself so that a window doesn't have some other michael@0: // window's menu bar. michael@0: + (void)paintMenubarForWindow:(NSWindow*)aWindow michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: // make sure we only act on windows that have this kind of michael@0: // object as a delegate michael@0: id windowDelegate = [aWindow delegate]; michael@0: if ([windowDelegate class] != [self class]) michael@0: return; michael@0: michael@0: nsCocoaWindow* geckoWidget = [windowDelegate geckoWidget]; michael@0: NS_ASSERTION(geckoWidget, "Window delegate not returning a gecko widget!"); michael@0: michael@0: nsMenuBarX* geckoMenuBar = geckoWidget->GetMenuBar(); michael@0: if (geckoMenuBar) { michael@0: geckoMenuBar->Paint(); michael@0: } michael@0: else { michael@0: // sometimes we don't have a native application menu early in launching michael@0: if (!sApplicationMenu) michael@0: return; michael@0: michael@0: NSMenu* mainMenu = [NSApp mainMenu]; michael@0: NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!"); michael@0: michael@0: // Create a new menu bar. michael@0: // We create a GeckoNSMenu because all menu bar NSMenu objects should use that subclass for michael@0: // key handling reasons. michael@0: GeckoNSMenu* newMenuBar = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"]; michael@0: michael@0: // move the application menu from the existing menu bar to the new one michael@0: NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain]; michael@0: [mainMenu removeItemAtIndex:0]; michael@0: [newMenuBar insertItem:firstMenuItem atIndex:0]; michael@0: [firstMenuItem release]; michael@0: michael@0: // set our new menu bar as the main menu michael@0: [NSApp setMainMenu:newMenuBar]; michael@0: [newMenuBar release]; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (id)initWithGeckoWindow:(nsCocoaWindow*)geckoWind michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: [super init]; michael@0: mGeckoWindow = geckoWind; michael@0: mToplevelActiveState = false; michael@0: mHasEverBeenZoomed = false; michael@0: return self; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: - (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)proposedFrameSize michael@0: { michael@0: RollUpPopups(); michael@0: michael@0: return proposedFrameSize; michael@0: } michael@0: michael@0: - (void)windowDidResize:(NSNotification *)aNotification michael@0: { michael@0: BaseWindow* window = [aNotification object]; michael@0: [window updateTrackingArea]; michael@0: michael@0: if (!mGeckoWindow) michael@0: return; michael@0: michael@0: // Resizing might have changed our zoom state. michael@0: mGeckoWindow->DispatchSizeModeEvent(); michael@0: mGeckoWindow->ReportSizeEvent(); michael@0: } michael@0: michael@0: - (void)windowDidChangeScreen:(NSNotification *)aNotification michael@0: { michael@0: if (!mGeckoWindow) michael@0: return; michael@0: michael@0: // Because of Cocoa's peculiar treatment of zero-size windows (see comments michael@0: // at GetBackingScaleFactor() above), we sometimes have a situation where michael@0: // our concept of backing scale (based on the screen where the zero-sized michael@0: // window is positioned) differs from Cocoa's idea (always based on the michael@0: // Retina screen, AFAICT, even when an external non-Retina screen is the michael@0: // primary display). michael@0: // michael@0: // As a result, if the window was created with zero size on an external michael@0: // display, but then made visible on the (secondary) Retina screen, we michael@0: // will *not* get a windowDidChangeBackingProperties notification for it. michael@0: // This leads to an incorrect GetDefaultScale(), and widget coordinate michael@0: // confusion, as per bug 853252. michael@0: // michael@0: // To work around this, we check for a backing scale mismatch when we michael@0: // receive a windowDidChangeScreen notification, as we will receive this michael@0: // even if Cocoa was already treating the zero-size window as having michael@0: // Retina backing scale. michael@0: NSWindow *window = (NSWindow *)[aNotification object]; michael@0: if ([window respondsToSelector:@selector(backingScaleFactor)]) { michael@0: if (GetBackingScaleFactor(window) != mGeckoWindow->BackingScaleFactor()) { michael@0: mGeckoWindow->BackingScaleFactorChanged(); michael@0: } michael@0: } michael@0: michael@0: mGeckoWindow->ReportMoveEvent(); michael@0: } michael@0: michael@0: // Lion's full screen mode will bypass our internal fullscreen tracking, so michael@0: // we need to catch it when we transition and call our own methods, which in michael@0: // turn will fire "fullscreen" events. michael@0: - (void)windowDidEnterFullScreen:(NSNotification *)notification michael@0: { michael@0: if (!mGeckoWindow) { michael@0: return; michael@0: } michael@0: michael@0: mGeckoWindow->EnteredFullScreen(true); michael@0: } michael@0: michael@0: - (void)windowDidExitFullScreen:(NSNotification *)notification michael@0: { michael@0: if (!mGeckoWindow) { michael@0: return; michael@0: } michael@0: michael@0: mGeckoWindow->EnteredFullScreen(false); michael@0: } michael@0: michael@0: - (void)windowDidFailToEnterFullScreen:(NSWindow *)window michael@0: { michael@0: if (!mGeckoWindow) { michael@0: return; michael@0: } michael@0: michael@0: mGeckoWindow->EnteredFullScreen(false); michael@0: } michael@0: michael@0: - (void)windowDidFailToExitFullScreen:(NSWindow *)window michael@0: { michael@0: if (!mGeckoWindow) { michael@0: return; michael@0: } michael@0: michael@0: mGeckoWindow->EnteredFullScreen(true); michael@0: } michael@0: michael@0: - (void)windowDidBecomeMain:(NSNotification *)aNotification michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: RollUpPopups(); michael@0: ChildViewMouseTracker::ReEvaluateMouseEnterState(); michael@0: michael@0: // [NSApp _isRunningAppModal] will return true if we're running an OS dialog michael@0: // app modally. If one of those is up then we want it to retain its menu bar. michael@0: if ([NSApp _isRunningAppModal]) michael@0: return; michael@0: NSWindow* window = [aNotification object]; michael@0: if (window) michael@0: [WindowDelegate paintMenubarForWindow:window]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)windowDidResignMain:(NSNotification *)aNotification michael@0: { michael@0: RollUpPopups(); michael@0: ChildViewMouseTracker::ReEvaluateMouseEnterState(); michael@0: michael@0: // [NSApp _isRunningAppModal] will return true if we're running an OS dialog michael@0: // app modally. If one of those is up then we want it to retain its menu bar. michael@0: if ([NSApp _isRunningAppModal]) michael@0: return; michael@0: nsRefPtr hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar(); michael@0: if (hiddenWindowMenuBar) { michael@0: // printf("painting hidden window menu bar due to window losing main status\n"); michael@0: hiddenWindowMenuBar->Paint(); michael@0: } michael@0: } michael@0: michael@0: - (void)windowDidBecomeKey:(NSNotification *)aNotification michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: RollUpPopups(); michael@0: ChildViewMouseTracker::ReEvaluateMouseEnterState(); michael@0: michael@0: NSWindow* window = [aNotification object]; michael@0: if ([window isSheet]) michael@0: [WindowDelegate paintMenubarForWindow:window]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)windowDidResignKey:(NSNotification *)aNotification michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: RollUpPopups(); michael@0: ChildViewMouseTracker::ReEvaluateMouseEnterState(); michael@0: michael@0: // If a sheet just resigned key then we should paint the menu bar michael@0: // for whatever window is now main. michael@0: NSWindow* window = [aNotification object]; michael@0: if ([window isSheet]) michael@0: [WindowDelegate paintMenubarForWindow:[NSApp mainWindow]]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)windowWillMove:(NSNotification *)aNotification michael@0: { michael@0: RollUpPopups(); michael@0: } michael@0: michael@0: - (void)windowDidMove:(NSNotification *)aNotification michael@0: { michael@0: if (mGeckoWindow) michael@0: mGeckoWindow->ReportMoveEvent(); michael@0: } michael@0: michael@0: - (BOOL)windowShouldClose:(id)sender michael@0: { michael@0: nsIWidgetListener* listener = mGeckoWindow ? mGeckoWindow->GetWidgetListener() : nullptr; michael@0: if (listener) michael@0: listener->RequestWindowClose(mGeckoWindow); michael@0: return NO; // gecko will do it michael@0: } michael@0: michael@0: - (void)windowWillClose:(NSNotification *)aNotification michael@0: { michael@0: RollUpPopups(); michael@0: } michael@0: michael@0: - (void)windowWillMiniaturize:(NSNotification *)aNotification michael@0: { michael@0: RollUpPopups(); michael@0: } michael@0: michael@0: - (void)windowDidMiniaturize:(NSNotification *)aNotification michael@0: { michael@0: if (mGeckoWindow) michael@0: mGeckoWindow->DispatchSizeModeEvent(); michael@0: } michael@0: michael@0: - (void)windowDidDeminiaturize:(NSNotification *)aNotification michael@0: { michael@0: if (mGeckoWindow) michael@0: mGeckoWindow->DispatchSizeModeEvent(); michael@0: } michael@0: michael@0: - (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)proposedFrame michael@0: { michael@0: if (!mHasEverBeenZoomed && [window isZoomed]) michael@0: return NO; // See bug 429954. michael@0: michael@0: mHasEverBeenZoomed = YES; michael@0: return YES; michael@0: } michael@0: michael@0: - (void)didEndSheet:(NSWindow*)sheet returnCode:(int)returnCode contextInfo:(void*)contextInfo michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: // Note: 'contextInfo' (if it is set) is the window that is the parent of michael@0: // the sheet. The value of contextInfo is determined in michael@0: // nsCocoaWindow::Show(). If it's set, 'contextInfo' is always the top- michael@0: // level window, not another sheet itself. But 'contextInfo' is nil if michael@0: // our parent window is also a sheet -- in that case we shouldn't send michael@0: // the top-level window any activate events (because it's our parent michael@0: // window that needs to get these events, not the top-level window). michael@0: [TopLevelWindowData deactivateInWindow:sheet]; michael@0: [sheet orderOut:self]; michael@0: if (contextInfo) michael@0: [TopLevelWindowData activateInWindow:(NSWindow*)contextInfo]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)windowDidChangeBackingProperties:(NSNotification *)aNotification michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NSWindow *window = (NSWindow *)[aNotification object]; michael@0: michael@0: if ([window respondsToSelector:@selector(backingScaleFactor)]) { michael@0: CGFloat oldFactor = michael@0: [[[aNotification userInfo] michael@0: objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; michael@0: if ([window backingScaleFactor] != oldFactor) { michael@0: mGeckoWindow->BackingScaleFactorChanged(); michael@0: } michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (nsCocoaWindow*)geckoWidget michael@0: { michael@0: return mGeckoWindow; michael@0: } michael@0: michael@0: - (bool)toplevelActiveState michael@0: { michael@0: return mToplevelActiveState; michael@0: } michael@0: michael@0: - (void)sendToplevelActivateEvents michael@0: { michael@0: if (!mToplevelActiveState && mGeckoWindow) { michael@0: nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener(); michael@0: if (listener) michael@0: listener->WindowActivated(); michael@0: mToplevelActiveState = true; michael@0: } michael@0: } michael@0: michael@0: - (void)sendToplevelDeactivateEvents michael@0: { michael@0: if (mToplevelActiveState && mGeckoWindow) { michael@0: nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener(); michael@0: if (listener) michael@0: listener->WindowDeactivated(); michael@0: mToplevelActiveState = false; michael@0: } michael@0: } michael@0: michael@0: @end michael@0: michael@0: static float michael@0: GetDPI(NSWindow* aWindow) michael@0: { michael@0: NSScreen* screen = [aWindow screen]; michael@0: if (!screen) michael@0: return 96.0f; michael@0: michael@0: CGDirectDisplayID displayID = michael@0: [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] intValue]; michael@0: CGFloat heightMM = ::CGDisplayScreenSize(displayID).height; michael@0: size_t heightPx = ::CGDisplayPixelsHigh(displayID); michael@0: CGFloat scaleFactor = [aWindow userSpaceScaleFactor]; michael@0: if (scaleFactor < 0.01 || heightMM < 1 || heightPx < 1) { michael@0: // Something extremely bogus is going on michael@0: return 96.0f; michael@0: } michael@0: michael@0: // Currently we don't do our own scaling to take account michael@0: // of userSpaceScaleFactor, so every "pixel" we draw is actually michael@0: // userSpaceScaleFactor screen pixels. So divide the screen height michael@0: // by userSpaceScaleFactor to get the number of "device pixels" michael@0: // available. michael@0: float dpi = (heightPx / scaleFactor) / (heightMM / MM_PER_INCH_FLOAT); michael@0: michael@0: // Account for HiDPI mode where Cocoa's "points" do not correspond to real michael@0: // device pixels michael@0: CGFloat backingScale = GetBackingScaleFactor(aWindow); michael@0: michael@0: return dpi * backingScale; michael@0: } michael@0: michael@0: @interface NSView(FrameViewMethodSwizzling) michael@0: - (NSPoint)FrameView__closeButtonOrigin; michael@0: - (NSPoint)FrameView__fullScreenButtonOrigin; michael@0: @end michael@0: michael@0: @implementation NSView(FrameViewMethodSwizzling) michael@0: michael@0: - (NSPoint)FrameView__closeButtonOrigin michael@0: { michael@0: NSPoint defaultPosition = [self FrameView__closeButtonOrigin]; michael@0: if ([[self window] isKindOfClass:[ToolbarWindow class]]) { michael@0: return [(ToolbarWindow*)[self window] windowButtonsPositionWithDefaultPosition:defaultPosition]; michael@0: } michael@0: return defaultPosition; michael@0: } michael@0: michael@0: - (NSPoint)FrameView__fullScreenButtonOrigin michael@0: { michael@0: NSPoint defaultPosition = [self FrameView__fullScreenButtonOrigin]; michael@0: if ([[self window] isKindOfClass:[ToolbarWindow class]]) { michael@0: return [(ToolbarWindow*)[self window] fullScreenButtonPositionWithDefaultPosition:defaultPosition]; michael@0: } michael@0: return defaultPosition; michael@0: } michael@0: michael@0: @end michael@0: michael@0: static NSMutableSet *gSwizzledFrameViewClasses = nil; michael@0: michael@0: @interface NSWindow(PrivateSetNeedsDisplayInRectMethod) michael@0: - (void)_setNeedsDisplayInRect:(NSRect)aRect; michael@0: @end michael@0: michael@0: @interface BaseWindow(Private) michael@0: - (void)removeTrackingArea; michael@0: - (void)cursorUpdated:(NSEvent*)aEvent; michael@0: - (void)updateContentViewSize; michael@0: - (void)reflowTitlebarElements; michael@0: @end michael@0: michael@0: @implementation BaseWindow michael@0: michael@0: // The frame of a window is implemented using undocumented NSView subclasses. michael@0: // We offset the window buttons by overriding the methods _closeButtonOrigin michael@0: // and _fullScreenButtonOrigin on these frame view classes. The class which is michael@0: // used for a window is determined in the window's frameViewClassForStyleMask: michael@0: // method, so this is where we make sure that we have swizzled the method on michael@0: // all encountered classes. michael@0: + (Class)frameViewClassForStyleMask:(NSUInteger)styleMask michael@0: { michael@0: Class frameViewClass = [super frameViewClassForStyleMask:styleMask]; michael@0: michael@0: if (!gSwizzledFrameViewClasses) { michael@0: gSwizzledFrameViewClasses = [[NSMutableSet setWithCapacity:3] retain]; michael@0: if (!gSwizzledFrameViewClasses) { michael@0: return frameViewClass; michael@0: } michael@0: } michael@0: michael@0: static IMP our_closeButtonOrigin = michael@0: class_getMethodImplementation([NSView class], michael@0: @selector(FrameView__closeButtonOrigin)); michael@0: static IMP our_fullScreenButtonOrigin = michael@0: class_getMethodImplementation([NSView class], michael@0: @selector(FrameView__fullScreenButtonOrigin)); michael@0: michael@0: if (![gSwizzledFrameViewClasses containsObject:frameViewClass]) { michael@0: // Either of these methods might be implemented in both a subclass of michael@0: // NSFrameView and one of its own subclasses. Which means that if we michael@0: // aren't careful we might end up swizzling the same method twice. michael@0: // Since method swizzling involves swapping pointers, this would break michael@0: // things. michael@0: IMP _closeButtonOrigin = michael@0: class_getMethodImplementation(frameViewClass, michael@0: @selector(_closeButtonOrigin)); michael@0: if (_closeButtonOrigin && _closeButtonOrigin != our_closeButtonOrigin) { michael@0: nsToolkit::SwizzleMethods(frameViewClass, @selector(_closeButtonOrigin), michael@0: @selector(FrameView__closeButtonOrigin)); michael@0: } michael@0: IMP _fullScreenButtonOrigin = michael@0: class_getMethodImplementation(frameViewClass, michael@0: @selector(_fullScreenButtonOrigin)); michael@0: if (_fullScreenButtonOrigin && michael@0: _fullScreenButtonOrigin != our_fullScreenButtonOrigin) { michael@0: nsToolkit::SwizzleMethods(frameViewClass, @selector(_fullScreenButtonOrigin), michael@0: @selector(FrameView__fullScreenButtonOrigin)); michael@0: } michael@0: [gSwizzledFrameViewClasses addObject:frameViewClass]; michael@0: } michael@0: michael@0: return frameViewClass; michael@0: } michael@0: michael@0: - (id)initWithContentRect:(NSRect)aContentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)aFlag michael@0: { michael@0: mDrawsIntoWindowFrame = NO; michael@0: [super initWithContentRect:aContentRect styleMask:aStyle backing:aBufferingType defer:aFlag]; michael@0: mState = nil; michael@0: mActiveTitlebarColor = nil; michael@0: mInactiveTitlebarColor = nil; michael@0: mScheduledShadowInvalidation = NO; michael@0: mDisabledNeedsDisplay = NO; michael@0: mDPI = GetDPI(self); michael@0: mTrackingArea = nil; michael@0: mBeingShown = NO; michael@0: mDrawTitle = NO; michael@0: [self updateTrackingArea]; michael@0: michael@0: return self; michael@0: } michael@0: michael@0: - (void)setBeingShown:(BOOL)aValue michael@0: { michael@0: mBeingShown = aValue; michael@0: } michael@0: michael@0: - (BOOL)isBeingShown michael@0: { michael@0: return mBeingShown; michael@0: } michael@0: michael@0: - (BOOL)isVisibleOrBeingShown michael@0: { michael@0: return [super isVisible] || mBeingShown; michael@0: } michael@0: michael@0: - (void)disableSetNeedsDisplay michael@0: { michael@0: mDisabledNeedsDisplay = YES; michael@0: } michael@0: michael@0: - (void)enableSetNeedsDisplay michael@0: { michael@0: mDisabledNeedsDisplay = NO; michael@0: } michael@0: michael@0: - (void)dealloc michael@0: { michael@0: [mActiveTitlebarColor release]; michael@0: [mInactiveTitlebarColor release]; michael@0: [self removeTrackingArea]; michael@0: ChildViewMouseTracker::OnDestroyWindow(self); michael@0: [super dealloc]; michael@0: } michael@0: michael@0: static const NSString* kStateTitleKey = @"title"; michael@0: static const NSString* kStateDrawsContentsIntoWindowFrameKey = @"drawsContentsIntoWindowFrame"; michael@0: static const NSString* kStateActiveTitlebarColorKey = @"activeTitlebarColor"; michael@0: static const NSString* kStateInactiveTitlebarColorKey = @"inactiveTitlebarColor"; michael@0: static const NSString* kStateShowsToolbarButton = @"showsToolbarButton"; michael@0: michael@0: - (void)importState:(NSDictionary*)aState michael@0: { michael@0: [self setTitle:[aState objectForKey:kStateTitleKey]]; michael@0: [self setDrawsContentsIntoWindowFrame:[[aState objectForKey:kStateDrawsContentsIntoWindowFrameKey] boolValue]]; michael@0: [self setTitlebarColor:[aState objectForKey:kStateActiveTitlebarColorKey] forActiveWindow:YES]; michael@0: [self setTitlebarColor:[aState objectForKey:kStateInactiveTitlebarColorKey] forActiveWindow:NO]; michael@0: [self setShowsToolbarButton:[[aState objectForKey:kStateShowsToolbarButton] boolValue]]; michael@0: } michael@0: michael@0: - (NSMutableDictionary*)exportState michael@0: { michael@0: NSMutableDictionary* state = [NSMutableDictionary dictionaryWithCapacity:10]; michael@0: [state setObject:[self title] forKey:kStateTitleKey]; michael@0: [state setObject:[NSNumber numberWithBool:[self drawsContentsIntoWindowFrame]] michael@0: forKey:kStateDrawsContentsIntoWindowFrameKey]; michael@0: NSColor* activeTitlebarColor = [self titlebarColorForActiveWindow:YES]; michael@0: if (activeTitlebarColor) { michael@0: [state setObject:activeTitlebarColor forKey:kStateActiveTitlebarColorKey]; michael@0: } michael@0: NSColor* inactiveTitlebarColor = [self titlebarColorForActiveWindow:NO]; michael@0: if (inactiveTitlebarColor) { michael@0: [state setObject:inactiveTitlebarColor forKey:kStateInactiveTitlebarColorKey]; michael@0: } michael@0: [state setObject:[NSNumber numberWithBool:[self showsToolbarButton]] michael@0: forKey:kStateShowsToolbarButton]; michael@0: return state; michael@0: } michael@0: michael@0: - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState michael@0: { michael@0: bool changed = (aState != mDrawsIntoWindowFrame); michael@0: mDrawsIntoWindowFrame = aState; michael@0: if (changed) { michael@0: [self updateContentViewSize]; michael@0: [self reflowTitlebarElements]; michael@0: } michael@0: } michael@0: michael@0: - (BOOL)drawsContentsIntoWindowFrame michael@0: { michael@0: return mDrawsIntoWindowFrame; michael@0: } michael@0: michael@0: - (void)setWantsTitleDrawn:(BOOL)aDrawTitle michael@0: { michael@0: mDrawTitle = aDrawTitle; michael@0: } michael@0: michael@0: - (BOOL)wantsTitleDrawn michael@0: { michael@0: return mDrawTitle; michael@0: } michael@0: michael@0: // Pass nil here to get the default appearance. michael@0: - (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive michael@0: { michael@0: [aColor retain]; michael@0: if (aActive) { michael@0: [mActiveTitlebarColor release]; michael@0: mActiveTitlebarColor = aColor; michael@0: } else { michael@0: [mInactiveTitlebarColor release]; michael@0: mInactiveTitlebarColor = aColor; michael@0: } michael@0: } michael@0: michael@0: - (NSColor*)titlebarColorForActiveWindow:(BOOL)aActive michael@0: { michael@0: return aActive ? mActiveTitlebarColor : mInactiveTitlebarColor; michael@0: } michael@0: michael@0: - (void)deferredInvalidateShadow michael@0: { michael@0: if (mScheduledShadowInvalidation || [self isOpaque] || ![self hasShadow]) michael@0: return; michael@0: michael@0: [self performSelector:@selector(invalidateShadow) withObject:nil afterDelay:0]; michael@0: mScheduledShadowInvalidation = YES; michael@0: } michael@0: michael@0: - (void)invalidateShadow michael@0: { michael@0: [super invalidateShadow]; michael@0: mScheduledShadowInvalidation = NO; michael@0: } michael@0: michael@0: - (float)getDPI michael@0: { michael@0: return mDPI; michael@0: } michael@0: michael@0: - (NSView*)trackingAreaView michael@0: { michael@0: NSView* contentView = [self contentView]; michael@0: return [contentView superview] ? [contentView superview] : contentView; michael@0: } michael@0: michael@0: - (ChildView*)mainChildView michael@0: { michael@0: NSView *contentView = [self contentView]; michael@0: // A PopupWindow's contentView is a ChildView object. michael@0: if ([contentView isKindOfClass:[ChildView class]]) { michael@0: return (ChildView*)contentView; michael@0: } michael@0: NSView* lastView = [[contentView subviews] lastObject]; michael@0: if ([lastView isKindOfClass:[ChildView class]]) { michael@0: return (ChildView*)lastView; michael@0: } michael@0: return nil; michael@0: } michael@0: michael@0: - (void)removeTrackingArea michael@0: { michael@0: if (mTrackingArea) { michael@0: [[self trackingAreaView] removeTrackingArea:mTrackingArea]; michael@0: [mTrackingArea release]; michael@0: mTrackingArea = nil; michael@0: } michael@0: } michael@0: michael@0: - (void)updateTrackingArea michael@0: { michael@0: [self removeTrackingArea]; michael@0: michael@0: NSView* view = [self trackingAreaView]; michael@0: const NSTrackingAreaOptions options = michael@0: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways; michael@0: mTrackingArea = [[NSTrackingArea alloc] initWithRect:[view bounds] michael@0: options:options michael@0: owner:self michael@0: userInfo:nil]; michael@0: [view addTrackingArea:mTrackingArea]; michael@0: } michael@0: michael@0: - (void)mouseEntered:(NSEvent*)aEvent michael@0: { michael@0: ChildViewMouseTracker::MouseEnteredWindow(aEvent); michael@0: } michael@0: michael@0: - (void)mouseExited:(NSEvent*)aEvent michael@0: { michael@0: ChildViewMouseTracker::MouseExitedWindow(aEvent); michael@0: } michael@0: michael@0: - (void)mouseMoved:(NSEvent*)aEvent michael@0: { michael@0: ChildViewMouseTracker::MouseMoved(aEvent); michael@0: } michael@0: michael@0: - (void)cursorUpdated:(NSEvent*)aEvent michael@0: { michael@0: // Nothing to do here, but NSTrackingArea wants us to implement this method. michael@0: } michael@0: michael@0: - (void)_setNeedsDisplayInRect:(NSRect)aRect michael@0: { michael@0: // Prevent unnecessary invalidations due to moving NSViews (e.g. for plugins) michael@0: if (!mDisabledNeedsDisplay) { michael@0: // This method is only called by Cocoa, so when we're here, we know that michael@0: // it's available and don't need to check whether our superclass responds michael@0: // to the selector. michael@0: [super _setNeedsDisplayInRect:aRect]; michael@0: } michael@0: } michael@0: michael@0: - (void)updateContentViewSize michael@0: { michael@0: NSRect rect = [self contentRectForFrameRect:[self frame]]; michael@0: [[self contentView] setFrameSize:rect.size]; michael@0: } michael@0: michael@0: // Possibly move the titlebar buttons. michael@0: - (void)reflowTitlebarElements michael@0: { michael@0: NSView *frameView = [[self contentView] superview]; michael@0: if ([frameView respondsToSelector:@selector(_tileTitlebarAndRedisplay:)]) { michael@0: [frameView _tileTitlebarAndRedisplay:NO]; michael@0: } michael@0: } michael@0: michael@0: // Override methods that translate between content rect and frame rect. michael@0: - (NSRect)contentRectForFrameRect:(NSRect)aRect michael@0: { michael@0: if ([self drawsContentsIntoWindowFrame]) { michael@0: return aRect; michael@0: } michael@0: return [super contentRectForFrameRect:aRect]; michael@0: } michael@0: michael@0: - (NSRect)contentRectForFrameRect:(NSRect)aRect styleMask:(NSUInteger)aMask michael@0: { michael@0: if ([self drawsContentsIntoWindowFrame]) { michael@0: return aRect; michael@0: } michael@0: if ([super respondsToSelector:@selector(contentRectForFrameRect:styleMask:)]) { michael@0: return [super contentRectForFrameRect:aRect styleMask:aMask]; michael@0: } else { michael@0: return [NSWindow contentRectForFrameRect:aRect styleMask:aMask]; michael@0: } michael@0: } michael@0: michael@0: - (NSRect)frameRectForContentRect:(NSRect)aRect michael@0: { michael@0: if ([self drawsContentsIntoWindowFrame]) { michael@0: return aRect; michael@0: } michael@0: return [super frameRectForContentRect:aRect]; michael@0: } michael@0: michael@0: - (NSRect)frameRectForContentRect:(NSRect)aRect styleMask:(NSUInteger)aMask michael@0: { michael@0: if ([self drawsContentsIntoWindowFrame]) { michael@0: return aRect; michael@0: } michael@0: if ([super respondsToSelector:@selector(frameRectForContentRect:styleMask:)]) { michael@0: return [super frameRectForContentRect:aRect styleMask:aMask]; michael@0: } else { michael@0: return [NSWindow frameRectForContentRect:aRect styleMask:aMask]; michael@0: } michael@0: } michael@0: michael@0: - (void)setContentView:(NSView*)aView michael@0: { michael@0: [super setContentView:aView]; michael@0: michael@0: // Now move the contentView to the bottommost layer so that it's guaranteed michael@0: // to be under the window buttons. michael@0: NSView* frameView = [aView superview]; michael@0: [aView removeFromSuperview]; michael@0: [frameView addSubview:aView positioned:NSWindowBelow relativeTo:nil]; michael@0: } michael@0: michael@0: - (NSArray*)titlebarControls michael@0: { michael@0: // Return all subviews of the frameView which are not the content view. michael@0: NSView* frameView = [[self contentView] superview]; michael@0: NSMutableArray* array = [[[frameView subviews] mutableCopy] autorelease]; michael@0: [array removeObject:[self contentView]]; michael@0: return array; michael@0: } michael@0: michael@0: - (BOOL)respondsToSelector:(SEL)aSelector michael@0: { michael@0: // Claim the window doesn't respond to this so that the system michael@0: // doesn't steal keyboard equivalents for it. Bug 613710. michael@0: if (aSelector == @selector(cancelOperation:)) { michael@0: return NO; michael@0: } michael@0: michael@0: return [super respondsToSelector:aSelector]; michael@0: } michael@0: michael@0: - (void) doCommandBySelector:(SEL)aSelector michael@0: { michael@0: // We override this so that it won't beep if it can't act. michael@0: // We want to control the beeping for missing or disabled michael@0: // commands ourselves. michael@0: [self tryToPerform:aSelector with:nil]; michael@0: } michael@0: michael@0: - (id)accessibilityAttributeValue:(NSString *)attribute michael@0: { michael@0: id retval = [super accessibilityAttributeValue:attribute]; michael@0: michael@0: // The following works around a problem with Text-to-Speech on OS X 10.7. michael@0: // See bug 674612 for more info. michael@0: // michael@0: // When accessibility is off, AXUIElementCopyAttributeValue(), when called michael@0: // on an AXApplication object to get its AXFocusedUIElement attribute, michael@0: // always returns an AXWindow object (the actual browser window -- never a michael@0: // mozAccessible object). This also happens with accessibility turned on, michael@0: // if no other object in the browser window has yet been focused. But if michael@0: // the browser window has a title bar (as it currently always does), the michael@0: // AXWindow object will always have four "accessible" children, one of which michael@0: // is an AXStaticText object (the title bar's "title"; the other three are michael@0: // the close, minimize and zoom buttons). This means that (for complicated michael@0: // reasons, for which see bug 674612) Text-to-Speech on OS X 10.7 will often michael@0: // "speak" the window title, no matter what text is selected, or even if no michael@0: // text at all is selected. (This always happens when accessibility is off. michael@0: // It doesn't happen in Firefox releases because Apple has (on OS X 10.7) michael@0: // special-cased the handling of apps whose CFBundleIdentifier is michael@0: // org.mozilla.firefox.) michael@0: // michael@0: // We work around this problem by only returning AXChildren that are michael@0: // mozAccessible object or are one of the titlebar's buttons (which michael@0: // instantiate subclasses of NSButtonCell). michael@0: if (nsCocoaFeatures::OnLionOrLater() && [retval isKindOfClass:[NSArray class]] && michael@0: [attribute isEqualToString:@"AXChildren"]) { michael@0: NSMutableArray *holder = [NSMutableArray arrayWithCapacity:10]; michael@0: [holder addObjectsFromArray:(NSArray *)retval]; michael@0: NSUInteger count = [holder count]; michael@0: for (NSInteger i = count - 1; i >= 0; --i) { michael@0: id item = [holder objectAtIndex:i]; michael@0: // Remove anything from holder that isn't one of the titlebar's buttons michael@0: // (which instantiate subclasses of NSButtonCell) or a mozAccessible michael@0: // object (or one of its subclasses). michael@0: if (![item isKindOfClass:[NSButtonCell class]] && michael@0: ![item respondsToSelector:@selector(hasRepresentedView)]) { michael@0: [holder removeObjectAtIndex:i]; michael@0: } michael@0: } michael@0: retval = [NSArray arrayWithArray:holder]; michael@0: } michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: // If we were built on OS X 10.6 or with the 10.6 SDK and are running on Lion, michael@0: // the OS (specifically -[NSWindow sendEvent:]) won't send NSEventTypeGesture michael@0: // events to -[ChildView magnifyWithEvent:] as it should. The following code michael@0: // gets around this. See bug 863841. michael@0: #if !defined( MAC_OS_X_VERSION_10_7 ) || \ michael@0: ( MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 ) michael@0: - (void)sendEvent:(NSEvent *)anEvent michael@0: { michael@0: if ([ChildView isLionSmartMagnifyEvent: anEvent]) { michael@0: [[self mainChildView] magnifyWithEvent:anEvent]; michael@0: return; michael@0: } michael@0: [super sendEvent:anEvent]; michael@0: } michael@0: #endif michael@0: michael@0: @end michael@0: michael@0: // This class allows us to exercise control over the window's title bar. This michael@0: // allows for a "unified toolbar" look without having to extend the content michael@0: // area into the title bar. It works like this: michael@0: // 1) We set the window's style to textured. michael@0: // 2) Because of this, the background color applies to the entire window, including michael@0: // the titlebar area. For normal textured windows, the default pattern is a michael@0: // "brushed metal" image on Tiger and a unified gradient on Leopard. michael@0: // 3) We set the background color to a custom NSColor subclass that knows how tall the window is. michael@0: // When -set is called on it, it sets a pattern (with a draw callback) as the fill. In that callback, michael@0: // it paints the the titlebar and background colors in the correct areas of the context it's given, michael@0: // which will fill the entire window (CG will tile it horizontally for us). michael@0: // 4) Whenever the window's main state changes and when [window display] is called, michael@0: // Cocoa redraws the titlebar using the patternDraw callback function. michael@0: // michael@0: // This class also provides us with a pill button to show/hide the toolbar up to 10.6. michael@0: // michael@0: // Drawing the unified gradient in the titlebar and the toolbar works like this: michael@0: // 1) In the style sheet we set the toolbar's -moz-appearance to toolbar or michael@0: // -moz-mac-unified-toolbar. michael@0: // 2) When the toolbar is visible and we paint the application chrome michael@0: // window, the array that Gecko passes nsChildView::UpdateThemeGeometries michael@0: // will contain an entry for the widget type NS_THEME_TOOLBAR or michael@0: // NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR. michael@0: // 3) nsChildView::UpdateThemeGeometries finds the toolbar frame's ToolbarWindow michael@0: // and passes the toolbar frame's height to setUnifiedToolbarHeight. michael@0: // 4) If the toolbar height has changed, a titlebar redraw is triggered and the michael@0: // upper part of the unified gradient is drawn in the titlebar. michael@0: // 5) The lower part of the unified gradient in the toolbar is drawn during michael@0: // normal window content painting in nsNativeThemeCocoa::DrawUnifiedToolbar. michael@0: // michael@0: // Whenever the unified gradient is drawn in the titlebar or the toolbar, both michael@0: // titlebar height and toolbar height must be known in order to construct the michael@0: // correct gradient. But you can only get from the toolbar frame michael@0: // to the containing window - the other direction doesn't work. That's why the michael@0: // toolbar height is cached in the ToolbarWindow but nsNativeThemeCocoa can simply michael@0: // query the window for its titlebar height when drawing the toolbar. michael@0: // michael@0: // Note that in drawsContentsIntoWindowFrame mode, titlebar drawing works in a michael@0: // completely different way: In that mode, the window's mainChildView will michael@0: // cover the titlebar completely and nothing that happens in the window michael@0: // background will reach the screen. michael@0: @implementation ToolbarWindow michael@0: michael@0: - (id)initWithContentRect:(NSRect)aContentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)aFlag michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: aStyle = aStyle | NSTexturedBackgroundWindowMask; michael@0: if ((self = [super initWithContentRect:aContentRect styleMask:aStyle backing:aBufferingType defer:aFlag])) { michael@0: mColor = [[TitlebarAndBackgroundColor alloc] initWithWindow:self]; michael@0: // Bypass our guard method below. michael@0: [super setBackgroundColor:mColor]; michael@0: mBackgroundColor = [[NSColor whiteColor] retain]; michael@0: michael@0: mUnifiedToolbarHeight = 22.0f; michael@0: mWindowButtonsRect = NSZeroRect; michael@0: mFullScreenButtonRect = NSZeroRect; michael@0: michael@0: // setBottomCornerRounded: is a private API call, so we check to make sure michael@0: // we respond to it just in case. michael@0: if ([self respondsToSelector:@selector(setBottomCornerRounded:)]) michael@0: [self setBottomCornerRounded:nsCocoaFeatures::OnLionOrLater()]; michael@0: michael@0: [self setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge]; michael@0: [self setContentBorderThickness:0.0f forEdge:NSMaxYEdge]; michael@0: } michael@0: return self; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: - (void)dealloc michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: [mColor release]; michael@0: [mBackgroundColor release]; michael@0: [mTitlebarView release]; michael@0: [super dealloc]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive michael@0: { michael@0: [super setTitlebarColor:aColor forActiveWindow:aActive]; michael@0: [self setTitlebarNeedsDisplayInRect:[self titlebarRect]]; michael@0: } michael@0: michael@0: - (void)setBackgroundColor:(NSColor*)aColor michael@0: { michael@0: [aColor retain]; michael@0: [mBackgroundColor release]; michael@0: mBackgroundColor = aColor; michael@0: } michael@0: michael@0: - (NSColor*)windowBackgroundColor michael@0: { michael@0: return mBackgroundColor; michael@0: } michael@0: michael@0: - (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect michael@0: { michael@0: [self setTitlebarNeedsDisplayInRect:aRect sync:NO]; michael@0: } michael@0: michael@0: - (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect sync:(BOOL)aSync michael@0: { michael@0: NSRect titlebarRect = [self titlebarRect]; michael@0: NSRect rect = NSIntersectionRect(titlebarRect, aRect); michael@0: if (NSIsEmptyRect(rect)) michael@0: return; michael@0: michael@0: NSView* borderView = [[self contentView] superview]; michael@0: if (!borderView) michael@0: return; michael@0: michael@0: if (aSync) { michael@0: [borderView displayRect:rect]; michael@0: } else { michael@0: [borderView setNeedsDisplayInRect:rect]; michael@0: } michael@0: } michael@0: michael@0: - (NSRect)titlebarRect michael@0: { michael@0: CGFloat titlebarHeight = [self titlebarHeight]; michael@0: return NSMakeRect(0, [self frame].size.height - titlebarHeight, michael@0: [self frame].size.width, titlebarHeight); michael@0: } michael@0: michael@0: // Returns the unified height of titlebar + toolbar. michael@0: - (CGFloat)unifiedToolbarHeight michael@0: { michael@0: return mUnifiedToolbarHeight; michael@0: } michael@0: michael@0: - (CGFloat)titlebarHeight michael@0: { michael@0: // We use the original content rect here, not what we return from michael@0: // [self contentRectForFrameRect:], because that would give us a michael@0: // titlebarHeight of zero in drawsContentsIntoWindowFrame mode. michael@0: NSRect frameRect = [self frame]; michael@0: NSRect originalContentRect = [NSWindow contentRectForFrameRect:frameRect styleMask:[self styleMask]]; michael@0: return NSMaxY(frameRect) - NSMaxY(originalContentRect); michael@0: } michael@0: michael@0: // Stores the complete height of titlebar + toolbar. michael@0: - (void)setUnifiedToolbarHeight:(CGFloat)aHeight michael@0: { michael@0: if (aHeight == mUnifiedToolbarHeight) michael@0: return; michael@0: michael@0: mUnifiedToolbarHeight = aHeight; michael@0: michael@0: // Update sheet positioning hint michael@0: CGFloat topMargin = mUnifiedToolbarHeight - [self titlebarHeight]; michael@0: [self setContentBorderThickness:topMargin forEdge:NSMaxYEdge]; michael@0: michael@0: // Redraw the title bar. If we're inside painting, we'll do it right now, michael@0: // otherwise we'll just invalidate it. michael@0: BOOL needSyncRedraw = ([NSView focusView] != nil); michael@0: [self setTitlebarNeedsDisplayInRect:[self titlebarRect] sync:needSyncRedraw]; michael@0: } michael@0: michael@0: // Extending the content area into the title bar works by resizing the michael@0: // mainChildView so that it covers the titlebar. michael@0: - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState michael@0: { michael@0: BOOL stateChanged = ([self drawsContentsIntoWindowFrame] != aState); michael@0: [super setDrawsContentsIntoWindowFrame:aState]; michael@0: if (stateChanged && [[self delegate] isKindOfClass:[WindowDelegate class]]) { michael@0: // Here we extend / shrink our mainChildView. We do that by firing a resize michael@0: // event which will cause the ChildView to be resized to the rect returned michael@0: // by nsCocoaWindow::GetClientBounds. GetClientBounds bases its return michael@0: // value on what we return from drawsContentsIntoWindowFrame. michael@0: WindowDelegate *windowDelegate = (WindowDelegate *)[self delegate]; michael@0: nsCocoaWindow *geckoWindow = [windowDelegate geckoWidget]; michael@0: if (geckoWindow) { michael@0: // Re-layout our contents. michael@0: geckoWindow->ReportSizeEvent(); michael@0: } michael@0: michael@0: // Resizing the content area causes a reflow which would send a synthesized michael@0: // mousemove event to the old mouse position relative to the top left michael@0: // corner of the content area. But the mouse has shifted relative to the michael@0: // content area, so that event would have wrong position information. So michael@0: // we'll send a mouse move event with the correct new position. michael@0: ChildViewMouseTracker::ResendLastMouseMoveEvent(); michael@0: } michael@0: } michael@0: michael@0: - (void)setWantsTitleDrawn:(BOOL)aDrawTitle michael@0: { michael@0: [super setWantsTitleDrawn:aDrawTitle]; michael@0: [self setTitlebarNeedsDisplayInRect:[self titlebarRect]]; michael@0: } michael@0: michael@0: - (void)placeWindowButtons:(NSRect)aRect michael@0: { michael@0: if (!NSEqualRects(mWindowButtonsRect, aRect)) { michael@0: mWindowButtonsRect = aRect; michael@0: [self reflowTitlebarElements]; michael@0: } michael@0: } michael@0: michael@0: - (NSPoint)windowButtonsPositionWithDefaultPosition:(NSPoint)aDefaultPosition michael@0: { michael@0: if ([self drawsContentsIntoWindowFrame] && !NSIsEmptyRect(mWindowButtonsRect)) { michael@0: return NSMakePoint(std::max(mWindowButtonsRect.origin.x, aDefaultPosition.x), michael@0: std::min(mWindowButtonsRect.origin.y, aDefaultPosition.y)); michael@0: } michael@0: return aDefaultPosition; michael@0: } michael@0: michael@0: - (void)placeFullScreenButton:(NSRect)aRect michael@0: { michael@0: if (!NSEqualRects(mFullScreenButtonRect, aRect)) { michael@0: mFullScreenButtonRect = aRect; michael@0: [self reflowTitlebarElements]; michael@0: } michael@0: } michael@0: michael@0: - (NSPoint)fullScreenButtonPositionWithDefaultPosition:(NSPoint)aDefaultPosition; michael@0: { michael@0: if ([self drawsContentsIntoWindowFrame] && !NSIsEmptyRect(mFullScreenButtonRect)) { michael@0: return NSMakePoint(std::min(mFullScreenButtonRect.origin.x, aDefaultPosition.x), michael@0: std::min(mFullScreenButtonRect.origin.y, aDefaultPosition.y)); michael@0: } michael@0: return aDefaultPosition; michael@0: } michael@0: michael@0: // Returning YES here makes the setShowsToolbarButton method work even though michael@0: // the window doesn't contain an NSToolbar. michael@0: - (BOOL)_hasToolbar michael@0: { michael@0: return YES; michael@0: } michael@0: michael@0: // Dispatch a toolbar pill button clicked message to Gecko. michael@0: - (void)_toolbarPillButtonClicked:(id)sender michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: RollUpPopups(); michael@0: michael@0: if ([[self delegate] isKindOfClass:[WindowDelegate class]]) { michael@0: WindowDelegate *windowDelegate = (WindowDelegate *)[self delegate]; michael@0: nsCocoaWindow *geckoWindow = [windowDelegate geckoWidget]; michael@0: if (!geckoWindow) michael@0: return; michael@0: michael@0: nsIWidgetListener* listener = geckoWindow->GetWidgetListener(); michael@0: if (listener) michael@0: listener->OSToolbarButtonPressed(); michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // Retain and release "self" to avoid crashes when our widget (and its native michael@0: // window) is closed as a result of processing a key equivalent (e.g. michael@0: // Command+w or Command+q). This workaround is only needed for a window michael@0: // that can become key. michael@0: - (BOOL)performKeyEquivalent:(NSEvent*)theEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: NSWindow *nativeWindow = [self retain]; michael@0: BOOL retval = [super performKeyEquivalent:theEvent]; michael@0: [nativeWindow release]; michael@0: return retval; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); michael@0: } michael@0: michael@0: - (void)sendEvent:(NSEvent *)anEvent michael@0: { michael@0: NSEventType type = [anEvent type]; michael@0: michael@0: switch (type) { michael@0: case NSScrollWheel: michael@0: case NSLeftMouseDown: michael@0: case NSLeftMouseUp: michael@0: case NSRightMouseDown: michael@0: case NSRightMouseUp: michael@0: case NSOtherMouseDown: michael@0: case NSOtherMouseUp: michael@0: case NSMouseMoved: michael@0: case NSLeftMouseDragged: michael@0: case NSRightMouseDragged: michael@0: case NSOtherMouseDragged: michael@0: { michael@0: // Drop all mouse events if a modal window has appeared above us. michael@0: // This helps make us behave as if the OS were running a "real" modal michael@0: // event loop. michael@0: id delegate = [self delegate]; michael@0: if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) { michael@0: nsCocoaWindow *widget = [(WindowDelegate *)delegate geckoWidget]; michael@0: if (widget) { michael@0: if (type == NSMouseMoved) { michael@0: [[self mainChildView] updateWindowDraggableStateOnMouseMove:anEvent]; michael@0: } michael@0: if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window)) michael@0: return; michael@0: if (widget->HasModalDescendents()) michael@0: return; michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: [super sendEvent:anEvent]; michael@0: } michael@0: michael@0: @end michael@0: michael@0: // Custom NSColor subclass where most of the work takes place for drawing in michael@0: // the titlebar area. Not used in drawsContentsIntoWindowFrame mode. michael@0: @implementation TitlebarAndBackgroundColor michael@0: michael@0: - (id)initWithWindow:(ToolbarWindow*)aWindow michael@0: { michael@0: if ((self = [super init])) { michael@0: mWindow = aWindow; // weak ref to avoid a cycle michael@0: } michael@0: return self; michael@0: } michael@0: michael@0: static void michael@0: DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect, michael@0: CGFloat aUnifiedToolbarHeight, BOOL aIsMain) michael@0: { michael@0: if (aTitlebarRect.size.width * aTitlebarRect.size.height > CUIDRAW_MAX_AREA) { michael@0: return; michael@0: } michael@0: michael@0: CUIDraw([NSWindow coreUIRenderer], aTitlebarRect, aContext, michael@0: (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: michael@0: @"kCUIWidgetWindowFrame", @"widget", michael@0: @"regularwin", @"windowtype", michael@0: (aIsMain ? @"normal" : @"inactive"), @"state", michael@0: [NSNumber numberWithDouble:aUnifiedToolbarHeight], @"kCUIWindowFrameUnifiedTitleBarHeightKey", michael@0: [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawTitleSeparatorKey", michael@0: nil], michael@0: nil); michael@0: michael@0: if (nsCocoaFeatures::OnLionOrLater()) { michael@0: // On Lion the call to CUIDraw doesn't draw the top pixel strip at some michael@0: // window widths. We don't want to have a flickering transparent line, so michael@0: // we overdraw it. michael@0: CGContextSetRGBFillColor(aContext, 0.95, 0.95, 0.95, 1); michael@0: CGContextFillRect(aContext, CGRectMake(0, CGRectGetMaxY(aTitlebarRect) - 1, michael@0: aTitlebarRect.size.width, 1)); michael@0: } michael@0: } michael@0: michael@0: // Pattern draw callback for standard titlebar gradients and solid titlebar colors michael@0: static void michael@0: TitlebarDrawCallback(void* aInfo, CGContextRef aContext) michael@0: { michael@0: ToolbarWindow *window = (ToolbarWindow*)aInfo; michael@0: if (![window drawsContentsIntoWindowFrame]) { michael@0: NSRect titlebarRect = [window titlebarRect]; michael@0: BOOL isMain = [window isMainWindow]; michael@0: NSColor *titlebarColor = [window titlebarColorForActiveWindow:isMain]; michael@0: if (!titlebarColor) { michael@0: // If the titlebar color is nil, draw the default titlebar shading. michael@0: DrawNativeTitlebar(aContext, NSRectToCGRect(titlebarRect), michael@0: [window unifiedToolbarHeight], isMain); michael@0: } else { michael@0: // If the titlebar color is not nil, just set and draw it normally. michael@0: [NSGraphicsContext saveGraphicsState]; michael@0: [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:aContext flipped:NO]]; michael@0: [titlebarColor set]; michael@0: NSRectFill(titlebarRect); michael@0: [NSGraphicsContext restoreGraphicsState]; michael@0: } michael@0: } michael@0: } michael@0: michael@0: - (void)setFill michael@0: { michael@0: float patternWidth = [mWindow frame].size.width; michael@0: michael@0: CGPatternCallbacks callbacks = {0, &TitlebarDrawCallback, NULL}; michael@0: CGPatternRef pattern = CGPatternCreate(mWindow, CGRectMake(0.0f, 0.0f, patternWidth, [mWindow frame].size.height), michael@0: CGAffineTransformIdentity, patternWidth, [mWindow frame].size.height, michael@0: kCGPatternTilingConstantSpacing, true, &callbacks); michael@0: michael@0: // Set the pattern as the fill, which is what we were asked to do. All our michael@0: // drawing will take place in the patternDraw callback. michael@0: CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL); michael@0: CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; michael@0: CGContextSetFillColorSpace(context, patternSpace); michael@0: CGColorSpaceRelease(patternSpace); michael@0: CGFloat component = 1.0f; michael@0: CGContextSetFillPattern(context, pattern, &component); michael@0: CGPatternRelease(pattern); michael@0: } michael@0: michael@0: - (void)set michael@0: { michael@0: [self setFill]; michael@0: } michael@0: michael@0: - (NSString*)colorSpaceName michael@0: { michael@0: return NSDeviceRGBColorSpace; michael@0: } michael@0: michael@0: @end michael@0: michael@0: @implementation PopupWindow michael@0: michael@0: - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask michael@0: backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: mIsContextMenu = false; michael@0: return [super initWithContentRect:contentRect styleMask:styleMask michael@0: backing:bufferingType defer:deferCreation]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: - (BOOL)isContextMenu michael@0: { michael@0: return mIsContextMenu; michael@0: } michael@0: michael@0: - (void)setIsContextMenu:(BOOL)flag michael@0: { michael@0: mIsContextMenu = flag; michael@0: } michael@0: michael@0: - (BOOL)canBecomeMainWindow michael@0: { michael@0: // This is overriden because the default is 'yes' when a titlebar is present. michael@0: return NO; michael@0: } michael@0: michael@0: @end michael@0: michael@0: // According to Apple's docs on [NSWindow canBecomeKeyWindow] and [NSWindow michael@0: // canBecomeMainWindow], windows without a title bar or resize bar can't (by michael@0: // default) become key or main. But if a window can't become key, it can't michael@0: // accept keyboard input (bmo bug 393250). And it should also be possible for michael@0: // an otherwise "ordinary" window to become main. We need to override these michael@0: // two methods to make this happen. michael@0: @implementation BorderlessWindow michael@0: michael@0: - (BOOL)canBecomeKeyWindow michael@0: { michael@0: return YES; michael@0: } michael@0: michael@0: - (void)sendEvent:(NSEvent *)anEvent michael@0: { michael@0: NSEventType type = [anEvent type]; michael@0: michael@0: switch (type) { michael@0: case NSScrollWheel: michael@0: case NSLeftMouseDown: michael@0: case NSLeftMouseUp: michael@0: case NSRightMouseDown: michael@0: case NSRightMouseUp: michael@0: case NSOtherMouseDown: michael@0: case NSOtherMouseUp: michael@0: case NSMouseMoved: michael@0: case NSLeftMouseDragged: michael@0: case NSRightMouseDragged: michael@0: case NSOtherMouseDragged: michael@0: { michael@0: // Drop all mouse events if a modal window has appeared above us. michael@0: // This helps make us behave as if the OS were running a "real" modal michael@0: // event loop. michael@0: id delegate = [self delegate]; michael@0: if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) { michael@0: nsCocoaWindow *widget = [(WindowDelegate *)delegate geckoWidget]; michael@0: if (widget) { michael@0: if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window)) michael@0: return; michael@0: if (widget->HasModalDescendents()) michael@0: return; michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: [super sendEvent:anEvent]; michael@0: } michael@0: michael@0: // Apple's doc on this method says that the NSWindow class's default is not to michael@0: // become main if the window isn't "visible" -- so we should replicate that michael@0: // behavior here. As best I can tell, the [NSWindow isVisible] method is an michael@0: // accurate test of what Apple means by "visibility". michael@0: - (BOOL)canBecomeMainWindow michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: if (![self isVisible]) michael@0: return NO; michael@0: return YES; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); michael@0: } michael@0: michael@0: // Retain and release "self" to avoid crashes when our widget (and its native michael@0: // window) is closed as a result of processing a key equivalent (e.g. michael@0: // Command+w or Command+q). This workaround is only needed for a window michael@0: // that can become key. michael@0: - (BOOL)performKeyEquivalent:(NSEvent*)theEvent michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: NSWindow *nativeWindow = [self retain]; michael@0: BOOL retval = [super performKeyEquivalent:theEvent]; michael@0: [nativeWindow release]; michael@0: return retval; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); michael@0: } michael@0: michael@0: @end