widget/cocoa/nsCocoaWindow.mm

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

michael@0 1 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include "nsCocoaWindow.h"
michael@0 7
michael@0 8 #include "NativeKeyBindings.h"
michael@0 9 #include "TextInputHandler.h"
michael@0 10 #include "nsObjCExceptions.h"
michael@0 11 #include "nsCOMPtr.h"
michael@0 12 #include "nsWidgetsCID.h"
michael@0 13 #include "nsIRollupListener.h"
michael@0 14 #include "nsChildView.h"
michael@0 15 #include "nsWindowMap.h"
michael@0 16 #include "nsAppShell.h"
michael@0 17 #include "nsIAppShellService.h"
michael@0 18 #include "nsIBaseWindow.h"
michael@0 19 #include "nsIInterfaceRequestorUtils.h"
michael@0 20 #include "nsIXULWindow.h"
michael@0 21 #include "nsToolkit.h"
michael@0 22 #include "nsIDOMWindow.h"
michael@0 23 #include "nsPIDOMWindow.h"
michael@0 24 #include "nsIDOMElement.h"
michael@0 25 #include "nsThreadUtils.h"
michael@0 26 #include "nsMenuBarX.h"
michael@0 27 #include "nsMenuUtilsX.h"
michael@0 28 #include "nsStyleConsts.h"
michael@0 29 #include "nsNativeThemeColors.h"
michael@0 30 #include "nsChildView.h"
michael@0 31 #include "nsCocoaFeatures.h"
michael@0 32 #include "nsIScreenManager.h"
michael@0 33 #include "nsIWidgetListener.h"
michael@0 34 #include "nsIPresShell.h"
michael@0 35
michael@0 36 #include "gfxPlatform.h"
michael@0 37 #include "qcms.h"
michael@0 38
michael@0 39 #include "mozilla/AutoRestore.h"
michael@0 40 #include "mozilla/BasicEvents.h"
michael@0 41 #include "mozilla/Preferences.h"
michael@0 42 #include <algorithm>
michael@0 43
michael@0 44 namespace mozilla {
michael@0 45 namespace layers {
michael@0 46 class LayerManager;
michael@0 47 }
michael@0 48 }
michael@0 49 using namespace mozilla::layers;
michael@0 50 using namespace mozilla::widget;
michael@0 51 using namespace mozilla;
michael@0 52
michael@0 53 // defined in nsAppShell.mm
michael@0 54 extern nsCocoaAppModalWindowList *gCocoaAppModalWindowList;
michael@0 55
michael@0 56 int32_t gXULModalLevel = 0;
michael@0 57
michael@0 58 // In principle there should be only one app-modal window at any given time.
michael@0 59 // But sometimes, despite our best efforts, another window appears above the
michael@0 60 // current app-modal window. So we need to keep a linked list of app-modal
michael@0 61 // windows. (A non-sheet window that appears above an app-modal window is
michael@0 62 // also made app-modal.) See nsCocoaWindow::SetModal().
michael@0 63 nsCocoaWindowList *gGeckoAppModalWindowList = NULL;
michael@0 64
michael@0 65 // defined in nsMenuBarX.mm
michael@0 66 extern NSMenu* sApplicationMenu; // Application menu shared by all menubars
michael@0 67
michael@0 68 // defined in nsChildView.mm
michael@0 69 extern BOOL gSomeMenuBarPainted;
michael@0 70
michael@0 71 extern "C" {
michael@0 72 // CGSPrivate.h
michael@0 73 typedef NSInteger CGSConnection;
michael@0 74 typedef NSInteger CGSWindow;
michael@0 75 typedef NSUInteger CGSWindowFilterRef;
michael@0 76 extern CGSConnection _CGSDefaultConnection(void);
michael@0 77 extern CGError CGSSetWindowShadowAndRimParameters(const CGSConnection cid, CGSWindow wid, float standardDeviation, float density, int offsetX, int offsetY, unsigned int flags);
michael@0 78 extern CGError CGSSetWindowBackgroundBlurRadius(CGSConnection cid, CGSWindow wid, NSUInteger blur);
michael@0 79 }
michael@0 80
michael@0 81 #define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
michael@0 82
michael@0 83 NS_IMPL_ISUPPORTS_INHERITED(nsCocoaWindow, Inherited, nsPIWidgetCocoa)
michael@0 84
michael@0 85 // A note on testing to see if your object is a sheet...
michael@0 86 // |mWindowType == eWindowType_sheet| is true if your gecko nsIWidget is a sheet
michael@0 87 // widget - whether or not the sheet is showing. |[mWindow isSheet]| will return
michael@0 88 // true *only when the sheet is actually showing*. Choose your test wisely.
michael@0 89
michael@0 90 static void RollUpPopups()
michael@0 91 {
michael@0 92 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
michael@0 93 NS_ENSURE_TRUE_VOID(rollupListener);
michael@0 94 nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
michael@0 95 if (!rollupWidget)
michael@0 96 return;
michael@0 97 rollupListener->Rollup(0, nullptr, nullptr);
michael@0 98 }
michael@0 99
michael@0 100 nsCocoaWindow::nsCocoaWindow()
michael@0 101 : mParent(nullptr)
michael@0 102 , mWindow(nil)
michael@0 103 , mDelegate(nil)
michael@0 104 , mSheetWindowParent(nil)
michael@0 105 , mPopupContentView(nil)
michael@0 106 , mShadowStyle(NS_STYLE_WINDOW_SHADOW_DEFAULT)
michael@0 107 , mBackingScaleFactor(0.0)
michael@0 108 , mAnimationType(nsIWidget::eGenericWindowAnimation)
michael@0 109 , mWindowMadeHere(false)
michael@0 110 , mSheetNeedsShow(false)
michael@0 111 , mFullScreen(false)
michael@0 112 , mInFullScreenTransition(false)
michael@0 113 , mModal(false)
michael@0 114 , mUsesNativeFullScreen(false)
michael@0 115 , mIsAnimationSuppressed(false)
michael@0 116 , mInReportMoveEvent(false)
michael@0 117 , mInResize(false)
michael@0 118 , mNumModalDescendents(0)
michael@0 119 {
michael@0 120
michael@0 121 }
michael@0 122
michael@0 123 void nsCocoaWindow::DestroyNativeWindow()
michael@0 124 {
michael@0 125 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 126
michael@0 127 if (!mWindow)
michael@0 128 return;
michael@0 129
michael@0 130 // We want to unhook the delegate here because we don't want events
michael@0 131 // sent to it after this object has been destroyed.
michael@0 132 [mWindow setDelegate:nil];
michael@0 133 [mWindow close];
michael@0 134 mWindow = nil;
michael@0 135 [mDelegate autorelease];
michael@0 136
michael@0 137 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 138 }
michael@0 139
michael@0 140 nsCocoaWindow::~nsCocoaWindow()
michael@0 141 {
michael@0 142 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 143
michael@0 144 // Notify the children that we're gone. Popup windows (e.g. tooltips) can
michael@0 145 // have nsChildView children. 'kid' is an nsChildView object if and only if
michael@0 146 // its 'type' is 'eWindowType_child' or 'eWindowType_plugin'.
michael@0 147 // childView->ResetParent() can change our list of children while it's
michael@0 148 // being iterated, so the way we iterate the list must allow for this.
michael@0 149 for (nsIWidget* kid = mLastChild; kid;) {
michael@0 150 nsWindowType kidType = kid->WindowType();
michael@0 151 if (kidType == eWindowType_child || kidType == eWindowType_plugin) {
michael@0 152 nsChildView* childView = static_cast<nsChildView*>(kid);
michael@0 153 kid = kid->GetPrevSibling();
michael@0 154 childView->ResetParent();
michael@0 155 } else {
michael@0 156 nsCocoaWindow* childWindow = static_cast<nsCocoaWindow*>(kid);
michael@0 157 childWindow->mParent = nullptr;
michael@0 158 kid = kid->GetPrevSibling();
michael@0 159 }
michael@0 160 }
michael@0 161
michael@0 162 if (mWindow && mWindowMadeHere) {
michael@0 163 DestroyNativeWindow();
michael@0 164 }
michael@0 165
michael@0 166 NS_IF_RELEASE(mPopupContentView);
michael@0 167
michael@0 168 // Deal with the possiblity that we're being destroyed while running modal.
michael@0 169 if (mModal) {
michael@0 170 NS_WARNING("Widget destroyed while running modal!");
michael@0 171 --gXULModalLevel;
michael@0 172 NS_ASSERTION(gXULModalLevel >= 0, "Wierdness setting modality!");
michael@0 173 }
michael@0 174
michael@0 175 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 176 }
michael@0 177
michael@0 178 // Find the screen that overlaps aRect the most,
michael@0 179 // if none are found default to the mainScreen.
michael@0 180 static NSScreen *FindTargetScreenForRect(const nsIntRect& aRect)
michael@0 181 {
michael@0 182 NSScreen *targetScreen = [NSScreen mainScreen];
michael@0 183 NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator];
michael@0 184 int largestIntersectArea = 0;
michael@0 185 while (NSScreen *screen = [screenEnum nextObject]) {
michael@0 186 nsIntRect screenRect(nsCocoaUtils::CocoaRectToGeckoRect([screen visibleFrame]));
michael@0 187 screenRect = screenRect.Intersect(aRect);
michael@0 188 int area = screenRect.width * screenRect.height;
michael@0 189 if (area > largestIntersectArea) {
michael@0 190 largestIntersectArea = area;
michael@0 191 targetScreen = screen;
michael@0 192 }
michael@0 193 }
michael@0 194 return targetScreen;
michael@0 195 }
michael@0 196
michael@0 197 // fits the rect to the screen that contains the largest area of it,
michael@0 198 // or to aScreen if a screen is passed in
michael@0 199 // NB: this operates with aRect in global display pixels
michael@0 200 static void FitRectToVisibleAreaForScreen(nsIntRect &aRect, NSScreen *aScreen,
michael@0 201 bool aUsesNativeFullScreen)
michael@0 202 {
michael@0 203 if (!aScreen) {
michael@0 204 aScreen = FindTargetScreenForRect(aRect);
michael@0 205 }
michael@0 206
michael@0 207 nsIntRect screenBounds(nsCocoaUtils::CocoaRectToGeckoRect([aScreen visibleFrame]));
michael@0 208
michael@0 209 if (aRect.width > screenBounds.width) {
michael@0 210 aRect.width = screenBounds.width;
michael@0 211 }
michael@0 212 if (aRect.height > screenBounds.height) {
michael@0 213 aRect.height = screenBounds.height;
michael@0 214 }
michael@0 215
michael@0 216 if (aRect.x - screenBounds.x + aRect.width > screenBounds.width) {
michael@0 217 aRect.x += screenBounds.width - (aRect.x - screenBounds.x + aRect.width);
michael@0 218 }
michael@0 219 if (aRect.y - screenBounds.y + aRect.height > screenBounds.height) {
michael@0 220 aRect.y += screenBounds.height - (aRect.y - screenBounds.y + aRect.height);
michael@0 221 }
michael@0 222
michael@0 223 // If the left/top edge of the window is off the screen in either direction,
michael@0 224 // then set the window to start at the left/top edge of the screen.
michael@0 225 if (aRect.x < screenBounds.x || aRect.x > (screenBounds.x + screenBounds.width)) {
michael@0 226 aRect.x = screenBounds.x;
michael@0 227 }
michael@0 228 if (aRect.y < screenBounds.y || aRect.y > (screenBounds.y + screenBounds.height)) {
michael@0 229 aRect.y = screenBounds.y;
michael@0 230 }
michael@0 231
michael@0 232 // If aRect is filling the screen and the window supports native (Lion-style)
michael@0 233 // fullscreen mode, reduce aRect's height and shift it down by 22 pixels
michael@0 234 // (nominally the height of the menu bar or of a window's title bar). For
michael@0 235 // some reason this works around bug 740923. Yes, it's a bodacious hack.
michael@0 236 // But until we know more it will have to do.
michael@0 237 if (aUsesNativeFullScreen && aRect.y == 0 && aRect.height == screenBounds.height) {
michael@0 238 aRect.y = 22;
michael@0 239 aRect.height -= 22;
michael@0 240 }
michael@0 241 }
michael@0 242
michael@0 243 // Some applications like Camino use native popup windows
michael@0 244 // (native context menus, native tooltips)
michael@0 245 static bool UseNativePopupWindows()
michael@0 246 {
michael@0 247 #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
michael@0 248 return true;
michael@0 249 #else
michael@0 250 return false;
michael@0 251 #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
michael@0 252 }
michael@0 253
michael@0 254 // aRect here is specified in global display pixels
michael@0 255 nsresult nsCocoaWindow::Create(nsIWidget *aParent,
michael@0 256 nsNativeWidget aNativeParent,
michael@0 257 const nsIntRect &aRect,
michael@0 258 nsDeviceContext *aContext,
michael@0 259 nsWidgetInitData *aInitData)
michael@0 260 {
michael@0 261 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 262
michael@0 263 // Because the hidden window is created outside of an event loop,
michael@0 264 // we have to provide an autorelease pool (see bug 559075).
michael@0 265 nsAutoreleasePool localPool;
michael@0 266
michael@0 267 nsIntRect newBounds = aRect;
michael@0 268 FitRectToVisibleAreaForScreen(newBounds, nullptr, mUsesNativeFullScreen);
michael@0 269
michael@0 270 // Set defaults which can be overriden from aInitData in BaseCreate
michael@0 271 mWindowType = eWindowType_toplevel;
michael@0 272 mBorderStyle = eBorderStyle_default;
michael@0 273
michael@0 274 // Ensure that the toolkit is created.
michael@0 275 nsToolkit::GetToolkit();
michael@0 276
michael@0 277 // newBounds is still display (global screen) pixels at this point;
michael@0 278 // fortunately, BaseCreate doesn't actually use it so we don't
michael@0 279 // need to worry about trying to convert it to device pixels
michael@0 280 // when we don't have a window (or dev context, perhaps) yet
michael@0 281 Inherited::BaseCreate(aParent, newBounds, aContext, aInitData);
michael@0 282
michael@0 283 mParent = aParent;
michael@0 284
michael@0 285 // Applications that use native popups don't want us to create popup windows.
michael@0 286 if ((mWindowType == eWindowType_popup) && UseNativePopupWindows())
michael@0 287 return NS_OK;
michael@0 288
michael@0 289 nsresult rv =
michael@0 290 CreateNativeWindow(nsCocoaUtils::GeckoRectToCocoaRect(newBounds),
michael@0 291 mBorderStyle, false);
michael@0 292 NS_ENSURE_SUCCESS(rv, rv);
michael@0 293
michael@0 294 if (mWindowType == eWindowType_popup) {
michael@0 295 if (aInitData->mMouseTransparent) {
michael@0 296 [mWindow setIgnoresMouseEvents:YES];
michael@0 297 }
michael@0 298 // now we can convert newBounds to device pixels for the window we created,
michael@0 299 // as the child view expects a rect expressed in the dev pix of its parent
michael@0 300 double scale = BackingScaleFactor();
michael@0 301 newBounds.x *= scale;
michael@0 302 newBounds.y *= scale;
michael@0 303 newBounds.width *= scale;
michael@0 304 newBounds.height *= scale;
michael@0 305 return CreatePopupContentView(newBounds, aContext);
michael@0 306 }
michael@0 307
michael@0 308 mIsAnimationSuppressed = aInitData->mIsAnimationSuppressed;
michael@0 309
michael@0 310 return NS_OK;
michael@0 311
michael@0 312 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 313 }
michael@0 314
michael@0 315 static unsigned int WindowMaskForBorderStyle(nsBorderStyle aBorderStyle)
michael@0 316 {
michael@0 317 bool allOrDefault = (aBorderStyle == eBorderStyle_all ||
michael@0 318 aBorderStyle == eBorderStyle_default);
michael@0 319
michael@0 320 /* Apple's docs on NSWindow styles say that "a window's style mask should
michael@0 321 * include NSTitledWindowMask if it includes any of the others [besides
michael@0 322 * NSBorderlessWindowMask]". This implies that a borderless window
michael@0 323 * shouldn't have any other styles than NSBorderlessWindowMask.
michael@0 324 */
michael@0 325 if (!allOrDefault && !(aBorderStyle & eBorderStyle_title))
michael@0 326 return NSBorderlessWindowMask;
michael@0 327
michael@0 328 unsigned int mask = NSTitledWindowMask;
michael@0 329 if (allOrDefault || aBorderStyle & eBorderStyle_close)
michael@0 330 mask |= NSClosableWindowMask;
michael@0 331 if (allOrDefault || aBorderStyle & eBorderStyle_minimize)
michael@0 332 mask |= NSMiniaturizableWindowMask;
michael@0 333 if (allOrDefault || aBorderStyle & eBorderStyle_resizeh)
michael@0 334 mask |= NSResizableWindowMask;
michael@0 335
michael@0 336 return mask;
michael@0 337 }
michael@0 338
michael@0 339 NS_IMETHODIMP nsCocoaWindow::ReparentNativeWidget(nsIWidget* aNewParent)
michael@0 340 {
michael@0 341 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 342 }
michael@0 343
michael@0 344 // If aRectIsFrameRect, aRect specifies the frame rect of the new window.
michael@0 345 // Otherwise, aRect.x/y specify the position of the window's frame relative to
michael@0 346 // the bottom of the menubar and aRect.width/height specify the size of the
michael@0 347 // content rect.
michael@0 348 nsresult nsCocoaWindow::CreateNativeWindow(const NSRect &aRect,
michael@0 349 nsBorderStyle aBorderStyle,
michael@0 350 bool aRectIsFrameRect)
michael@0 351 {
michael@0 352 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 353
michael@0 354 // We default to NSBorderlessWindowMask, add features if needed.
michael@0 355 unsigned int features = NSBorderlessWindowMask;
michael@0 356
michael@0 357 // Configure the window we will create based on the window type.
michael@0 358 switch (mWindowType)
michael@0 359 {
michael@0 360 case eWindowType_invisible:
michael@0 361 case eWindowType_child:
michael@0 362 case eWindowType_plugin:
michael@0 363 break;
michael@0 364 case eWindowType_popup:
michael@0 365 if (aBorderStyle != eBorderStyle_default && mBorderStyle & eBorderStyle_title) {
michael@0 366 features |= NSTitledWindowMask;
michael@0 367 if (aBorderStyle & eBorderStyle_close) {
michael@0 368 features |= NSClosableWindowMask;
michael@0 369 }
michael@0 370 }
michael@0 371 break;
michael@0 372 case eWindowType_toplevel:
michael@0 373 case eWindowType_dialog:
michael@0 374 features = WindowMaskForBorderStyle(aBorderStyle);
michael@0 375 break;
michael@0 376 case eWindowType_sheet:
michael@0 377 if (mParent->WindowType() != eWindowType_invisible &&
michael@0 378 aBorderStyle & eBorderStyle_resizeh) {
michael@0 379 features = NSResizableWindowMask;
michael@0 380 }
michael@0 381 else {
michael@0 382 features = NSMiniaturizableWindowMask;
michael@0 383 }
michael@0 384 features |= NSTitledWindowMask;
michael@0 385 break;
michael@0 386 default:
michael@0 387 NS_ERROR("Unhandled window type!");
michael@0 388 return NS_ERROR_FAILURE;
michael@0 389 }
michael@0 390
michael@0 391 NSRect contentRect;
michael@0 392
michael@0 393 if (aRectIsFrameRect) {
michael@0 394 contentRect = [NSWindow contentRectForFrameRect:aRect styleMask:features];
michael@0 395 } else {
michael@0 396 /*
michael@0 397 * We pass a content area rect to initialize the native Cocoa window. The
michael@0 398 * content rect we give is the same size as the size we're given by gecko.
michael@0 399 * The origin we're given for non-popup windows is moved down by the height
michael@0 400 * of the menu bar so that an origin of (0,100) from gecko puts the window
michael@0 401 * 100 pixels below the top of the available desktop area. We also move the
michael@0 402 * origin down by the height of a title bar if it exists. This is so the
michael@0 403 * origin that gecko gives us for the top-left of the window turns out to
michael@0 404 * be the top-left of the window we create. This is how it was done in
michael@0 405 * Carbon. If it ought to be different we'll probably need to look at all
michael@0 406 * the callers.
michael@0 407 *
michael@0 408 * Note: This means that if you put a secondary screen on top of your main
michael@0 409 * screen and open a window in the top screen, it'll be incorrectly shifted
michael@0 410 * down by the height of the menu bar. Same thing would happen in Carbon.
michael@0 411 *
michael@0 412 * Note: If you pass a rect with 0,0 for an origin, the window ends up in a
michael@0 413 * weird place for some reason. This stops that without breaking popups.
michael@0 414 */
michael@0 415 // Compensate for difference between frame and content area height (e.g. title bar).
michael@0 416 NSRect newWindowFrame = [NSWindow frameRectForContentRect:aRect styleMask:features];
michael@0 417
michael@0 418 contentRect = aRect;
michael@0 419 contentRect.origin.y -= (newWindowFrame.size.height - aRect.size.height);
michael@0 420
michael@0 421 if (mWindowType != eWindowType_popup)
michael@0 422 contentRect.origin.y -= [[NSApp mainMenu] menuBarHeight];
michael@0 423 }
michael@0 424
michael@0 425 // NSLog(@"Top-level window being created at Cocoa rect: %f, %f, %f, %f\n",
michael@0 426 // rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
michael@0 427
michael@0 428 Class windowClass = [BaseWindow class];
michael@0 429 // If we have a titlebar on a top-level window, we want to be able to control the
michael@0 430 // titlebar color (for unified windows), so use the special ToolbarWindow class.
michael@0 431 // Note that we need to check the window type because we mark sheets as
michael@0 432 // having titlebars.
michael@0 433 if ((mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) &&
michael@0 434 (features & NSTitledWindowMask))
michael@0 435 windowClass = [ToolbarWindow class];
michael@0 436 // If we're a popup window we need to use the PopupWindow class.
michael@0 437 else if (mWindowType == eWindowType_popup)
michael@0 438 windowClass = [PopupWindow class];
michael@0 439 // If we're a non-popup borderless window we need to use the
michael@0 440 // BorderlessWindow class.
michael@0 441 else if (features == NSBorderlessWindowMask)
michael@0 442 windowClass = [BorderlessWindow class];
michael@0 443
michael@0 444 // Create the window
michael@0 445 mWindow = [[windowClass alloc] initWithContentRect:contentRect styleMask:features
michael@0 446 backing:NSBackingStoreBuffered defer:YES];
michael@0 447
michael@0 448 // setup our notification delegate. Note that setDelegate: does NOT retain.
michael@0 449 mDelegate = [[WindowDelegate alloc] initWithGeckoWindow:this];
michael@0 450 [mWindow setDelegate:mDelegate];
michael@0 451
michael@0 452 // Make sure that the content rect we gave has been honored.
michael@0 453 NSRect wantedFrame = [mWindow frameRectForContentRect:contentRect];
michael@0 454 if (!NSEqualRects([mWindow frame], wantedFrame)) {
michael@0 455 // This can happen when the window is not on the primary screen.
michael@0 456 [mWindow setFrame:wantedFrame display:NO];
michael@0 457 }
michael@0 458 UpdateBounds();
michael@0 459
michael@0 460 if (mWindowType == eWindowType_invisible) {
michael@0 461 [mWindow setLevel:kCGDesktopWindowLevelKey];
michael@0 462 } else if (mWindowType == eWindowType_popup) {
michael@0 463 SetPopupWindowLevel();
michael@0 464 [mWindow setHasShadow:YES];
michael@0 465 }
michael@0 466
michael@0 467 [mWindow setBackgroundColor:[NSColor clearColor]];
michael@0 468 #ifdef MOZ_B2G
michael@0 469 // In B2G, we don't create popups and we need OMTC to work (because out of
michael@0 470 // process compositing depends on it). Therefore, we don't need our windows
michael@0 471 // to be transparent.
michael@0 472 [mWindow setOpaque:YES];
michael@0 473 #else
michael@0 474 [mWindow setOpaque:NO];
michael@0 475 #endif
michael@0 476 [mWindow setContentMinSize:NSMakeSize(60, 60)];
michael@0 477 [mWindow disableCursorRects];
michael@0 478
michael@0 479 // Make sure the window starts out not draggable by the background.
michael@0 480 // We will turn it on as necessary.
michael@0 481 [mWindow setMovableByWindowBackground:NO];
michael@0 482
michael@0 483 [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
michael@0 484 mWindowMadeHere = true;
michael@0 485
michael@0 486 return NS_OK;
michael@0 487
michael@0 488 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 489 }
michael@0 490
michael@0 491 NS_IMETHODIMP nsCocoaWindow::CreatePopupContentView(const nsIntRect &aRect,
michael@0 492 nsDeviceContext *aContext)
michael@0 493 {
michael@0 494 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 495
michael@0 496 // We need to make our content view a ChildView.
michael@0 497 mPopupContentView = new nsChildView();
michael@0 498 if (!mPopupContentView)
michael@0 499 return NS_ERROR_FAILURE;
michael@0 500
michael@0 501 NS_ADDREF(mPopupContentView);
michael@0 502
michael@0 503 nsIWidget* thisAsWidget = static_cast<nsIWidget*>(this);
michael@0 504 mPopupContentView->Create(thisAsWidget, nullptr, aRect, aContext, nullptr);
michael@0 505
michael@0 506 ChildView* newContentView = (ChildView*)mPopupContentView->GetNativeData(NS_NATIVE_WIDGET);
michael@0 507 [mWindow setContentView:newContentView];
michael@0 508
michael@0 509 return NS_OK;
michael@0 510
michael@0 511 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 512 }
michael@0 513
michael@0 514 NS_IMETHODIMP nsCocoaWindow::Destroy()
michael@0 515 {
michael@0 516 // If we don't hide here we run into problems with panels, this is not ideal.
michael@0 517 // (Bug 891424)
michael@0 518 Show(false);
michael@0 519
michael@0 520 if (mPopupContentView)
michael@0 521 mPopupContentView->Destroy();
michael@0 522
michael@0 523 nsBaseWidget::Destroy();
michael@0 524 // nsBaseWidget::Destroy() calls GetParent()->RemoveChild(this). But we
michael@0 525 // don't implement GetParent(), so we need to do the equivalent here.
michael@0 526 if (mParent) {
michael@0 527 mParent->RemoveChild(this);
michael@0 528 }
michael@0 529 nsBaseWidget::OnDestroy();
michael@0 530
michael@0 531 if (mFullScreen) {
michael@0 532 // On Lion we don't have to mess with the OS chrome when in Full Screen
michael@0 533 // mode. But we do have to destroy the native window here (and not wait
michael@0 534 // for that to happen in our destructor). We don't switch away from the
michael@0 535 // native window's space until the window is destroyed, and otherwise this
michael@0 536 // might not happen for several seconds (because at least one object
michael@0 537 // holding a reference to ourselves is usually waiting to be garbage-
michael@0 538 // collected). See bug 757618.
michael@0 539 if (mUsesNativeFullScreen) {
michael@0 540 DestroyNativeWindow();
michael@0 541 } else if (mWindow) {
michael@0 542 nsCocoaUtils::HideOSChromeOnScreen(false, [mWindow screen]);
michael@0 543 }
michael@0 544 }
michael@0 545
michael@0 546 return NS_OK;
michael@0 547 }
michael@0 548
michael@0 549 nsIWidget* nsCocoaWindow::GetSheetWindowParent(void)
michael@0 550 {
michael@0 551 if (mWindowType != eWindowType_sheet)
michael@0 552 return nullptr;
michael@0 553 nsCocoaWindow *parent = static_cast<nsCocoaWindow*>(mParent);
michael@0 554 while (parent && (parent->mWindowType == eWindowType_sheet))
michael@0 555 parent = static_cast<nsCocoaWindow*>(parent->mParent);
michael@0 556 return parent;
michael@0 557 }
michael@0 558
michael@0 559 void* nsCocoaWindow::GetNativeData(uint32_t aDataType)
michael@0 560 {
michael@0 561 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL;
michael@0 562
michael@0 563 void* retVal = nullptr;
michael@0 564
michael@0 565 switch (aDataType) {
michael@0 566 // to emulate how windows works, we always have to return a NSView
michael@0 567 // for NS_NATIVE_WIDGET
michael@0 568 case NS_NATIVE_WIDGET:
michael@0 569 case NS_NATIVE_DISPLAY:
michael@0 570 retVal = [mWindow contentView];
michael@0 571 break;
michael@0 572
michael@0 573 case NS_NATIVE_WINDOW:
michael@0 574 retVal = mWindow;
michael@0 575 break;
michael@0 576
michael@0 577 case NS_NATIVE_GRAPHIC:
michael@0 578 // There isn't anything that makes sense to return here,
michael@0 579 // and it doesn't matter so just return nullptr.
michael@0 580 NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a top-level window!");
michael@0 581 break;
michael@0 582 }
michael@0 583
michael@0 584 return retVal;
michael@0 585
michael@0 586 NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
michael@0 587 }
michael@0 588
michael@0 589 bool nsCocoaWindow::IsVisible() const
michael@0 590 {
michael@0 591 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 592
michael@0 593 return (mWindow && ([mWindow isVisibleOrBeingShown] || mSheetNeedsShow));
michael@0 594
michael@0 595 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
michael@0 596 }
michael@0 597
michael@0 598 NS_IMETHODIMP nsCocoaWindow::SetModal(bool aState)
michael@0 599 {
michael@0 600 if (!mWindow)
michael@0 601 return NS_OK;
michael@0 602
michael@0 603 // This is used during startup (outside the event loop) when creating
michael@0 604 // the add-ons compatibility checking dialog and the profile manager UI;
michael@0 605 // therefore, it needs to provide an autorelease pool to avoid cocoa
michael@0 606 // objects leaking.
michael@0 607 nsAutoreleasePool localPool;
michael@0 608
michael@0 609 mModal = aState;
michael@0 610 nsCocoaWindow *aParent = static_cast<nsCocoaWindow*>(mParent);
michael@0 611 if (aState) {
michael@0 612 ++gXULModalLevel;
michael@0 613 if (gCocoaAppModalWindowList)
michael@0 614 gCocoaAppModalWindowList->PushGecko(mWindow, this);
michael@0 615 // When a non-sheet window gets "set modal", make the window(s) that it
michael@0 616 // appears over behave as they should. We can't rely on native methods to
michael@0 617 // do this, for the following reason: The OS runs modal non-sheet windows
michael@0 618 // in an event loop (using [NSApplication runModalForWindow:] or similar
michael@0 619 // methods) that's incompatible with the modal event loop in nsXULWindow::
michael@0 620 // ShowModal() (each of these event loops is "exclusive", and can't run at
michael@0 621 // the same time as other (similar) event loops).
michael@0 622 if (mWindowType != eWindowType_sheet) {
michael@0 623 while (aParent) {
michael@0 624 if (aParent->mNumModalDescendents++ == 0) {
michael@0 625 NSWindow *aWindow = aParent->GetCocoaWindow();
michael@0 626 if (aParent->mWindowType != eWindowType_invisible) {
michael@0 627 [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:NO];
michael@0 628 [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:NO];
michael@0 629 [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:NO];
michael@0 630 }
michael@0 631 }
michael@0 632 aParent = static_cast<nsCocoaWindow*>(aParent->mParent);
michael@0 633 }
michael@0 634 [mWindow setLevel:NSModalPanelWindowLevel];
michael@0 635 nsCocoaWindowList *windowList = new nsCocoaWindowList;
michael@0 636 if (windowList) {
michael@0 637 windowList->window = this; // Don't ADDREF
michael@0 638 windowList->prev = gGeckoAppModalWindowList;
michael@0 639 gGeckoAppModalWindowList = windowList;
michael@0 640 }
michael@0 641 }
michael@0 642 }
michael@0 643 else {
michael@0 644 --gXULModalLevel;
michael@0 645 NS_ASSERTION(gXULModalLevel >= 0, "Mismatched call to nsCocoaWindow::SetModal(false)!");
michael@0 646 if (gCocoaAppModalWindowList)
michael@0 647 gCocoaAppModalWindowList->PopGecko(mWindow, this);
michael@0 648 if (mWindowType != eWindowType_sheet) {
michael@0 649 while (aParent) {
michael@0 650 if (--aParent->mNumModalDescendents == 0) {
michael@0 651 NSWindow *aWindow = aParent->GetCocoaWindow();
michael@0 652 if (aParent->mWindowType != eWindowType_invisible) {
michael@0 653 [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:YES];
michael@0 654 [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:YES];
michael@0 655 [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:YES];
michael@0 656 }
michael@0 657 }
michael@0 658 NS_ASSERTION(aParent->mNumModalDescendents >= 0, "Widget hierarchy changed while modal!");
michael@0 659 aParent = static_cast<nsCocoaWindow*>(aParent->mParent);
michael@0 660 }
michael@0 661 if (gGeckoAppModalWindowList) {
michael@0 662 NS_ASSERTION(gGeckoAppModalWindowList->window == this, "Widget hierarchy changed while modal!");
michael@0 663 nsCocoaWindowList *saved = gGeckoAppModalWindowList;
michael@0 664 gGeckoAppModalWindowList = gGeckoAppModalWindowList->prev;
michael@0 665 delete saved; // "window" not ADDREFed
michael@0 666 }
michael@0 667 if (mWindowType == eWindowType_popup)
michael@0 668 SetPopupWindowLevel();
michael@0 669 else
michael@0 670 [mWindow setLevel:NSNormalWindowLevel];
michael@0 671 }
michael@0 672 }
michael@0 673 return NS_OK;
michael@0 674 }
michael@0 675
michael@0 676 // Hide or show this window
michael@0 677 NS_IMETHODIMP nsCocoaWindow::Show(bool bState)
michael@0 678 {
michael@0 679 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 680
michael@0 681 if (!mWindow)
michael@0 682 return NS_OK;
michael@0 683
michael@0 684 // We need to re-execute sometimes in order to bring already-visible
michael@0 685 // windows forward.
michael@0 686 if (!mSheetNeedsShow && !bState && ![mWindow isVisible])
michael@0 687 return NS_OK;
michael@0 688
michael@0 689 // Protect against re-entering.
michael@0 690 if (bState && [mWindow isBeingShown])
michael@0 691 return NS_OK;
michael@0 692
michael@0 693 [mWindow setBeingShown:bState];
michael@0 694
michael@0 695 nsIWidget* parentWidget = mParent;
michael@0 696 nsCOMPtr<nsPIWidgetCocoa> piParentWidget(do_QueryInterface(parentWidget));
michael@0 697 NSWindow* nativeParentWindow = (parentWidget) ?
michael@0 698 (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW) : nil;
michael@0 699
michael@0 700 if (bState && !mBounds.IsEmpty()) {
michael@0 701 if (mPopupContentView) {
michael@0 702 // Ensure our content view is visible. We never need to hide it.
michael@0 703 mPopupContentView->Show(true);
michael@0 704 }
michael@0 705
michael@0 706 if (mWindowType == eWindowType_sheet) {
michael@0 707 // bail if no parent window (its basically what we do in Carbon)
michael@0 708 if (!nativeParentWindow || !piParentWidget)
michael@0 709 return NS_ERROR_FAILURE;
michael@0 710
michael@0 711 NSWindow* topNonSheetWindow = nativeParentWindow;
michael@0 712
michael@0 713 // If this sheet is the child of another sheet, hide the parent so that
michael@0 714 // this sheet can be displayed. Leave the parent mSheetNeedsShow alone,
michael@0 715 // that is only used to handle sibling sheet contention. The parent will
michael@0 716 // return once there are no more child sheets.
michael@0 717 bool parentIsSheet = false;
michael@0 718 if (NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) &&
michael@0 719 parentIsSheet) {
michael@0 720 piParentWidget->GetSheetWindowParent(&topNonSheetWindow);
michael@0 721 [NSApp endSheet:nativeParentWindow];
michael@0 722 }
michael@0 723
michael@0 724 nsCocoaWindow* sheetShown = nullptr;
michael@0 725 if (NS_SUCCEEDED(piParentWidget->GetChildSheet(true, &sheetShown)) &&
michael@0 726 (!sheetShown || sheetShown == this)) {
michael@0 727 // If this sheet is already the sheet actually being shown, don't
michael@0 728 // tell it to show again. Otherwise the number of calls to
michael@0 729 // [NSApp beginSheet...] won't match up with [NSApp endSheet...].
michael@0 730 if (![mWindow isVisible]) {
michael@0 731 mSheetNeedsShow = false;
michael@0 732 mSheetWindowParent = topNonSheetWindow;
michael@0 733 // Only set contextInfo if our parent isn't a sheet.
michael@0 734 NSWindow* contextInfo = parentIsSheet ? nil : mSheetWindowParent;
michael@0 735 [TopLevelWindowData deactivateInWindow:mSheetWindowParent];
michael@0 736 [NSApp beginSheet:mWindow
michael@0 737 modalForWindow:mSheetWindowParent
michael@0 738 modalDelegate:mDelegate
michael@0 739 didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
michael@0 740 contextInfo:contextInfo];
michael@0 741 [TopLevelWindowData activateInWindow:mWindow];
michael@0 742 SendSetZLevelEvent();
michael@0 743 }
michael@0 744 }
michael@0 745 else {
michael@0 746 // A sibling of this sheet is active, don't show this sheet yet.
michael@0 747 // When the active sheet hides, its brothers and sisters that have
michael@0 748 // mSheetNeedsShow set will have their opportunities to display.
michael@0 749 mSheetNeedsShow = true;
michael@0 750 }
michael@0 751 }
michael@0 752 else if (mWindowType == eWindowType_popup) {
michael@0 753 // If a popup window is shown after being hidden, it needs to be "reset"
michael@0 754 // for it to receive any mouse events aside from mouse-moved events
michael@0 755 // (because it was removed from the "window cache" when it was hidden
michael@0 756 // -- see below). Setting the window number to -1 and then back to its
michael@0 757 // original value seems to accomplish this. The idea was "borrowed"
michael@0 758 // from the Java Embedding Plugin.
michael@0 759 NSInteger windowNumber = [mWindow windowNumber];
michael@0 760 [mWindow _setWindowNumber:-1];
michael@0 761 [mWindow _setWindowNumber:windowNumber];
michael@0 762 // For reasons that aren't yet clear, calls to [NSWindow orderFront:] or
michael@0 763 // [NSWindow makeKeyAndOrderFront:] can sometimes trigger "Error (1000)
michael@0 764 // creating CGSWindow", which in turn triggers an internal inconsistency
michael@0 765 // NSException. These errors shouldn't be fatal. So we need to wrap
michael@0 766 // calls to ...orderFront: in LOGONLY blocks. See bmo bug 470864.
michael@0 767 NS_OBJC_BEGIN_TRY_LOGONLY_BLOCK;
michael@0 768 [[mWindow contentView] setNeedsDisplay:YES];
michael@0 769 [mWindow orderFront:nil];
michael@0 770 NS_OBJC_END_TRY_LOGONLY_BLOCK;
michael@0 771 SendSetZLevelEvent();
michael@0 772 AdjustWindowShadow();
michael@0 773 SetWindowBackgroundBlur();
michael@0 774 // If our popup window is a non-native context menu, tell the OS (and
michael@0 775 // other programs) that a menu has opened. This is how the OS knows to
michael@0 776 // close other programs' context menus when ours open.
michael@0 777 if ([mWindow isKindOfClass:[PopupWindow class]] &&
michael@0 778 [(PopupWindow*) mWindow isContextMenu]) {
michael@0 779 [[NSDistributedNotificationCenter defaultCenter]
michael@0 780 postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification"
michael@0 781 object:@"org.mozilla.gecko.PopupWindow"];
michael@0 782 }
michael@0 783
michael@0 784 // If a parent window was supplied and this is a popup at the parent
michael@0 785 // level, set its child window. This will cause the child window to
michael@0 786 // appear above the parent and move when the parent does. Setting this
michael@0 787 // needs to happen after the _setWindowNumber calls above, otherwise the
michael@0 788 // window doesn't focus properly.
michael@0 789 if (nativeParentWindow && mPopupLevel == ePopupLevelParent)
michael@0 790 [nativeParentWindow addChildWindow:mWindow
michael@0 791 ordered:NSWindowAbove];
michael@0 792 }
michael@0 793 else {
michael@0 794 NS_OBJC_BEGIN_TRY_LOGONLY_BLOCK;
michael@0 795 if (mWindowType == eWindowType_toplevel &&
michael@0 796 [mWindow respondsToSelector:@selector(setAnimationBehavior:)]) {
michael@0 797 NSWindowAnimationBehavior behavior;
michael@0 798 if (mIsAnimationSuppressed) {
michael@0 799 behavior = NSWindowAnimationBehaviorNone;
michael@0 800 } else {
michael@0 801 switch (mAnimationType) {
michael@0 802 case nsIWidget::eDocumentWindowAnimation:
michael@0 803 behavior = NSWindowAnimationBehaviorDocumentWindow;
michael@0 804 break;
michael@0 805 default:
michael@0 806 NS_NOTREACHED("unexpected mAnimationType value");
michael@0 807 // fall through
michael@0 808 case nsIWidget::eGenericWindowAnimation:
michael@0 809 behavior = NSWindowAnimationBehaviorDefault;
michael@0 810 break;
michael@0 811 }
michael@0 812 }
michael@0 813 [mWindow setAnimationBehavior:behavior];
michael@0 814 }
michael@0 815 [mWindow makeKeyAndOrderFront:nil];
michael@0 816 NS_OBJC_END_TRY_LOGONLY_BLOCK;
michael@0 817 SendSetZLevelEvent();
michael@0 818 }
michael@0 819 }
michael@0 820 else {
michael@0 821 // roll up any popups if a top-level window is going away
michael@0 822 if (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog)
michael@0 823 RollUpPopups();
michael@0 824
michael@0 825 // now get rid of the window/sheet
michael@0 826 if (mWindowType == eWindowType_sheet) {
michael@0 827 if (mSheetNeedsShow) {
michael@0 828 // This is an attempt to hide a sheet that never had a chance to
michael@0 829 // be shown. There's nothing to do other than make sure that it
michael@0 830 // won't show.
michael@0 831 mSheetNeedsShow = false;
michael@0 832 }
michael@0 833 else {
michael@0 834 // get sheet's parent *before* hiding the sheet (which breaks the linkage)
michael@0 835 NSWindow* sheetParent = mSheetWindowParent;
michael@0 836
michael@0 837 // hide the sheet
michael@0 838 [NSApp endSheet:mWindow];
michael@0 839
michael@0 840 [TopLevelWindowData deactivateInWindow:mWindow];
michael@0 841
michael@0 842 nsCocoaWindow* siblingSheetToShow = nullptr;
michael@0 843 bool parentIsSheet = false;
michael@0 844
michael@0 845 if (nativeParentWindow && piParentWidget &&
michael@0 846 NS_SUCCEEDED(piParentWidget->GetChildSheet(false, &siblingSheetToShow)) &&
michael@0 847 siblingSheetToShow) {
michael@0 848 // First, give sibling sheets an opportunity to show.
michael@0 849 siblingSheetToShow->Show(true);
michael@0 850 }
michael@0 851 else if (nativeParentWindow && piParentWidget &&
michael@0 852 NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) &&
michael@0 853 parentIsSheet) {
michael@0 854 // Only set contextInfo if the parent of the parent sheet we're about
michael@0 855 // to restore isn't itself a sheet.
michael@0 856 NSWindow* contextInfo = sheetParent;
michael@0 857 nsIWidget* grandparentWidget = nil;
michael@0 858 if (NS_SUCCEEDED(piParentWidget->GetRealParent(&grandparentWidget)) && grandparentWidget) {
michael@0 859 nsCOMPtr<nsPIWidgetCocoa> piGrandparentWidget(do_QueryInterface(grandparentWidget));
michael@0 860 bool grandparentIsSheet = false;
michael@0 861 if (piGrandparentWidget && NS_SUCCEEDED(piGrandparentWidget->GetIsSheet(&grandparentIsSheet)) &&
michael@0 862 grandparentIsSheet) {
michael@0 863 contextInfo = nil;
michael@0 864 }
michael@0 865 }
michael@0 866 // If there are no sibling sheets, but the parent is a sheet, restore
michael@0 867 // it. It wasn't sent any deactivate events when it was hidden, so
michael@0 868 // don't call through Show, just let the OS put it back up.
michael@0 869 [NSApp beginSheet:nativeParentWindow
michael@0 870 modalForWindow:sheetParent
michael@0 871 modalDelegate:[nativeParentWindow delegate]
michael@0 872 didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
michael@0 873 contextInfo:contextInfo];
michael@0 874 }
michael@0 875 else {
michael@0 876 // Sheet, that was hard. No more siblings or parents, going back
michael@0 877 // to a real window.
michael@0 878 NS_OBJC_BEGIN_TRY_LOGONLY_BLOCK;
michael@0 879 [sheetParent makeKeyAndOrderFront:nil];
michael@0 880 NS_OBJC_END_TRY_LOGONLY_BLOCK;
michael@0 881 }
michael@0 882 SendSetZLevelEvent();
michael@0 883 }
michael@0 884 }
michael@0 885 else {
michael@0 886 // If the window is a popup window with a parent window we need to
michael@0 887 // unhook it here before ordering it out. When you order out the child
michael@0 888 // of a window it hides the parent window.
michael@0 889 if (mWindowType == eWindowType_popup && nativeParentWindow)
michael@0 890 [nativeParentWindow removeChildWindow:mWindow];
michael@0 891
michael@0 892 [mWindow orderOut:nil];
michael@0 893 // Unless it's explicitly removed from NSApp's "window cache", a popup
michael@0 894 // window will keep receiving mouse-moved events even after it's been
michael@0 895 // "ordered out" (instead of the browser window that was underneath it,
michael@0 896 // until you click on that window). This is bmo bug 378645, but it's
michael@0 897 // surely an Apple bug. The "window cache" is an undocumented subsystem,
michael@0 898 // all of whose methods are included in the NSWindowCache category of
michael@0 899 // the NSApplication class (in header files generated using class-dump).
michael@0 900 // This workaround was "borrowed" from the Java Embedding Plugin (which
michael@0 901 // uses it for a different purpose).
michael@0 902 if (mWindowType == eWindowType_popup)
michael@0 903 [NSApp _removeWindowFromCache:mWindow];
michael@0 904
michael@0 905 // If our popup window is a non-native context menu, tell the OS (and
michael@0 906 // other programs) that a menu has closed.
michael@0 907 if ([mWindow isKindOfClass:[PopupWindow class]] &&
michael@0 908 [(PopupWindow*) mWindow isContextMenu]) {
michael@0 909 [[NSDistributedNotificationCenter defaultCenter]
michael@0 910 postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification"
michael@0 911 object:@"org.mozilla.gecko.PopupWindow"];
michael@0 912 }
michael@0 913 }
michael@0 914 }
michael@0 915
michael@0 916 [mWindow setBeingShown:NO];
michael@0 917
michael@0 918 return NS_OK;
michael@0 919
michael@0 920 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 921 }
michael@0 922
michael@0 923 struct ShadowParams {
michael@0 924 float standardDeviation;
michael@0 925 float density;
michael@0 926 int offsetX;
michael@0 927 int offsetY;
michael@0 928 unsigned int flags;
michael@0 929 };
michael@0 930
michael@0 931 // These numbers have been determined by looking at the results of
michael@0 932 // CGSGetWindowShadowAndRimParameters for native window types.
michael@0 933 static const ShadowParams kWindowShadowParameters[] = {
michael@0 934 { 0.0f, 0.0f, 0, 0, 0 }, // none
michael@0 935 { 8.0f, 0.5f, 0, 6, 1 }, // default
michael@0 936 { 10.0f, 0.44f, 0, 10, 512 }, // menu
michael@0 937 { 8.0f, 0.5f, 0, 6, 1 }, // tooltip
michael@0 938 { 4.0f, 0.6f, 0, 4, 512 } // sheet
michael@0 939 };
michael@0 940
michael@0 941 // This method will adjust the window shadow style for popup windows after
michael@0 942 // they have been made visible. Before they're visible, their window number
michael@0 943 // might be -1, which is not useful.
michael@0 944 // We won't attempt to change the shadow for windows that can acquire key state
michael@0 945 // since OS X will reset the shadow whenever that happens.
michael@0 946 void
michael@0 947 nsCocoaWindow::AdjustWindowShadow()
michael@0 948 {
michael@0 949 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 950
michael@0 951 if (!mWindow || ![mWindow isVisible] || ![mWindow hasShadow] ||
michael@0 952 [mWindow canBecomeKeyWindow] || [mWindow windowNumber] == -1)
michael@0 953 return;
michael@0 954
michael@0 955 const ShadowParams& params = kWindowShadowParameters[mShadowStyle];
michael@0 956 CGSConnection cid = _CGSDefaultConnection();
michael@0 957 CGSSetWindowShadowAndRimParameters(cid, [mWindow windowNumber],
michael@0 958 params.standardDeviation, params.density,
michael@0 959 params.offsetX, params.offsetY,
michael@0 960 params.flags);
michael@0 961
michael@0 962 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 963 }
michael@0 964
michael@0 965 static const NSUInteger kWindowBackgroundBlurRadius = 4;
michael@0 966
michael@0 967 void
michael@0 968 nsCocoaWindow::SetWindowBackgroundBlur()
michael@0 969 {
michael@0 970 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 971
michael@0 972 if (!mWindow || ![mWindow isVisible] || [mWindow windowNumber] == -1)
michael@0 973 return;
michael@0 974
michael@0 975 // Only blur the background of menus and fake sheets.
michael@0 976 if (mShadowStyle != NS_STYLE_WINDOW_SHADOW_MENU &&
michael@0 977 mShadowStyle != NS_STYLE_WINDOW_SHADOW_SHEET)
michael@0 978 return;
michael@0 979
michael@0 980 CGSConnection cid = _CGSDefaultConnection();
michael@0 981 CGSSetWindowBackgroundBlurRadius(cid, [mWindow windowNumber], kWindowBackgroundBlurRadius);
michael@0 982
michael@0 983 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 984 }
michael@0 985
michael@0 986 nsresult
michael@0 987 nsCocoaWindow::ConfigureChildren(const nsTArray<Configuration>& aConfigurations)
michael@0 988 {
michael@0 989 if (mPopupContentView) {
michael@0 990 mPopupContentView->ConfigureChildren(aConfigurations);
michael@0 991 }
michael@0 992 return NS_OK;
michael@0 993 }
michael@0 994
michael@0 995 LayerManager*
michael@0 996 nsCocoaWindow::GetLayerManager(PLayerTransactionChild* aShadowManager,
michael@0 997 LayersBackend aBackendHint,
michael@0 998 LayerManagerPersistence aPersistence,
michael@0 999 bool* aAllowRetaining)
michael@0 1000 {
michael@0 1001 if (mPopupContentView) {
michael@0 1002 return mPopupContentView->GetLayerManager(aShadowManager,
michael@0 1003 aBackendHint,
michael@0 1004 aPersistence,
michael@0 1005 aAllowRetaining);
michael@0 1006 }
michael@0 1007 return nullptr;
michael@0 1008 }
michael@0 1009
michael@0 1010 nsTransparencyMode nsCocoaWindow::GetTransparencyMode()
michael@0 1011 {
michael@0 1012 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 1013
michael@0 1014 return (!mWindow || [mWindow isOpaque]) ? eTransparencyOpaque : eTransparencyTransparent;
michael@0 1015
michael@0 1016 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(eTransparencyOpaque);
michael@0 1017 }
michael@0 1018
michael@0 1019 // This is called from nsMenuPopupFrame when making a popup transparent.
michael@0 1020 // For other window types, nsChildView::SetTransparencyMode is used.
michael@0 1021 void nsCocoaWindow::SetTransparencyMode(nsTransparencyMode aMode)
michael@0 1022 {
michael@0 1023 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 1024
michael@0 1025 if (!mWindow)
michael@0 1026 return;
michael@0 1027
michael@0 1028 BOOL isTransparent = aMode == eTransparencyTransparent;
michael@0 1029 BOOL currentTransparency = ![mWindow isOpaque];
michael@0 1030 if (isTransparent != currentTransparency) {
michael@0 1031 [mWindow setOpaque:!isTransparent];
michael@0 1032 [mWindow setBackgroundColor:(isTransparent ? [NSColor clearColor] : [NSColor whiteColor])];
michael@0 1033 }
michael@0 1034
michael@0 1035 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 1036 }
michael@0 1037
michael@0 1038 NS_IMETHODIMP nsCocoaWindow::Enable(bool aState)
michael@0 1039 {
michael@0 1040 return NS_OK;
michael@0 1041 }
michael@0 1042
michael@0 1043 bool nsCocoaWindow::IsEnabled() const
michael@0 1044 {
michael@0 1045 return true;
michael@0 1046 }
michael@0 1047
michael@0 1048 #define kWindowPositionSlop 20
michael@0 1049
michael@0 1050 NS_IMETHODIMP nsCocoaWindow::ConstrainPosition(bool aAllowSlop,
michael@0 1051 int32_t *aX, int32_t *aY)
michael@0 1052 {
michael@0 1053 if (!mWindow || ![mWindow screen]) {
michael@0 1054 return NS_OK;
michael@0 1055 }
michael@0 1056
michael@0 1057 nsIntRect screenBounds;
michael@0 1058
michael@0 1059 int32_t width, height;
michael@0 1060
michael@0 1061 NSRect frame = [mWindow frame];
michael@0 1062
michael@0 1063 // zero size rects confuse the screen manager
michael@0 1064 width = std::max<int32_t>(frame.size.width, 1);
michael@0 1065 height = std::max<int32_t>(frame.size.height, 1);
michael@0 1066
michael@0 1067 nsCOMPtr<nsIScreenManager> screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
michael@0 1068 if (screenMgr) {
michael@0 1069 nsCOMPtr<nsIScreen> screen;
michael@0 1070 screenMgr->ScreenForRect(*aX, *aY, width, height, getter_AddRefs(screen));
michael@0 1071
michael@0 1072 if (screen) {
michael@0 1073 screen->GetRectDisplayPix(&(screenBounds.x), &(screenBounds.y),
michael@0 1074 &(screenBounds.width), &(screenBounds.height));
michael@0 1075 }
michael@0 1076 }
michael@0 1077
michael@0 1078 if (aAllowSlop) {
michael@0 1079 if (*aX < screenBounds.x - width + kWindowPositionSlop) {
michael@0 1080 *aX = screenBounds.x - width + kWindowPositionSlop;
michael@0 1081 } else if (*aX >= screenBounds.x + screenBounds.width - kWindowPositionSlop) {
michael@0 1082 *aX = screenBounds.x + screenBounds.width - kWindowPositionSlop;
michael@0 1083 }
michael@0 1084
michael@0 1085 if (*aY < screenBounds.y - height + kWindowPositionSlop) {
michael@0 1086 *aY = screenBounds.y - height + kWindowPositionSlop;
michael@0 1087 } else if (*aY >= screenBounds.y + screenBounds.height - kWindowPositionSlop) {
michael@0 1088 *aY = screenBounds.y + screenBounds.height - kWindowPositionSlop;
michael@0 1089 }
michael@0 1090 } else {
michael@0 1091 if (*aX < screenBounds.x) {
michael@0 1092 *aX = screenBounds.x;
michael@0 1093 } else if (*aX >= screenBounds.x + screenBounds.width - width) {
michael@0 1094 *aX = screenBounds.x + screenBounds.width - width;
michael@0 1095 }
michael@0 1096
michael@0 1097 if (*aY < screenBounds.y) {
michael@0 1098 *aY = screenBounds.y;
michael@0 1099 } else if (*aY >= screenBounds.y + screenBounds.height - height) {
michael@0 1100 *aY = screenBounds.y + screenBounds.height - height;
michael@0 1101 }
michael@0 1102 }
michael@0 1103
michael@0 1104 return NS_OK;
michael@0 1105 }
michael@0 1106
michael@0 1107 void nsCocoaWindow::SetSizeConstraints(const SizeConstraints& aConstraints)
michael@0 1108 {
michael@0 1109 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 1110
michael@0 1111 // Popups can be smaller than (60, 60)
michael@0 1112 NSRect rect =
michael@0 1113 (mWindowType == eWindowType_popup) ? NSZeroRect : NSMakeRect(0.0, 0.0, 60, 60);
michael@0 1114 rect = [mWindow frameRectForContentRect:rect];
michael@0 1115
michael@0 1116 CGFloat scaleFactor = BackingScaleFactor();
michael@0 1117
michael@0 1118 SizeConstraints c = aConstraints;
michael@0 1119 c.mMinSize.width =
michael@0 1120 std::max(nsCocoaUtils::CocoaPointsToDevPixels(rect.size.width, scaleFactor),
michael@0 1121 c.mMinSize.width);
michael@0 1122 c.mMinSize.height =
michael@0 1123 std::max(nsCocoaUtils::CocoaPointsToDevPixels(rect.size.height, scaleFactor),
michael@0 1124 c.mMinSize.height);
michael@0 1125
michael@0 1126 NSSize minSize = {
michael@0 1127 nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.width, scaleFactor),
michael@0 1128 nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.height, scaleFactor)
michael@0 1129 };
michael@0 1130 [mWindow setMinSize:minSize];
michael@0 1131
michael@0 1132 NSSize maxSize = {
michael@0 1133 c.mMaxSize.width == NS_MAXSIZE ?
michael@0 1134 FLT_MAX : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.width, scaleFactor),
michael@0 1135 c.mMaxSize.height == NS_MAXSIZE ?
michael@0 1136 FLT_MAX : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.height, scaleFactor)
michael@0 1137 };
michael@0 1138 [mWindow setMaxSize:maxSize];
michael@0 1139
michael@0 1140 nsBaseWidget::SetSizeConstraints(c);
michael@0 1141
michael@0 1142 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 1143 }
michael@0 1144
michael@0 1145 // Coordinates are global display pixels
michael@0 1146 NS_IMETHODIMP nsCocoaWindow::Move(double aX, double aY)
michael@0 1147 {
michael@0 1148 if (!mWindow) {
michael@0 1149 return NS_OK;
michael@0 1150 }
michael@0 1151
michael@0 1152 // The point we have is in Gecko coordinates (origin top-left). Convert
michael@0 1153 // it to Cocoa ones (origin bottom-left).
michael@0 1154 NSPoint coord = {
michael@0 1155 static_cast<float>(aX),
michael@0 1156 static_cast<float>(nsCocoaUtils::FlippedScreenY(NSToIntRound(aY)))
michael@0 1157 };
michael@0 1158
michael@0 1159 NSRect frame = [mWindow frame];
michael@0 1160 if (frame.origin.x != coord.x ||
michael@0 1161 frame.origin.y + frame.size.height != coord.y) {
michael@0 1162 [mWindow setFrameTopLeftPoint:coord];
michael@0 1163 }
michael@0 1164
michael@0 1165 return NS_OK;
michael@0 1166 }
michael@0 1167
michael@0 1168 // Position the window behind the given window
michael@0 1169 NS_METHOD nsCocoaWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
michael@0 1170 nsIWidget *aWidget, bool aActivate)
michael@0 1171 {
michael@0 1172 return NS_OK;
michael@0 1173 }
michael@0 1174
michael@0 1175 NS_METHOD nsCocoaWindow::SetSizeMode(int32_t aMode)
michael@0 1176 {
michael@0 1177 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1178
michael@0 1179 if (!mWindow)
michael@0 1180 return NS_OK;
michael@0 1181
michael@0 1182 // mSizeMode will be updated in DispatchSizeModeEvent, which will be called
michael@0 1183 // from a delegate method that handles the state change during one of the
michael@0 1184 // calls below.
michael@0 1185 nsSizeMode previousMode = mSizeMode;
michael@0 1186
michael@0 1187 if (aMode == nsSizeMode_Normal) {
michael@0 1188 if ([mWindow isMiniaturized])
michael@0 1189 [mWindow deminiaturize:nil];
michael@0 1190 else if (previousMode == nsSizeMode_Maximized && [mWindow isZoomed])
michael@0 1191 [mWindow zoom:nil];
michael@0 1192 }
michael@0 1193 else if (aMode == nsSizeMode_Minimized) {
michael@0 1194 if (![mWindow isMiniaturized])
michael@0 1195 [mWindow miniaturize:nil];
michael@0 1196 }
michael@0 1197 else if (aMode == nsSizeMode_Maximized) {
michael@0 1198 if ([mWindow isMiniaturized])
michael@0 1199 [mWindow deminiaturize:nil];
michael@0 1200 if (![mWindow isZoomed])
michael@0 1201 [mWindow zoom:nil];
michael@0 1202 }
michael@0 1203 else if (aMode == nsSizeMode_Fullscreen) {
michael@0 1204 if (!mFullScreen)
michael@0 1205 MakeFullScreen(true);
michael@0 1206 }
michael@0 1207
michael@0 1208 return NS_OK;
michael@0 1209
michael@0 1210 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1211 }
michael@0 1212
michael@0 1213 // This has to preserve the window's frame bounds.
michael@0 1214 // This method requires (as does the Windows impl.) that you call Resize shortly
michael@0 1215 // after calling HideWindowChrome. See bug 498835 for fixing this.
michael@0 1216 NS_IMETHODIMP nsCocoaWindow::HideWindowChrome(bool aShouldHide)
michael@0 1217 {
michael@0 1218 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1219
michael@0 1220 if (!mWindow || !mWindowMadeHere ||
michael@0 1221 (mWindowType != eWindowType_toplevel && mWindowType != eWindowType_dialog))
michael@0 1222 return NS_ERROR_FAILURE;
michael@0 1223
michael@0 1224 BOOL isVisible = [mWindow isVisible];
michael@0 1225
michael@0 1226 // Remove child windows.
michael@0 1227 NSArray* childWindows = [mWindow childWindows];
michael@0 1228 NSEnumerator* enumerator = [childWindows objectEnumerator];
michael@0 1229 NSWindow* child = nil;
michael@0 1230 while ((child = [enumerator nextObject])) {
michael@0 1231 [mWindow removeChildWindow:child];
michael@0 1232 }
michael@0 1233
michael@0 1234 // Remove the content view.
michael@0 1235 NSView* contentView = [mWindow contentView];
michael@0 1236 [contentView retain];
michael@0 1237 [contentView removeFromSuperviewWithoutNeedingDisplay];
michael@0 1238
michael@0 1239 // Save state (like window title).
michael@0 1240 NSMutableDictionary* state = [mWindow exportState];
michael@0 1241
michael@0 1242 // Recreate the window with the right border style.
michael@0 1243 NSRect frameRect = [mWindow frame];
michael@0 1244 DestroyNativeWindow();
michael@0 1245 nsresult rv = CreateNativeWindow(frameRect, aShouldHide ? eBorderStyle_none : mBorderStyle, true);
michael@0 1246 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1247
michael@0 1248 // Re-import state.
michael@0 1249 [mWindow importState:state];
michael@0 1250
michael@0 1251 // Reparent the content view.
michael@0 1252 [mWindow setContentView:contentView];
michael@0 1253 [contentView release];
michael@0 1254
michael@0 1255 // Reparent child windows.
michael@0 1256 enumerator = [childWindows objectEnumerator];
michael@0 1257 while ((child = [enumerator nextObject])) {
michael@0 1258 [mWindow addChildWindow:child ordered:NSWindowAbove];
michael@0 1259 }
michael@0 1260
michael@0 1261 // Show the new window.
michael@0 1262 if (isVisible) {
michael@0 1263 rv = Show(true);
michael@0 1264 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1265 }
michael@0 1266
michael@0 1267 return NS_OK;
michael@0 1268
michael@0 1269 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1270 }
michael@0 1271
michael@0 1272 void nsCocoaWindow::EnteredFullScreen(bool aFullScreen)
michael@0 1273 {
michael@0 1274 mInFullScreenTransition = false;
michael@0 1275 mFullScreen = aFullScreen;
michael@0 1276 DispatchSizeModeEvent();
michael@0 1277 }
michael@0 1278
michael@0 1279 NS_METHOD nsCocoaWindow::MakeFullScreen(bool aFullScreen)
michael@0 1280 {
michael@0 1281 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1282
michael@0 1283 if (!mWindow) {
michael@0 1284 return NS_OK;
michael@0 1285 }
michael@0 1286
michael@0 1287 // We will call into MakeFullScreen redundantly when entering/exiting
michael@0 1288 // fullscreen mode via OS X controls. When that happens we should just handle
michael@0 1289 // it gracefully - no need to ASSERT.
michael@0 1290 if (mFullScreen == aFullScreen) {
michael@0 1291 return NS_OK;
michael@0 1292 }
michael@0 1293
michael@0 1294 // If we're using native fullscreen mode and our native window is invisible,
michael@0 1295 // our attempt to go into fullscreen mode will fail with an assertion in
michael@0 1296 // system code, without [WindowDelegate windowDidFailToEnterFullScreen:]
michael@0 1297 // ever getting called. To pre-empt this we bail here. See bug 752294.
michael@0 1298 if (mUsesNativeFullScreen && aFullScreen && ![mWindow isVisible]) {
michael@0 1299 EnteredFullScreen(false);
michael@0 1300 return NS_OK;
michael@0 1301 }
michael@0 1302
michael@0 1303 mInFullScreenTransition = true;
michael@0 1304
michael@0 1305 if (mUsesNativeFullScreen) {
michael@0 1306 // Calling toggleFullScreen will result in windowDid(FailTo)?(Enter|Exit)FullScreen
michael@0 1307 // to be called from the OS. We will call EnteredFullScreen from those methods,
michael@0 1308 // where mFullScreen will be set and a sizemode event will be dispatched.
michael@0 1309 [mWindow toggleFullScreen:nil];
michael@0 1310 } else {
michael@0 1311 NSDisableScreenUpdates();
michael@0 1312 // The order here matters. When we exit full screen mode, we need to show the
michael@0 1313 // Dock first, otherwise the newly-created window won't have its minimize
michael@0 1314 // button enabled. See bug 526282.
michael@0 1315 nsCocoaUtils::HideOSChromeOnScreen(aFullScreen, [mWindow screen]);
michael@0 1316 nsresult rv = nsBaseWidget::MakeFullScreen(aFullScreen);
michael@0 1317 NSEnableScreenUpdates();
michael@0 1318 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1319
michael@0 1320 EnteredFullScreen(aFullScreen);
michael@0 1321 }
michael@0 1322
michael@0 1323 return NS_OK;
michael@0 1324
michael@0 1325 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1326 }
michael@0 1327
michael@0 1328 // Coordinates are global display pixels
michael@0 1329 nsresult nsCocoaWindow::DoResize(double aX, double aY,
michael@0 1330 double aWidth, double aHeight,
michael@0 1331 bool aRepaint,
michael@0 1332 bool aConstrainToCurrentScreen)
michael@0 1333 {
michael@0 1334 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1335
michael@0 1336 if (!mWindow || mInResize) {
michael@0 1337 return NS_OK;
michael@0 1338 }
michael@0 1339
michael@0 1340 AutoRestore<bool> reentrantResizeGuard(mInResize);
michael@0 1341 mInResize = true;
michael@0 1342
michael@0 1343 // ConstrainSize operates in device pixels, so we need to convert using
michael@0 1344 // the backing scale factor here
michael@0 1345 CGFloat scale = BackingScaleFactor();
michael@0 1346 int32_t width = NSToIntRound(aWidth * scale);
michael@0 1347 int32_t height = NSToIntRound(aHeight * scale);
michael@0 1348 ConstrainSize(&width, &height);
michael@0 1349
michael@0 1350 nsIntRect newBounds(NSToIntRound(aX), NSToIntRound(aY),
michael@0 1351 NSToIntRound(width / scale),
michael@0 1352 NSToIntRound(height / scale));
michael@0 1353
michael@0 1354 // constrain to the screen that contains the largest area of the new rect
michael@0 1355 FitRectToVisibleAreaForScreen(newBounds,
michael@0 1356 aConstrainToCurrentScreen ?
michael@0 1357 [mWindow screen] : nullptr,
michael@0 1358 mUsesNativeFullScreen);
michael@0 1359
michael@0 1360 // convert requested bounds into Cocoa coordinate system
michael@0 1361 NSRect newFrame = nsCocoaUtils::GeckoRectToCocoaRect(newBounds);
michael@0 1362
michael@0 1363 NSRect frame = [mWindow frame];
michael@0 1364 BOOL isMoving = newFrame.origin.x != frame.origin.x ||
michael@0 1365 newFrame.origin.y != frame.origin.y;
michael@0 1366 BOOL isResizing = newFrame.size.width != frame.size.width ||
michael@0 1367 newFrame.size.height != frame.size.height;
michael@0 1368
michael@0 1369 if (!isMoving && !isResizing) {
michael@0 1370 return NS_OK;
michael@0 1371 }
michael@0 1372
michael@0 1373 // We ignore aRepaint -- we have to call display:YES, otherwise the
michael@0 1374 // title bar doesn't immediately get repainted and is displayed in
michael@0 1375 // the wrong place, leading to a visual jump.
michael@0 1376 [mWindow setFrame:newFrame display:YES];
michael@0 1377
michael@0 1378 return NS_OK;
michael@0 1379
michael@0 1380 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1381 }
michael@0 1382
michael@0 1383 // Coordinates are global display pixels
michael@0 1384 NS_IMETHODIMP nsCocoaWindow::Resize(double aX, double aY,
michael@0 1385 double aWidth, double aHeight,
michael@0 1386 bool aRepaint)
michael@0 1387 {
michael@0 1388 return DoResize(aX, aY, aWidth, aHeight, aRepaint, false);
michael@0 1389 }
michael@0 1390
michael@0 1391 // Coordinates are global display pixels
michael@0 1392 NS_IMETHODIMP nsCocoaWindow::Resize(double aWidth, double aHeight, bool aRepaint)
michael@0 1393 {
michael@0 1394 double invScale = 1.0 / GetDefaultScale().scale;
michael@0 1395 return DoResize(mBounds.x * invScale, mBounds.y * invScale,
michael@0 1396 aWidth, aHeight, aRepaint, true);
michael@0 1397 }
michael@0 1398
michael@0 1399 NS_IMETHODIMP nsCocoaWindow::GetClientBounds(nsIntRect &aRect)
michael@0 1400 {
michael@0 1401 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1402
michael@0 1403 CGFloat scaleFactor = BackingScaleFactor();
michael@0 1404 if (!mWindow) {
michael@0 1405 aRect = nsCocoaUtils::CocoaRectToGeckoRectDevPix(NSZeroRect, scaleFactor);
michael@0 1406 return NS_OK;
michael@0 1407 }
michael@0 1408
michael@0 1409 NSRect r;
michael@0 1410 if ([mWindow isKindOfClass:[ToolbarWindow class]] &&
michael@0 1411 [(ToolbarWindow*)mWindow drawsContentsIntoWindowFrame]) {
michael@0 1412 r = [mWindow frame];
michael@0 1413 } else {
michael@0 1414 r = [mWindow contentRectForFrameRect:[mWindow frame]];
michael@0 1415 }
michael@0 1416
michael@0 1417 aRect = nsCocoaUtils::CocoaRectToGeckoRectDevPix(r, scaleFactor);
michael@0 1418
michael@0 1419 return NS_OK;
michael@0 1420
michael@0 1421 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1422 }
michael@0 1423
michael@0 1424 void
michael@0 1425 nsCocoaWindow::UpdateBounds()
michael@0 1426 {
michael@0 1427 NSRect frame = NSZeroRect;
michael@0 1428 if (mWindow) {
michael@0 1429 frame = [mWindow frame];
michael@0 1430 }
michael@0 1431 mBounds = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
michael@0 1432 }
michael@0 1433
michael@0 1434 NS_IMETHODIMP nsCocoaWindow::GetScreenBounds(nsIntRect &aRect)
michael@0 1435 {
michael@0 1436 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1437
michael@0 1438 #ifdef DEBUG
michael@0 1439 nsIntRect r = nsCocoaUtils::CocoaRectToGeckoRectDevPix([mWindow frame], BackingScaleFactor());
michael@0 1440 NS_ASSERTION(mWindow && mBounds == r, "mBounds out of sync!");
michael@0 1441 #endif
michael@0 1442
michael@0 1443 aRect = mBounds;
michael@0 1444 return NS_OK;
michael@0 1445
michael@0 1446 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1447 }
michael@0 1448
michael@0 1449 double
michael@0 1450 nsCocoaWindow::GetDefaultScaleInternal()
michael@0 1451 {
michael@0 1452 return BackingScaleFactor();
michael@0 1453 }
michael@0 1454
michael@0 1455 static CGFloat
michael@0 1456 GetBackingScaleFactor(NSWindow* aWindow)
michael@0 1457 {
michael@0 1458 NSRect frame = [aWindow frame];
michael@0 1459 if (frame.size.width > 0 && frame.size.height > 0) {
michael@0 1460 return nsCocoaUtils::GetBackingScaleFactor(aWindow);
michael@0 1461 }
michael@0 1462
michael@0 1463 // For windows with zero width or height, the backingScaleFactor method
michael@0 1464 // is broken - it will always return 2 on a retina macbook, even when
michael@0 1465 // the window position implies it's on a non-hidpi external display
michael@0 1466 // (to the extent that a zero-area window can be said to be "on" a
michael@0 1467 // display at all!)
michael@0 1468 // And to make matters worse, Cocoa even fires a
michael@0 1469 // windowDidChangeBackingProperties notification with the
michael@0 1470 // NSBackingPropertyOldScaleFactorKey key when a window on an
michael@0 1471 // external display is resized to/from zero height, even though it hasn't
michael@0 1472 // really changed screens.
michael@0 1473
michael@0 1474 // This causes us to handle popup window sizing incorrectly when the
michael@0 1475 // popup is resized to zero height (bug 820327) - nsXULPopupManager
michael@0 1476 // becomes (incorrectly) convinced the popup has been explicitly forced
michael@0 1477 // to a non-default size and needs to have size attributes attached.
michael@0 1478
michael@0 1479 // Workaround: instead of asking the window, we'll find the screen it is on
michael@0 1480 // and ask that for *its* backing scale factor.
michael@0 1481
michael@0 1482 // (See bug 853252 and additional comments in windowDidChangeScreen: below
michael@0 1483 // for further complications this causes.)
michael@0 1484
michael@0 1485 // First, expand the rect so that it actually has a measurable area,
michael@0 1486 // for FindTargetScreenForRect to use.
michael@0 1487 if (frame.size.width == 0) {
michael@0 1488 frame.size.width = 1;
michael@0 1489 }
michael@0 1490 if (frame.size.height == 0) {
michael@0 1491 frame.size.height = 1;
michael@0 1492 }
michael@0 1493
michael@0 1494 // Then identify the screen it belongs to, and return its scale factor.
michael@0 1495 NSScreen *screen =
michael@0 1496 FindTargetScreenForRect(nsCocoaUtils::CocoaRectToGeckoRect(frame));
michael@0 1497 return nsCocoaUtils::GetBackingScaleFactor(screen);
michael@0 1498 }
michael@0 1499
michael@0 1500 CGFloat
michael@0 1501 nsCocoaWindow::BackingScaleFactor()
michael@0 1502 {
michael@0 1503 if (mBackingScaleFactor > 0.0) {
michael@0 1504 return mBackingScaleFactor;
michael@0 1505 }
michael@0 1506 if (!mWindow) {
michael@0 1507 return 1.0;
michael@0 1508 }
michael@0 1509 mBackingScaleFactor = GetBackingScaleFactor(mWindow);
michael@0 1510 return mBackingScaleFactor;
michael@0 1511 }
michael@0 1512
michael@0 1513 void
michael@0 1514 nsCocoaWindow::BackingScaleFactorChanged()
michael@0 1515 {
michael@0 1516 CGFloat newScale = GetBackingScaleFactor(mWindow);
michael@0 1517
michael@0 1518 // ignore notification if it hasn't really changed (or maybe we have
michael@0 1519 // disabled HiDPI mode via prefs)
michael@0 1520 if (mBackingScaleFactor == newScale) {
michael@0 1521 return;
michael@0 1522 }
michael@0 1523
michael@0 1524 if (mBackingScaleFactor > 0.0) {
michael@0 1525 // convert size constraints to the new device pixel coordinate space
michael@0 1526 double scaleFactor = newScale / mBackingScaleFactor;
michael@0 1527 mSizeConstraints.mMinSize.width =
michael@0 1528 NSToIntRound(mSizeConstraints.mMinSize.width * scaleFactor);
michael@0 1529 mSizeConstraints.mMinSize.height =
michael@0 1530 NSToIntRound(mSizeConstraints.mMinSize.height * scaleFactor);
michael@0 1531 if (mSizeConstraints.mMaxSize.width < NS_MAXSIZE) {
michael@0 1532 mSizeConstraints.mMaxSize.width =
michael@0 1533 std::min(NS_MAXSIZE,
michael@0 1534 NSToIntRound(mSizeConstraints.mMaxSize.width * scaleFactor));
michael@0 1535 }
michael@0 1536 if (mSizeConstraints.mMaxSize.height < NS_MAXSIZE) {
michael@0 1537 mSizeConstraints.mMaxSize.height =
michael@0 1538 std::min(NS_MAXSIZE,
michael@0 1539 NSToIntRound(mSizeConstraints.mMaxSize.height * scaleFactor));
michael@0 1540 }
michael@0 1541 }
michael@0 1542
michael@0 1543 mBackingScaleFactor = newScale;
michael@0 1544
michael@0 1545 if (!mWidgetListener || mWidgetListener->GetXULWindow()) {
michael@0 1546 return;
michael@0 1547 }
michael@0 1548
michael@0 1549 nsIPresShell* presShell = mWidgetListener->GetPresShell();
michael@0 1550 if (presShell) {
michael@0 1551 presShell->BackingScaleFactorChanged();
michael@0 1552 }
michael@0 1553 }
michael@0 1554
michael@0 1555 int32_t
michael@0 1556 nsCocoaWindow::RoundsWidgetCoordinatesTo()
michael@0 1557 {
michael@0 1558 if (BackingScaleFactor() == 2.0) {
michael@0 1559 return 2;
michael@0 1560 }
michael@0 1561 return 1;
michael@0 1562 }
michael@0 1563
michael@0 1564 NS_IMETHODIMP nsCocoaWindow::SetCursor(nsCursor aCursor)
michael@0 1565 {
michael@0 1566 if (mPopupContentView)
michael@0 1567 return mPopupContentView->SetCursor(aCursor);
michael@0 1568
michael@0 1569 return NS_OK;
michael@0 1570 }
michael@0 1571
michael@0 1572 NS_IMETHODIMP nsCocoaWindow::SetCursor(imgIContainer* aCursor,
michael@0 1573 uint32_t aHotspotX, uint32_t aHotspotY)
michael@0 1574 {
michael@0 1575 if (mPopupContentView)
michael@0 1576 return mPopupContentView->SetCursor(aCursor, aHotspotX, aHotspotY);
michael@0 1577
michael@0 1578 return NS_OK;
michael@0 1579 }
michael@0 1580
michael@0 1581 NS_IMETHODIMP nsCocoaWindow::SetTitle(const nsAString& aTitle)
michael@0 1582 {
michael@0 1583 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1584
michael@0 1585 if (!mWindow)
michael@0 1586 return NS_OK;
michael@0 1587
michael@0 1588 const nsString& strTitle = PromiseFlatString(aTitle);
michael@0 1589 NSString* title = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(strTitle.get())
michael@0 1590 length:strTitle.Length()];
michael@0 1591
michael@0 1592 if ([mWindow drawsContentsIntoWindowFrame] && ![mWindow wantsTitleDrawn]) {
michael@0 1593 // Don't cause invalidations.
michael@0 1594 [mWindow disableSetNeedsDisplay];
michael@0 1595 [mWindow setTitle:title];
michael@0 1596 [mWindow enableSetNeedsDisplay];
michael@0 1597 } else {
michael@0 1598 [mWindow setTitle:title];
michael@0 1599 }
michael@0 1600
michael@0 1601 return NS_OK;
michael@0 1602
michael@0 1603 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1604 }
michael@0 1605
michael@0 1606 NS_IMETHODIMP nsCocoaWindow::Invalidate(const nsIntRect & aRect)
michael@0 1607 {
michael@0 1608 if (mPopupContentView) {
michael@0 1609 return mPopupContentView->Invalidate(aRect);
michael@0 1610 }
michael@0 1611
michael@0 1612 return NS_OK;
michael@0 1613 }
michael@0 1614
michael@0 1615 // Pass notification of some drag event to Gecko
michael@0 1616 //
michael@0 1617 // The drag manager has let us know that something related to a drag has
michael@0 1618 // occurred in this window. It could be any number of things, ranging from
michael@0 1619 // a drop, to a drag enter/leave, or a drag over event. The actual event
michael@0 1620 // is passed in |aMessage| and is passed along to our event hanlder so Gecko
michael@0 1621 // knows about it.
michael@0 1622 bool nsCocoaWindow::DragEvent(unsigned int aMessage, Point aMouseGlobal, UInt16 aKeyModifiers)
michael@0 1623 {
michael@0 1624 return false;
michael@0 1625 }
michael@0 1626
michael@0 1627 NS_IMETHODIMP nsCocoaWindow::SendSetZLevelEvent()
michael@0 1628 {
michael@0 1629 nsWindowZ placement = nsWindowZTop;
michael@0 1630 nsIWidget* actualBelow;
michael@0 1631 if (mWidgetListener)
michael@0 1632 mWidgetListener->ZLevelChanged(true, &placement, nullptr, &actualBelow);
michael@0 1633 return NS_OK;
michael@0 1634 }
michael@0 1635
michael@0 1636 NS_IMETHODIMP nsCocoaWindow::GetChildSheet(bool aShown, nsCocoaWindow** _retval)
michael@0 1637 {
michael@0 1638 nsIWidget* child = GetFirstChild();
michael@0 1639
michael@0 1640 while (child) {
michael@0 1641 if (child->WindowType() == eWindowType_sheet) {
michael@0 1642 // if it's a sheet, it must be an nsCocoaWindow
michael@0 1643 nsCocoaWindow* cocoaWindow = static_cast<nsCocoaWindow*>(child);
michael@0 1644 if (cocoaWindow->mWindow &&
michael@0 1645 ((aShown && [cocoaWindow->mWindow isVisible]) ||
michael@0 1646 (!aShown && cocoaWindow->mSheetNeedsShow))) {
michael@0 1647 *_retval = cocoaWindow;
michael@0 1648 return NS_OK;
michael@0 1649 }
michael@0 1650 }
michael@0 1651 child = child->GetNextSibling();
michael@0 1652 }
michael@0 1653
michael@0 1654 *_retval = nullptr;
michael@0 1655
michael@0 1656 return NS_OK;
michael@0 1657 }
michael@0 1658
michael@0 1659 NS_IMETHODIMP nsCocoaWindow::GetRealParent(nsIWidget** parent)
michael@0 1660 {
michael@0 1661 *parent = mParent;
michael@0 1662 return NS_OK;
michael@0 1663 }
michael@0 1664
michael@0 1665 NS_IMETHODIMP nsCocoaWindow::GetIsSheet(bool* isSheet)
michael@0 1666 {
michael@0 1667 mWindowType == eWindowType_sheet ? *isSheet = true : *isSheet = false;
michael@0 1668 return NS_OK;
michael@0 1669 }
michael@0 1670
michael@0 1671 NS_IMETHODIMP nsCocoaWindow::GetSheetWindowParent(NSWindow** sheetWindowParent)
michael@0 1672 {
michael@0 1673 *sheetWindowParent = mSheetWindowParent;
michael@0 1674 return NS_OK;
michael@0 1675 }
michael@0 1676
michael@0 1677 // Invokes callback and ProcessEvent methods on Event Listener object
michael@0 1678 NS_IMETHODIMP
michael@0 1679 nsCocoaWindow::DispatchEvent(WidgetGUIEvent* event, nsEventStatus& aStatus)
michael@0 1680 {
michael@0 1681 aStatus = nsEventStatus_eIgnore;
michael@0 1682
michael@0 1683 nsIWidget* aWidget = event->widget;
michael@0 1684 NS_IF_ADDREF(aWidget);
michael@0 1685
michael@0 1686 if (mWidgetListener)
michael@0 1687 aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
michael@0 1688
michael@0 1689 NS_IF_RELEASE(aWidget);
michael@0 1690
michael@0 1691 return NS_OK;
michael@0 1692 }
michael@0 1693
michael@0 1694 // aFullScreen should be the window's mFullScreen. We don't have access to that
michael@0 1695 // from here, so we need to pass it in. mFullScreen should be the canonical
michael@0 1696 // indicator that a window is currently full screen and it makes sense to keep
michael@0 1697 // all sizemode logic here.
michael@0 1698 static nsSizeMode
michael@0 1699 GetWindowSizeMode(NSWindow* aWindow, bool aFullScreen) {
michael@0 1700 if (aFullScreen)
michael@0 1701 return nsSizeMode_Fullscreen;
michael@0 1702 if ([aWindow isMiniaturized])
michael@0 1703 return nsSizeMode_Minimized;
michael@0 1704 if (([aWindow styleMask] & NSResizableWindowMask) && [aWindow isZoomed])
michael@0 1705 return nsSizeMode_Maximized;
michael@0 1706 return nsSizeMode_Normal;
michael@0 1707 }
michael@0 1708
michael@0 1709 void
michael@0 1710 nsCocoaWindow::ReportMoveEvent()
michael@0 1711 {
michael@0 1712 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 1713
michael@0 1714 // Prevent recursion, which can become infinite (see bug 708278). This
michael@0 1715 // can happen when the call to [NSWindow setFrameTopLeftPoint:] in
michael@0 1716 // nsCocoaWindow::Move() triggers an immediate NSWindowDidMove notification
michael@0 1717 // (and a call to [WindowDelegate windowDidMove:]).
michael@0 1718 if (mInReportMoveEvent) {
michael@0 1719 return;
michael@0 1720 }
michael@0 1721 mInReportMoveEvent = true;
michael@0 1722
michael@0 1723 UpdateBounds();
michael@0 1724
michael@0 1725 // Dispatch the move event to Gecko
michael@0 1726 NotifyWindowMoved(mBounds.x, mBounds.y);
michael@0 1727
michael@0 1728 mInReportMoveEvent = false;
michael@0 1729
michael@0 1730 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 1731 }
michael@0 1732
michael@0 1733 void
michael@0 1734 nsCocoaWindow::DispatchSizeModeEvent()
michael@0 1735 {
michael@0 1736 if (!mWindow) {
michael@0 1737 return;
michael@0 1738 }
michael@0 1739
michael@0 1740 nsSizeMode newMode = GetWindowSizeMode(mWindow, mFullScreen);
michael@0 1741
michael@0 1742 // Don't dispatch a sizemode event if:
michael@0 1743 // 1. the window is transitioning to fullscreen
michael@0 1744 // 2. the new sizemode is the same as the current sizemode
michael@0 1745 if (mInFullScreenTransition || mSizeMode == newMode) {
michael@0 1746 return;
michael@0 1747 }
michael@0 1748
michael@0 1749 mSizeMode = newMode;
michael@0 1750 if (mWidgetListener) {
michael@0 1751 mWidgetListener->SizeModeChanged(newMode);
michael@0 1752 }
michael@0 1753 }
michael@0 1754
michael@0 1755 void
michael@0 1756 nsCocoaWindow::ReportSizeEvent()
michael@0 1757 {
michael@0 1758 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 1759
michael@0 1760 UpdateBounds();
michael@0 1761
michael@0 1762 if (mWidgetListener) {
michael@0 1763 nsIntRect innerBounds;
michael@0 1764 GetClientBounds(innerBounds);
michael@0 1765 mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height);
michael@0 1766 }
michael@0 1767
michael@0 1768 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 1769 }
michael@0 1770
michael@0 1771 void nsCocoaWindow::SetMenuBar(nsMenuBarX *aMenuBar)
michael@0 1772 {
michael@0 1773 if (mMenuBar)
michael@0 1774 mMenuBar->SetParent(nullptr);
michael@0 1775 if (!mWindow) {
michael@0 1776 mMenuBar = nullptr;
michael@0 1777 return;
michael@0 1778 }
michael@0 1779 mMenuBar = aMenuBar;
michael@0 1780
michael@0 1781 // Only paint for active windows, or paint the hidden window menu bar if no
michael@0 1782 // other menu bar has been painted yet so that some reasonable menu bar is
michael@0 1783 // displayed when the app starts up.
michael@0 1784 id windowDelegate = [mWindow delegate];
michael@0 1785 if (mMenuBar &&
michael@0 1786 ((!gSomeMenuBarPainted && nsMenuUtilsX::GetHiddenWindowMenuBar() == mMenuBar) ||
michael@0 1787 (windowDelegate && [windowDelegate toplevelActiveState])))
michael@0 1788 mMenuBar->Paint();
michael@0 1789 }
michael@0 1790
michael@0 1791 NS_IMETHODIMP nsCocoaWindow::SetFocus(bool aState)
michael@0 1792 {
michael@0 1793 if (!mWindow)
michael@0 1794 return NS_OK;
michael@0 1795
michael@0 1796 if (mPopupContentView) {
michael@0 1797 mPopupContentView->SetFocus(aState);
michael@0 1798 }
michael@0 1799 else if (aState && ([mWindow isVisible] || [mWindow isMiniaturized])) {
michael@0 1800 [mWindow makeKeyAndOrderFront:nil];
michael@0 1801 SendSetZLevelEvent();
michael@0 1802 }
michael@0 1803
michael@0 1804 return NS_OK;
michael@0 1805 }
michael@0 1806
michael@0 1807 nsIntPoint nsCocoaWindow::WidgetToScreenOffset()
michael@0 1808 {
michael@0 1809 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 1810
michael@0 1811 NSRect rect = NSZeroRect;
michael@0 1812 nsIntRect r;
michael@0 1813 if (mWindow) {
michael@0 1814 rect = [mWindow contentRectForFrameRect:[mWindow frame]];
michael@0 1815 }
michael@0 1816 r = nsCocoaUtils::CocoaRectToGeckoRectDevPix(rect, BackingScaleFactor());
michael@0 1817
michael@0 1818 return r.TopLeft();
michael@0 1819
michael@0 1820 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsIntPoint(0,0));
michael@0 1821 }
michael@0 1822
michael@0 1823 nsIntPoint nsCocoaWindow::GetClientOffset()
michael@0 1824 {
michael@0 1825 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 1826
michael@0 1827 nsIntRect clientRect;
michael@0 1828 GetClientBounds(clientRect);
michael@0 1829
michael@0 1830 return clientRect.TopLeft() - mBounds.TopLeft();
michael@0 1831
michael@0 1832 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsIntPoint(0, 0));
michael@0 1833 }
michael@0 1834
michael@0 1835 nsIntSize nsCocoaWindow::ClientToWindowSize(const nsIntSize& aClientSize)
michael@0 1836 {
michael@0 1837 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 1838
michael@0 1839 if (!mWindow)
michael@0 1840 return nsIntSize(0, 0);
michael@0 1841
michael@0 1842 CGFloat backingScale = BackingScaleFactor();
michael@0 1843 nsIntRect r(0, 0, aClientSize.width, aClientSize.height);
michael@0 1844 NSRect rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, backingScale);
michael@0 1845
michael@0 1846 NSRect inflatedRect = [mWindow frameRectForContentRect:rect];
michael@0 1847 return nsCocoaUtils::CocoaRectToGeckoRectDevPix(inflatedRect, backingScale).Size();
michael@0 1848
michael@0 1849 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsIntSize(0,0));
michael@0 1850 }
michael@0 1851
michael@0 1852 nsMenuBarX* nsCocoaWindow::GetMenuBar()
michael@0 1853 {
michael@0 1854 return mMenuBar;
michael@0 1855 }
michael@0 1856
michael@0 1857 NS_IMETHODIMP nsCocoaWindow::CaptureRollupEvents(nsIRollupListener* aListener, bool aDoCapture)
michael@0 1858 {
michael@0 1859 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1860
michael@0 1861 gRollupListener = nullptr;
michael@0 1862
michael@0 1863 if (aDoCapture) {
michael@0 1864 if (![NSApp isActive]) {
michael@0 1865 // We need to capture mouse event if we aren't
michael@0 1866 // the active application. We only set this up when needed
michael@0 1867 // because they cause spurious mouse event after crash
michael@0 1868 // and gdb sessions. See bug 699538.
michael@0 1869 nsToolkit::GetToolkit()->RegisterForAllProcessMouseEvents();
michael@0 1870 }
michael@0 1871 gRollupListener = aListener;
michael@0 1872
michael@0 1873 // Sometimes more than one popup window can be visible at the same time
michael@0 1874 // (e.g. nested non-native context menus, or the test case (attachment
michael@0 1875 // 276885) for bmo bug 392389, which displays a non-native combo-box in a
michael@0 1876 // non-native popup window). In these cases the "active" popup window should
michael@0 1877 // be the topmost -- the (nested) context menu the mouse is currently over,
michael@0 1878 // or the combo-box's drop-down list (when it's displayed). But (among
michael@0 1879 // windows that have the same "level") OS X makes topmost the window that
michael@0 1880 // last received a mouse-down event, which may be incorrect (in the combo-
michael@0 1881 // box case, it makes topmost the window containing the combo-box). So
michael@0 1882 // here we fiddle with a non-native popup window's level to make sure the
michael@0 1883 // "active" one is always above any other non-native popup windows that
michael@0 1884 // may be visible.
michael@0 1885 if (mWindow && (mWindowType == eWindowType_popup))
michael@0 1886 SetPopupWindowLevel();
michael@0 1887 } else {
michael@0 1888 nsToolkit::GetToolkit()->UnregisterAllProcessMouseEventHandlers();
michael@0 1889
michael@0 1890 // XXXndeakin this doesn't make sense.
michael@0 1891 // Why is the new window assumed to be a modal panel?
michael@0 1892 if (mWindow && (mWindowType == eWindowType_popup))
michael@0 1893 [mWindow setLevel:NSModalPanelWindowLevel];
michael@0 1894 }
michael@0 1895
michael@0 1896 return NS_OK;
michael@0 1897
michael@0 1898 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1899 }
michael@0 1900
michael@0 1901 NS_IMETHODIMP nsCocoaWindow::GetAttention(int32_t aCycleCount)
michael@0 1902 {
michael@0 1903 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1904
michael@0 1905 [NSApp requestUserAttention:NSInformationalRequest];
michael@0 1906 return NS_OK;
michael@0 1907
michael@0 1908 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1909 }
michael@0 1910
michael@0 1911 bool
michael@0 1912 nsCocoaWindow::HasPendingInputEvent()
michael@0 1913 {
michael@0 1914 return nsChildView::DoHasPendingInputEvent();
michael@0 1915 }
michael@0 1916
michael@0 1917 NS_IMETHODIMP nsCocoaWindow::SetWindowShadowStyle(int32_t aStyle)
michael@0 1918 {
michael@0 1919 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1920
michael@0 1921 if (!mWindow)
michael@0 1922 return NS_OK;
michael@0 1923
michael@0 1924 mShadowStyle = aStyle;
michael@0 1925 [mWindow setHasShadow:(aStyle != NS_STYLE_WINDOW_SHADOW_NONE)];
michael@0 1926 AdjustWindowShadow();
michael@0 1927 SetWindowBackgroundBlur();
michael@0 1928
michael@0 1929 return NS_OK;
michael@0 1930
michael@0 1931 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1932 }
michael@0 1933
michael@0 1934 void nsCocoaWindow::SetShowsToolbarButton(bool aShow)
michael@0 1935 {
michael@0 1936 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 1937
michael@0 1938 if (mWindow)
michael@0 1939 [mWindow setShowsToolbarButton:aShow];
michael@0 1940
michael@0 1941 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 1942 }
michael@0 1943
michael@0 1944 void nsCocoaWindow::SetShowsFullScreenButton(bool aShow)
michael@0 1945 {
michael@0 1946 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 1947
michael@0 1948 if (!mWindow || ![mWindow respondsToSelector:@selector(toggleFullScreen:)] ||
michael@0 1949 mUsesNativeFullScreen == aShow) {
michael@0 1950 return;
michael@0 1951 }
michael@0 1952
michael@0 1953 // If the window is currently in fullscreen mode, then we're going to
michael@0 1954 // transition out first, then set the collection behavior & toggle
michael@0 1955 // mUsesNativeFullScreen, then transtion back into fullscreen mode. This
michael@0 1956 // prevents us from getting into a conflicting state with MakeFullScreen
michael@0 1957 // where mUsesNativeFullScreen would lead us down the wrong path.
michael@0 1958 bool wasFullScreen = mFullScreen;
michael@0 1959
michael@0 1960 if (wasFullScreen) {
michael@0 1961 MakeFullScreen(false);
michael@0 1962 }
michael@0 1963
michael@0 1964 NSWindowCollectionBehavior newBehavior = [mWindow collectionBehavior];
michael@0 1965 if (aShow) {
michael@0 1966 newBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
michael@0 1967 } else {
michael@0 1968 newBehavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
michael@0 1969 }
michael@0 1970 [mWindow setCollectionBehavior:newBehavior];
michael@0 1971 mUsesNativeFullScreen = aShow;
michael@0 1972
michael@0 1973 if (wasFullScreen) {
michael@0 1974 MakeFullScreen(true);
michael@0 1975 }
michael@0 1976
michael@0 1977 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 1978 }
michael@0 1979
michael@0 1980 void nsCocoaWindow::SetWindowAnimationType(nsIWidget::WindowAnimationType aType)
michael@0 1981 {
michael@0 1982 mAnimationType = aType;
michael@0 1983 }
michael@0 1984
michael@0 1985 void
michael@0 1986 nsCocoaWindow::SetDrawsTitle(bool aDrawTitle)
michael@0 1987 {
michael@0 1988 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 1989
michael@0 1990 [mWindow setWantsTitleDrawn:aDrawTitle];
michael@0 1991
michael@0 1992 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 1993 }
michael@0 1994
michael@0 1995 NS_IMETHODIMP nsCocoaWindow::SetNonClientMargins(nsIntMargin &margins)
michael@0 1996 {
michael@0 1997 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 1998
michael@0 1999 SetDrawsInTitlebar(margins.top == 0);
michael@0 2000
michael@0 2001 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 2002 }
michael@0 2003
michael@0 2004 NS_IMETHODIMP nsCocoaWindow::SetWindowTitlebarColor(nscolor aColor, bool aActive)
michael@0 2005 {
michael@0 2006 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 2007
michael@0 2008 if (!mWindow)
michael@0 2009 return NS_OK;
michael@0 2010
michael@0 2011 // If they pass a color with a complete transparent alpha component, use the
michael@0 2012 // native titlebar appearance.
michael@0 2013 if (NS_GET_A(aColor) == 0) {
michael@0 2014 [mWindow setTitlebarColor:nil forActiveWindow:(BOOL)aActive];
michael@0 2015 } else {
michael@0 2016 // Transform from sRGBA to monitor RGBA. This seems like it would make trying
michael@0 2017 // to match the system appearance lame, so probably we just shouldn't color
michael@0 2018 // correct chrome.
michael@0 2019 if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
michael@0 2020 qcms_transform *transform = gfxPlatform::GetCMSRGBATransform();
michael@0 2021 if (transform) {
michael@0 2022 uint8_t color[3];
michael@0 2023 color[0] = NS_GET_R(aColor);
michael@0 2024 color[1] = NS_GET_G(aColor);
michael@0 2025 color[2] = NS_GET_B(aColor);
michael@0 2026 qcms_transform_data(transform, color, color, 1);
michael@0 2027 aColor = NS_RGB(color[0], color[1], color[2]);
michael@0 2028 }
michael@0 2029 }
michael@0 2030
michael@0 2031 [mWindow setTitlebarColor:[NSColor colorWithDeviceRed:NS_GET_R(aColor)/255.0
michael@0 2032 green:NS_GET_G(aColor)/255.0
michael@0 2033 blue:NS_GET_B(aColor)/255.0
michael@0 2034 alpha:NS_GET_A(aColor)/255.0]
michael@0 2035 forActiveWindow:(BOOL)aActive];
michael@0 2036 }
michael@0 2037 return NS_OK;
michael@0 2038
michael@0 2039 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 2040 }
michael@0 2041
michael@0 2042 void nsCocoaWindow::SetDrawsInTitlebar(bool aState)
michael@0 2043 {
michael@0 2044 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 2045
michael@0 2046 if (mWindow)
michael@0 2047 [mWindow setDrawsContentsIntoWindowFrame:aState];
michael@0 2048
michael@0 2049 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 2050 }
michael@0 2051
michael@0 2052 NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseEvent(nsIntPoint aPoint,
michael@0 2053 uint32_t aNativeMessage,
michael@0 2054 uint32_t aModifierFlags)
michael@0 2055 {
michael@0 2056 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 2057
michael@0 2058 if (mPopupContentView)
michael@0 2059 return mPopupContentView->SynthesizeNativeMouseEvent(aPoint, aNativeMessage,
michael@0 2060 aModifierFlags);
michael@0 2061
michael@0 2062 return NS_OK;
michael@0 2063
michael@0 2064 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 2065 }
michael@0 2066
michael@0 2067 gfxASurface* nsCocoaWindow::GetThebesSurface()
michael@0 2068 {
michael@0 2069 if (mPopupContentView)
michael@0 2070 return mPopupContentView->GetThebesSurface();
michael@0 2071 return nullptr;
michael@0 2072 }
michael@0 2073
michael@0 2074 void nsCocoaWindow::SetPopupWindowLevel()
michael@0 2075 {
michael@0 2076 if (!mWindow)
michael@0 2077 return;
michael@0 2078
michael@0 2079 // Floating popups are at the floating level and hide when the window is
michael@0 2080 // deactivated.
michael@0 2081 if (mPopupLevel == ePopupLevelFloating) {
michael@0 2082 [mWindow setLevel:NSFloatingWindowLevel];
michael@0 2083 [mWindow setHidesOnDeactivate:YES];
michael@0 2084 }
michael@0 2085 else {
michael@0 2086 // Otherwise, this is a top-level or parent popup. Parent popups always
michael@0 2087 // appear just above their parent and essentially ignore the level.
michael@0 2088 [mWindow setLevel:NSPopUpMenuWindowLevel];
michael@0 2089 [mWindow setHidesOnDeactivate:NO];
michael@0 2090 }
michael@0 2091 }
michael@0 2092
michael@0 2093 NS_IMETHODIMP
michael@0 2094 nsCocoaWindow::NotifyIME(const IMENotification& aIMENotification)
michael@0 2095 {
michael@0 2096 switch (aIMENotification.mMessage) {
michael@0 2097 case NOTIFY_IME_OF_FOCUS:
michael@0 2098 if (mInputContext.IsPasswordEditor()) {
michael@0 2099 TextInputHandler::EnableSecureEventInput();
michael@0 2100 }
michael@0 2101 return NS_OK;
michael@0 2102 case NOTIFY_IME_OF_BLUR:
michael@0 2103 // When we're going to be deactive, we must disable the secure event input
michael@0 2104 // mode, see the Carbon Event Manager Reference.
michael@0 2105 TextInputHandler::EnsureSecureEventInputDisabled();
michael@0 2106 return NS_OK;
michael@0 2107 default:
michael@0 2108 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 2109 }
michael@0 2110 }
michael@0 2111
michael@0 2112 NS_IMETHODIMP_(void)
michael@0 2113 nsCocoaWindow::SetInputContext(const InputContext& aContext,
michael@0 2114 const InputContextAction& aAction)
michael@0 2115 {
michael@0 2116 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 2117
michael@0 2118 if (mWindow &&
michael@0 2119 [mWindow firstResponder] == mWindow &&
michael@0 2120 [mWindow isKeyWindow] &&
michael@0 2121 [[NSApplication sharedApplication] isActive]) {
michael@0 2122 if (aContext.IsPasswordEditor()) {
michael@0 2123 TextInputHandler::EnableSecureEventInput();
michael@0 2124 } else {
michael@0 2125 TextInputHandler::EnsureSecureEventInputDisabled();
michael@0 2126 }
michael@0 2127 }
michael@0 2128 mInputContext = aContext;
michael@0 2129
michael@0 2130 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 2131 }
michael@0 2132
michael@0 2133 NS_IMETHODIMP_(bool)
michael@0 2134 nsCocoaWindow::ExecuteNativeKeyBinding(NativeKeyBindingsType aType,
michael@0 2135 const WidgetKeyboardEvent& aEvent,
michael@0 2136 DoCommandCallback aCallback,
michael@0 2137 void* aCallbackData)
michael@0 2138 {
michael@0 2139 NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
michael@0 2140 return keyBindings->Execute(aEvent, aCallback, aCallbackData);
michael@0 2141 }
michael@0 2142
michael@0 2143
michael@0 2144 @implementation WindowDelegate
michael@0 2145
michael@0 2146 // We try to find a gecko menu bar to paint. If one does not exist, just paint
michael@0 2147 // the application menu by itself so that a window doesn't have some other
michael@0 2148 // window's menu bar.
michael@0 2149 + (void)paintMenubarForWindow:(NSWindow*)aWindow
michael@0 2150 {
michael@0 2151 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 2152
michael@0 2153 // make sure we only act on windows that have this kind of
michael@0 2154 // object as a delegate
michael@0 2155 id windowDelegate = [aWindow delegate];
michael@0 2156 if ([windowDelegate class] != [self class])
michael@0 2157 return;
michael@0 2158
michael@0 2159 nsCocoaWindow* geckoWidget = [windowDelegate geckoWidget];
michael@0 2160 NS_ASSERTION(geckoWidget, "Window delegate not returning a gecko widget!");
michael@0 2161
michael@0 2162 nsMenuBarX* geckoMenuBar = geckoWidget->GetMenuBar();
michael@0 2163 if (geckoMenuBar) {
michael@0 2164 geckoMenuBar->Paint();
michael@0 2165 }
michael@0 2166 else {
michael@0 2167 // sometimes we don't have a native application menu early in launching
michael@0 2168 if (!sApplicationMenu)
michael@0 2169 return;
michael@0 2170
michael@0 2171 NSMenu* mainMenu = [NSApp mainMenu];
michael@0 2172 NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
michael@0 2173
michael@0 2174 // Create a new menu bar.
michael@0 2175 // We create a GeckoNSMenu because all menu bar NSMenu objects should use that subclass for
michael@0 2176 // key handling reasons.
michael@0 2177 GeckoNSMenu* newMenuBar = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"];
michael@0 2178
michael@0 2179 // move the application menu from the existing menu bar to the new one
michael@0 2180 NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain];
michael@0 2181 [mainMenu removeItemAtIndex:0];
michael@0 2182 [newMenuBar insertItem:firstMenuItem atIndex:0];
michael@0 2183 [firstMenuItem release];
michael@0 2184
michael@0 2185 // set our new menu bar as the main menu
michael@0 2186 [NSApp setMainMenu:newMenuBar];
michael@0 2187 [newMenuBar release];
michael@0 2188 }
michael@0 2189
michael@0 2190 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 2191 }
michael@0 2192
michael@0 2193 - (id)initWithGeckoWindow:(nsCocoaWindow*)geckoWind
michael@0 2194 {
michael@0 2195 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
michael@0 2196
michael@0 2197 [super init];
michael@0 2198 mGeckoWindow = geckoWind;
michael@0 2199 mToplevelActiveState = false;
michael@0 2200 mHasEverBeenZoomed = false;
michael@0 2201 return self;
michael@0 2202
michael@0 2203 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
michael@0 2204 }
michael@0 2205
michael@0 2206 - (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)proposedFrameSize
michael@0 2207 {
michael@0 2208 RollUpPopups();
michael@0 2209
michael@0 2210 return proposedFrameSize;
michael@0 2211 }
michael@0 2212
michael@0 2213 - (void)windowDidResize:(NSNotification *)aNotification
michael@0 2214 {
michael@0 2215 BaseWindow* window = [aNotification object];
michael@0 2216 [window updateTrackingArea];
michael@0 2217
michael@0 2218 if (!mGeckoWindow)
michael@0 2219 return;
michael@0 2220
michael@0 2221 // Resizing might have changed our zoom state.
michael@0 2222 mGeckoWindow->DispatchSizeModeEvent();
michael@0 2223 mGeckoWindow->ReportSizeEvent();
michael@0 2224 }
michael@0 2225
michael@0 2226 - (void)windowDidChangeScreen:(NSNotification *)aNotification
michael@0 2227 {
michael@0 2228 if (!mGeckoWindow)
michael@0 2229 return;
michael@0 2230
michael@0 2231 // Because of Cocoa's peculiar treatment of zero-size windows (see comments
michael@0 2232 // at GetBackingScaleFactor() above), we sometimes have a situation where
michael@0 2233 // our concept of backing scale (based on the screen where the zero-sized
michael@0 2234 // window is positioned) differs from Cocoa's idea (always based on the
michael@0 2235 // Retina screen, AFAICT, even when an external non-Retina screen is the
michael@0 2236 // primary display).
michael@0 2237 //
michael@0 2238 // As a result, if the window was created with zero size on an external
michael@0 2239 // display, but then made visible on the (secondary) Retina screen, we
michael@0 2240 // will *not* get a windowDidChangeBackingProperties notification for it.
michael@0 2241 // This leads to an incorrect GetDefaultScale(), and widget coordinate
michael@0 2242 // confusion, as per bug 853252.
michael@0 2243 //
michael@0 2244 // To work around this, we check for a backing scale mismatch when we
michael@0 2245 // receive a windowDidChangeScreen notification, as we will receive this
michael@0 2246 // even if Cocoa was already treating the zero-size window as having
michael@0 2247 // Retina backing scale.
michael@0 2248 NSWindow *window = (NSWindow *)[aNotification object];
michael@0 2249 if ([window respondsToSelector:@selector(backingScaleFactor)]) {
michael@0 2250 if (GetBackingScaleFactor(window) != mGeckoWindow->BackingScaleFactor()) {
michael@0 2251 mGeckoWindow->BackingScaleFactorChanged();
michael@0 2252 }
michael@0 2253 }
michael@0 2254
michael@0 2255 mGeckoWindow->ReportMoveEvent();
michael@0 2256 }
michael@0 2257
michael@0 2258 // Lion's full screen mode will bypass our internal fullscreen tracking, so
michael@0 2259 // we need to catch it when we transition and call our own methods, which in
michael@0 2260 // turn will fire "fullscreen" events.
michael@0 2261 - (void)windowDidEnterFullScreen:(NSNotification *)notification
michael@0 2262 {
michael@0 2263 if (!mGeckoWindow) {
michael@0 2264 return;
michael@0 2265 }
michael@0 2266
michael@0 2267 mGeckoWindow->EnteredFullScreen(true);
michael@0 2268 }
michael@0 2269
michael@0 2270 - (void)windowDidExitFullScreen:(NSNotification *)notification
michael@0 2271 {
michael@0 2272 if (!mGeckoWindow) {
michael@0 2273 return;
michael@0 2274 }
michael@0 2275
michael@0 2276 mGeckoWindow->EnteredFullScreen(false);
michael@0 2277 }
michael@0 2278
michael@0 2279 - (void)windowDidFailToEnterFullScreen:(NSWindow *)window
michael@0 2280 {
michael@0 2281 if (!mGeckoWindow) {
michael@0 2282 return;
michael@0 2283 }
michael@0 2284
michael@0 2285 mGeckoWindow->EnteredFullScreen(false);
michael@0 2286 }
michael@0 2287
michael@0 2288 - (void)windowDidFailToExitFullScreen:(NSWindow *)window
michael@0 2289 {
michael@0 2290 if (!mGeckoWindow) {
michael@0 2291 return;
michael@0 2292 }
michael@0 2293
michael@0 2294 mGeckoWindow->EnteredFullScreen(true);
michael@0 2295 }
michael@0 2296
michael@0 2297 - (void)windowDidBecomeMain:(NSNotification *)aNotification
michael@0 2298 {
michael@0 2299 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 2300
michael@0 2301 RollUpPopups();
michael@0 2302 ChildViewMouseTracker::ReEvaluateMouseEnterState();
michael@0 2303
michael@0 2304 // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
michael@0 2305 // app modally. If one of those is up then we want it to retain its menu bar.
michael@0 2306 if ([NSApp _isRunningAppModal])
michael@0 2307 return;
michael@0 2308 NSWindow* window = [aNotification object];
michael@0 2309 if (window)
michael@0 2310 [WindowDelegate paintMenubarForWindow:window];
michael@0 2311
michael@0 2312 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 2313 }
michael@0 2314
michael@0 2315 - (void)windowDidResignMain:(NSNotification *)aNotification
michael@0 2316 {
michael@0 2317 RollUpPopups();
michael@0 2318 ChildViewMouseTracker::ReEvaluateMouseEnterState();
michael@0 2319
michael@0 2320 // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
michael@0 2321 // app modally. If one of those is up then we want it to retain its menu bar.
michael@0 2322 if ([NSApp _isRunningAppModal])
michael@0 2323 return;
michael@0 2324 nsRefPtr<nsMenuBarX> hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
michael@0 2325 if (hiddenWindowMenuBar) {
michael@0 2326 // printf("painting hidden window menu bar due to window losing main status\n");
michael@0 2327 hiddenWindowMenuBar->Paint();
michael@0 2328 }
michael@0 2329 }
michael@0 2330
michael@0 2331 - (void)windowDidBecomeKey:(NSNotification *)aNotification
michael@0 2332 {
michael@0 2333 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 2334
michael@0 2335 RollUpPopups();
michael@0 2336 ChildViewMouseTracker::ReEvaluateMouseEnterState();
michael@0 2337
michael@0 2338 NSWindow* window = [aNotification object];
michael@0 2339 if ([window isSheet])
michael@0 2340 [WindowDelegate paintMenubarForWindow:window];
michael@0 2341
michael@0 2342 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 2343 }
michael@0 2344
michael@0 2345 - (void)windowDidResignKey:(NSNotification *)aNotification
michael@0 2346 {
michael@0 2347 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 2348
michael@0 2349 RollUpPopups();
michael@0 2350 ChildViewMouseTracker::ReEvaluateMouseEnterState();
michael@0 2351
michael@0 2352 // If a sheet just resigned key then we should paint the menu bar
michael@0 2353 // for whatever window is now main.
michael@0 2354 NSWindow* window = [aNotification object];
michael@0 2355 if ([window isSheet])
michael@0 2356 [WindowDelegate paintMenubarForWindow:[NSApp mainWindow]];
michael@0 2357
michael@0 2358 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 2359 }
michael@0 2360
michael@0 2361 - (void)windowWillMove:(NSNotification *)aNotification
michael@0 2362 {
michael@0 2363 RollUpPopups();
michael@0 2364 }
michael@0 2365
michael@0 2366 - (void)windowDidMove:(NSNotification *)aNotification
michael@0 2367 {
michael@0 2368 if (mGeckoWindow)
michael@0 2369 mGeckoWindow->ReportMoveEvent();
michael@0 2370 }
michael@0 2371
michael@0 2372 - (BOOL)windowShouldClose:(id)sender
michael@0 2373 {
michael@0 2374 nsIWidgetListener* listener = mGeckoWindow ? mGeckoWindow->GetWidgetListener() : nullptr;
michael@0 2375 if (listener)
michael@0 2376 listener->RequestWindowClose(mGeckoWindow);
michael@0 2377 return NO; // gecko will do it
michael@0 2378 }
michael@0 2379
michael@0 2380 - (void)windowWillClose:(NSNotification *)aNotification
michael@0 2381 {
michael@0 2382 RollUpPopups();
michael@0 2383 }
michael@0 2384
michael@0 2385 - (void)windowWillMiniaturize:(NSNotification *)aNotification
michael@0 2386 {
michael@0 2387 RollUpPopups();
michael@0 2388 }
michael@0 2389
michael@0 2390 - (void)windowDidMiniaturize:(NSNotification *)aNotification
michael@0 2391 {
michael@0 2392 if (mGeckoWindow)
michael@0 2393 mGeckoWindow->DispatchSizeModeEvent();
michael@0 2394 }
michael@0 2395
michael@0 2396 - (void)windowDidDeminiaturize:(NSNotification *)aNotification
michael@0 2397 {
michael@0 2398 if (mGeckoWindow)
michael@0 2399 mGeckoWindow->DispatchSizeModeEvent();
michael@0 2400 }
michael@0 2401
michael@0 2402 - (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)proposedFrame
michael@0 2403 {
michael@0 2404 if (!mHasEverBeenZoomed && [window isZoomed])
michael@0 2405 return NO; // See bug 429954.
michael@0 2406
michael@0 2407 mHasEverBeenZoomed = YES;
michael@0 2408 return YES;
michael@0 2409 }
michael@0 2410
michael@0 2411 - (void)didEndSheet:(NSWindow*)sheet returnCode:(int)returnCode contextInfo:(void*)contextInfo
michael@0 2412 {
michael@0 2413 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 2414
michael@0 2415 // Note: 'contextInfo' (if it is set) is the window that is the parent of
michael@0 2416 // the sheet. The value of contextInfo is determined in
michael@0 2417 // nsCocoaWindow::Show(). If it's set, 'contextInfo' is always the top-
michael@0 2418 // level window, not another sheet itself. But 'contextInfo' is nil if
michael@0 2419 // our parent window is also a sheet -- in that case we shouldn't send
michael@0 2420 // the top-level window any activate events (because it's our parent
michael@0 2421 // window that needs to get these events, not the top-level window).
michael@0 2422 [TopLevelWindowData deactivateInWindow:sheet];
michael@0 2423 [sheet orderOut:self];
michael@0 2424 if (contextInfo)
michael@0 2425 [TopLevelWindowData activateInWindow:(NSWindow*)contextInfo];
michael@0 2426
michael@0 2427 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 2428 }
michael@0 2429
michael@0 2430 - (void)windowDidChangeBackingProperties:(NSNotification *)aNotification
michael@0 2431 {
michael@0 2432 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 2433
michael@0 2434 NSWindow *window = (NSWindow *)[aNotification object];
michael@0 2435
michael@0 2436 if ([window respondsToSelector:@selector(backingScaleFactor)]) {
michael@0 2437 CGFloat oldFactor =
michael@0 2438 [[[aNotification userInfo]
michael@0 2439 objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue];
michael@0 2440 if ([window backingScaleFactor] != oldFactor) {
michael@0 2441 mGeckoWindow->BackingScaleFactorChanged();
michael@0 2442 }
michael@0 2443 }
michael@0 2444
michael@0 2445 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 2446 }
michael@0 2447
michael@0 2448 - (nsCocoaWindow*)geckoWidget
michael@0 2449 {
michael@0 2450 return mGeckoWindow;
michael@0 2451 }
michael@0 2452
michael@0 2453 - (bool)toplevelActiveState
michael@0 2454 {
michael@0 2455 return mToplevelActiveState;
michael@0 2456 }
michael@0 2457
michael@0 2458 - (void)sendToplevelActivateEvents
michael@0 2459 {
michael@0 2460 if (!mToplevelActiveState && mGeckoWindow) {
michael@0 2461 nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener();
michael@0 2462 if (listener)
michael@0 2463 listener->WindowActivated();
michael@0 2464 mToplevelActiveState = true;
michael@0 2465 }
michael@0 2466 }
michael@0 2467
michael@0 2468 - (void)sendToplevelDeactivateEvents
michael@0 2469 {
michael@0 2470 if (mToplevelActiveState && mGeckoWindow) {
michael@0 2471 nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener();
michael@0 2472 if (listener)
michael@0 2473 listener->WindowDeactivated();
michael@0 2474 mToplevelActiveState = false;
michael@0 2475 }
michael@0 2476 }
michael@0 2477
michael@0 2478 @end
michael@0 2479
michael@0 2480 static float
michael@0 2481 GetDPI(NSWindow* aWindow)
michael@0 2482 {
michael@0 2483 NSScreen* screen = [aWindow screen];
michael@0 2484 if (!screen)
michael@0 2485 return 96.0f;
michael@0 2486
michael@0 2487 CGDirectDisplayID displayID =
michael@0 2488 [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] intValue];
michael@0 2489 CGFloat heightMM = ::CGDisplayScreenSize(displayID).height;
michael@0 2490 size_t heightPx = ::CGDisplayPixelsHigh(displayID);
michael@0 2491 CGFloat scaleFactor = [aWindow userSpaceScaleFactor];
michael@0 2492 if (scaleFactor < 0.01 || heightMM < 1 || heightPx < 1) {
michael@0 2493 // Something extremely bogus is going on
michael@0 2494 return 96.0f;
michael@0 2495 }
michael@0 2496
michael@0 2497 // Currently we don't do our own scaling to take account
michael@0 2498 // of userSpaceScaleFactor, so every "pixel" we draw is actually
michael@0 2499 // userSpaceScaleFactor screen pixels. So divide the screen height
michael@0 2500 // by userSpaceScaleFactor to get the number of "device pixels"
michael@0 2501 // available.
michael@0 2502 float dpi = (heightPx / scaleFactor) / (heightMM / MM_PER_INCH_FLOAT);
michael@0 2503
michael@0 2504 // Account for HiDPI mode where Cocoa's "points" do not correspond to real
michael@0 2505 // device pixels
michael@0 2506 CGFloat backingScale = GetBackingScaleFactor(aWindow);
michael@0 2507
michael@0 2508 return dpi * backingScale;
michael@0 2509 }
michael@0 2510
michael@0 2511 @interface NSView(FrameViewMethodSwizzling)
michael@0 2512 - (NSPoint)FrameView__closeButtonOrigin;
michael@0 2513 - (NSPoint)FrameView__fullScreenButtonOrigin;
michael@0 2514 @end
michael@0 2515
michael@0 2516 @implementation NSView(FrameViewMethodSwizzling)
michael@0 2517
michael@0 2518 - (NSPoint)FrameView__closeButtonOrigin
michael@0 2519 {
michael@0 2520 NSPoint defaultPosition = [self FrameView__closeButtonOrigin];
michael@0 2521 if ([[self window] isKindOfClass:[ToolbarWindow class]]) {
michael@0 2522 return [(ToolbarWindow*)[self window] windowButtonsPositionWithDefaultPosition:defaultPosition];
michael@0 2523 }
michael@0 2524 return defaultPosition;
michael@0 2525 }
michael@0 2526
michael@0 2527 - (NSPoint)FrameView__fullScreenButtonOrigin
michael@0 2528 {
michael@0 2529 NSPoint defaultPosition = [self FrameView__fullScreenButtonOrigin];
michael@0 2530 if ([[self window] isKindOfClass:[ToolbarWindow class]]) {
michael@0 2531 return [(ToolbarWindow*)[self window] fullScreenButtonPositionWithDefaultPosition:defaultPosition];
michael@0 2532 }
michael@0 2533 return defaultPosition;
michael@0 2534 }
michael@0 2535
michael@0 2536 @end
michael@0 2537
michael@0 2538 static NSMutableSet *gSwizzledFrameViewClasses = nil;
michael@0 2539
michael@0 2540 @interface NSWindow(PrivateSetNeedsDisplayInRectMethod)
michael@0 2541 - (void)_setNeedsDisplayInRect:(NSRect)aRect;
michael@0 2542 @end
michael@0 2543
michael@0 2544 @interface BaseWindow(Private)
michael@0 2545 - (void)removeTrackingArea;
michael@0 2546 - (void)cursorUpdated:(NSEvent*)aEvent;
michael@0 2547 - (void)updateContentViewSize;
michael@0 2548 - (void)reflowTitlebarElements;
michael@0 2549 @end
michael@0 2550
michael@0 2551 @implementation BaseWindow
michael@0 2552
michael@0 2553 // The frame of a window is implemented using undocumented NSView subclasses.
michael@0 2554 // We offset the window buttons by overriding the methods _closeButtonOrigin
michael@0 2555 // and _fullScreenButtonOrigin on these frame view classes. The class which is
michael@0 2556 // used for a window is determined in the window's frameViewClassForStyleMask:
michael@0 2557 // method, so this is where we make sure that we have swizzled the method on
michael@0 2558 // all encountered classes.
michael@0 2559 + (Class)frameViewClassForStyleMask:(NSUInteger)styleMask
michael@0 2560 {
michael@0 2561 Class frameViewClass = [super frameViewClassForStyleMask:styleMask];
michael@0 2562
michael@0 2563 if (!gSwizzledFrameViewClasses) {
michael@0 2564 gSwizzledFrameViewClasses = [[NSMutableSet setWithCapacity:3] retain];
michael@0 2565 if (!gSwizzledFrameViewClasses) {
michael@0 2566 return frameViewClass;
michael@0 2567 }
michael@0 2568 }
michael@0 2569
michael@0 2570 static IMP our_closeButtonOrigin =
michael@0 2571 class_getMethodImplementation([NSView class],
michael@0 2572 @selector(FrameView__closeButtonOrigin));
michael@0 2573 static IMP our_fullScreenButtonOrigin =
michael@0 2574 class_getMethodImplementation([NSView class],
michael@0 2575 @selector(FrameView__fullScreenButtonOrigin));
michael@0 2576
michael@0 2577 if (![gSwizzledFrameViewClasses containsObject:frameViewClass]) {
michael@0 2578 // Either of these methods might be implemented in both a subclass of
michael@0 2579 // NSFrameView and one of its own subclasses. Which means that if we
michael@0 2580 // aren't careful we might end up swizzling the same method twice.
michael@0 2581 // Since method swizzling involves swapping pointers, this would break
michael@0 2582 // things.
michael@0 2583 IMP _closeButtonOrigin =
michael@0 2584 class_getMethodImplementation(frameViewClass,
michael@0 2585 @selector(_closeButtonOrigin));
michael@0 2586 if (_closeButtonOrigin && _closeButtonOrigin != our_closeButtonOrigin) {
michael@0 2587 nsToolkit::SwizzleMethods(frameViewClass, @selector(_closeButtonOrigin),
michael@0 2588 @selector(FrameView__closeButtonOrigin));
michael@0 2589 }
michael@0 2590 IMP _fullScreenButtonOrigin =
michael@0 2591 class_getMethodImplementation(frameViewClass,
michael@0 2592 @selector(_fullScreenButtonOrigin));
michael@0 2593 if (_fullScreenButtonOrigin &&
michael@0 2594 _fullScreenButtonOrigin != our_fullScreenButtonOrigin) {
michael@0 2595 nsToolkit::SwizzleMethods(frameViewClass, @selector(_fullScreenButtonOrigin),
michael@0 2596 @selector(FrameView__fullScreenButtonOrigin));
michael@0 2597 }
michael@0 2598 [gSwizzledFrameViewClasses addObject:frameViewClass];
michael@0 2599 }
michael@0 2600
michael@0 2601 return frameViewClass;
michael@0 2602 }
michael@0 2603
michael@0 2604 - (id)initWithContentRect:(NSRect)aContentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)aFlag
michael@0 2605 {
michael@0 2606 mDrawsIntoWindowFrame = NO;
michael@0 2607 [super initWithContentRect:aContentRect styleMask:aStyle backing:aBufferingType defer:aFlag];
michael@0 2608 mState = nil;
michael@0 2609 mActiveTitlebarColor = nil;
michael@0 2610 mInactiveTitlebarColor = nil;
michael@0 2611 mScheduledShadowInvalidation = NO;
michael@0 2612 mDisabledNeedsDisplay = NO;
michael@0 2613 mDPI = GetDPI(self);
michael@0 2614 mTrackingArea = nil;
michael@0 2615 mBeingShown = NO;
michael@0 2616 mDrawTitle = NO;
michael@0 2617 [self updateTrackingArea];
michael@0 2618
michael@0 2619 return self;
michael@0 2620 }
michael@0 2621
michael@0 2622 - (void)setBeingShown:(BOOL)aValue
michael@0 2623 {
michael@0 2624 mBeingShown = aValue;
michael@0 2625 }
michael@0 2626
michael@0 2627 - (BOOL)isBeingShown
michael@0 2628 {
michael@0 2629 return mBeingShown;
michael@0 2630 }
michael@0 2631
michael@0 2632 - (BOOL)isVisibleOrBeingShown
michael@0 2633 {
michael@0 2634 return [super isVisible] || mBeingShown;
michael@0 2635 }
michael@0 2636
michael@0 2637 - (void)disableSetNeedsDisplay
michael@0 2638 {
michael@0 2639 mDisabledNeedsDisplay = YES;
michael@0 2640 }
michael@0 2641
michael@0 2642 - (void)enableSetNeedsDisplay
michael@0 2643 {
michael@0 2644 mDisabledNeedsDisplay = NO;
michael@0 2645 }
michael@0 2646
michael@0 2647 - (void)dealloc
michael@0 2648 {
michael@0 2649 [mActiveTitlebarColor release];
michael@0 2650 [mInactiveTitlebarColor release];
michael@0 2651 [self removeTrackingArea];
michael@0 2652 ChildViewMouseTracker::OnDestroyWindow(self);
michael@0 2653 [super dealloc];
michael@0 2654 }
michael@0 2655
michael@0 2656 static const NSString* kStateTitleKey = @"title";
michael@0 2657 static const NSString* kStateDrawsContentsIntoWindowFrameKey = @"drawsContentsIntoWindowFrame";
michael@0 2658 static const NSString* kStateActiveTitlebarColorKey = @"activeTitlebarColor";
michael@0 2659 static const NSString* kStateInactiveTitlebarColorKey = @"inactiveTitlebarColor";
michael@0 2660 static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
michael@0 2661
michael@0 2662 - (void)importState:(NSDictionary*)aState
michael@0 2663 {
michael@0 2664 [self setTitle:[aState objectForKey:kStateTitleKey]];
michael@0 2665 [self setDrawsContentsIntoWindowFrame:[[aState objectForKey:kStateDrawsContentsIntoWindowFrameKey] boolValue]];
michael@0 2666 [self setTitlebarColor:[aState objectForKey:kStateActiveTitlebarColorKey] forActiveWindow:YES];
michael@0 2667 [self setTitlebarColor:[aState objectForKey:kStateInactiveTitlebarColorKey] forActiveWindow:NO];
michael@0 2668 [self setShowsToolbarButton:[[aState objectForKey:kStateShowsToolbarButton] boolValue]];
michael@0 2669 }
michael@0 2670
michael@0 2671 - (NSMutableDictionary*)exportState
michael@0 2672 {
michael@0 2673 NSMutableDictionary* state = [NSMutableDictionary dictionaryWithCapacity:10];
michael@0 2674 [state setObject:[self title] forKey:kStateTitleKey];
michael@0 2675 [state setObject:[NSNumber numberWithBool:[self drawsContentsIntoWindowFrame]]
michael@0 2676 forKey:kStateDrawsContentsIntoWindowFrameKey];
michael@0 2677 NSColor* activeTitlebarColor = [self titlebarColorForActiveWindow:YES];
michael@0 2678 if (activeTitlebarColor) {
michael@0 2679 [state setObject:activeTitlebarColor forKey:kStateActiveTitlebarColorKey];
michael@0 2680 }
michael@0 2681 NSColor* inactiveTitlebarColor = [self titlebarColorForActiveWindow:NO];
michael@0 2682 if (inactiveTitlebarColor) {
michael@0 2683 [state setObject:inactiveTitlebarColor forKey:kStateInactiveTitlebarColorKey];
michael@0 2684 }
michael@0 2685 [state setObject:[NSNumber numberWithBool:[self showsToolbarButton]]
michael@0 2686 forKey:kStateShowsToolbarButton];
michael@0 2687 return state;
michael@0 2688 }
michael@0 2689
michael@0 2690 - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState
michael@0 2691 {
michael@0 2692 bool changed = (aState != mDrawsIntoWindowFrame);
michael@0 2693 mDrawsIntoWindowFrame = aState;
michael@0 2694 if (changed) {
michael@0 2695 [self updateContentViewSize];
michael@0 2696 [self reflowTitlebarElements];
michael@0 2697 }
michael@0 2698 }
michael@0 2699
michael@0 2700 - (BOOL)drawsContentsIntoWindowFrame
michael@0 2701 {
michael@0 2702 return mDrawsIntoWindowFrame;
michael@0 2703 }
michael@0 2704
michael@0 2705 - (void)setWantsTitleDrawn:(BOOL)aDrawTitle
michael@0 2706 {
michael@0 2707 mDrawTitle = aDrawTitle;
michael@0 2708 }
michael@0 2709
michael@0 2710 - (BOOL)wantsTitleDrawn
michael@0 2711 {
michael@0 2712 return mDrawTitle;
michael@0 2713 }
michael@0 2714
michael@0 2715 // Pass nil here to get the default appearance.
michael@0 2716 - (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive
michael@0 2717 {
michael@0 2718 [aColor retain];
michael@0 2719 if (aActive) {
michael@0 2720 [mActiveTitlebarColor release];
michael@0 2721 mActiveTitlebarColor = aColor;
michael@0 2722 } else {
michael@0 2723 [mInactiveTitlebarColor release];
michael@0 2724 mInactiveTitlebarColor = aColor;
michael@0 2725 }
michael@0 2726 }
michael@0 2727
michael@0 2728 - (NSColor*)titlebarColorForActiveWindow:(BOOL)aActive
michael@0 2729 {
michael@0 2730 return aActive ? mActiveTitlebarColor : mInactiveTitlebarColor;
michael@0 2731 }
michael@0 2732
michael@0 2733 - (void)deferredInvalidateShadow
michael@0 2734 {
michael@0 2735 if (mScheduledShadowInvalidation || [self isOpaque] || ![self hasShadow])
michael@0 2736 return;
michael@0 2737
michael@0 2738 [self performSelector:@selector(invalidateShadow) withObject:nil afterDelay:0];
michael@0 2739 mScheduledShadowInvalidation = YES;
michael@0 2740 }
michael@0 2741
michael@0 2742 - (void)invalidateShadow
michael@0 2743 {
michael@0 2744 [super invalidateShadow];
michael@0 2745 mScheduledShadowInvalidation = NO;
michael@0 2746 }
michael@0 2747
michael@0 2748 - (float)getDPI
michael@0 2749 {
michael@0 2750 return mDPI;
michael@0 2751 }
michael@0 2752
michael@0 2753 - (NSView*)trackingAreaView
michael@0 2754 {
michael@0 2755 NSView* contentView = [self contentView];
michael@0 2756 return [contentView superview] ? [contentView superview] : contentView;
michael@0 2757 }
michael@0 2758
michael@0 2759 - (ChildView*)mainChildView
michael@0 2760 {
michael@0 2761 NSView *contentView = [self contentView];
michael@0 2762 // A PopupWindow's contentView is a ChildView object.
michael@0 2763 if ([contentView isKindOfClass:[ChildView class]]) {
michael@0 2764 return (ChildView*)contentView;
michael@0 2765 }
michael@0 2766 NSView* lastView = [[contentView subviews] lastObject];
michael@0 2767 if ([lastView isKindOfClass:[ChildView class]]) {
michael@0 2768 return (ChildView*)lastView;
michael@0 2769 }
michael@0 2770 return nil;
michael@0 2771 }
michael@0 2772
michael@0 2773 - (void)removeTrackingArea
michael@0 2774 {
michael@0 2775 if (mTrackingArea) {
michael@0 2776 [[self trackingAreaView] removeTrackingArea:mTrackingArea];
michael@0 2777 [mTrackingArea release];
michael@0 2778 mTrackingArea = nil;
michael@0 2779 }
michael@0 2780 }
michael@0 2781
michael@0 2782 - (void)updateTrackingArea
michael@0 2783 {
michael@0 2784 [self removeTrackingArea];
michael@0 2785
michael@0 2786 NSView* view = [self trackingAreaView];
michael@0 2787 const NSTrackingAreaOptions options =
michael@0 2788 NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways;
michael@0 2789 mTrackingArea = [[NSTrackingArea alloc] initWithRect:[view bounds]
michael@0 2790 options:options
michael@0 2791 owner:self
michael@0 2792 userInfo:nil];
michael@0 2793 [view addTrackingArea:mTrackingArea];
michael@0 2794 }
michael@0 2795
michael@0 2796 - (void)mouseEntered:(NSEvent*)aEvent
michael@0 2797 {
michael@0 2798 ChildViewMouseTracker::MouseEnteredWindow(aEvent);
michael@0 2799 }
michael@0 2800
michael@0 2801 - (void)mouseExited:(NSEvent*)aEvent
michael@0 2802 {
michael@0 2803 ChildViewMouseTracker::MouseExitedWindow(aEvent);
michael@0 2804 }
michael@0 2805
michael@0 2806 - (void)mouseMoved:(NSEvent*)aEvent
michael@0 2807 {
michael@0 2808 ChildViewMouseTracker::MouseMoved(aEvent);
michael@0 2809 }
michael@0 2810
michael@0 2811 - (void)cursorUpdated:(NSEvent*)aEvent
michael@0 2812 {
michael@0 2813 // Nothing to do here, but NSTrackingArea wants us to implement this method.
michael@0 2814 }
michael@0 2815
michael@0 2816 - (void)_setNeedsDisplayInRect:(NSRect)aRect
michael@0 2817 {
michael@0 2818 // Prevent unnecessary invalidations due to moving NSViews (e.g. for plugins)
michael@0 2819 if (!mDisabledNeedsDisplay) {
michael@0 2820 // This method is only called by Cocoa, so when we're here, we know that
michael@0 2821 // it's available and don't need to check whether our superclass responds
michael@0 2822 // to the selector.
michael@0 2823 [super _setNeedsDisplayInRect:aRect];
michael@0 2824 }
michael@0 2825 }
michael@0 2826
michael@0 2827 - (void)updateContentViewSize
michael@0 2828 {
michael@0 2829 NSRect rect = [self contentRectForFrameRect:[self frame]];
michael@0 2830 [[self contentView] setFrameSize:rect.size];
michael@0 2831 }
michael@0 2832
michael@0 2833 // Possibly move the titlebar buttons.
michael@0 2834 - (void)reflowTitlebarElements
michael@0 2835 {
michael@0 2836 NSView *frameView = [[self contentView] superview];
michael@0 2837 if ([frameView respondsToSelector:@selector(_tileTitlebarAndRedisplay:)]) {
michael@0 2838 [frameView _tileTitlebarAndRedisplay:NO];
michael@0 2839 }
michael@0 2840 }
michael@0 2841
michael@0 2842 // Override methods that translate between content rect and frame rect.
michael@0 2843 - (NSRect)contentRectForFrameRect:(NSRect)aRect
michael@0 2844 {
michael@0 2845 if ([self drawsContentsIntoWindowFrame]) {
michael@0 2846 return aRect;
michael@0 2847 }
michael@0 2848 return [super contentRectForFrameRect:aRect];
michael@0 2849 }
michael@0 2850
michael@0 2851 - (NSRect)contentRectForFrameRect:(NSRect)aRect styleMask:(NSUInteger)aMask
michael@0 2852 {
michael@0 2853 if ([self drawsContentsIntoWindowFrame]) {
michael@0 2854 return aRect;
michael@0 2855 }
michael@0 2856 if ([super respondsToSelector:@selector(contentRectForFrameRect:styleMask:)]) {
michael@0 2857 return [super contentRectForFrameRect:aRect styleMask:aMask];
michael@0 2858 } else {
michael@0 2859 return [NSWindow contentRectForFrameRect:aRect styleMask:aMask];
michael@0 2860 }
michael@0 2861 }
michael@0 2862
michael@0 2863 - (NSRect)frameRectForContentRect:(NSRect)aRect
michael@0 2864 {
michael@0 2865 if ([self drawsContentsIntoWindowFrame]) {
michael@0 2866 return aRect;
michael@0 2867 }
michael@0 2868 return [super frameRectForContentRect:aRect];
michael@0 2869 }
michael@0 2870
michael@0 2871 - (NSRect)frameRectForContentRect:(NSRect)aRect styleMask:(NSUInteger)aMask
michael@0 2872 {
michael@0 2873 if ([self drawsContentsIntoWindowFrame]) {
michael@0 2874 return aRect;
michael@0 2875 }
michael@0 2876 if ([super respondsToSelector:@selector(frameRectForContentRect:styleMask:)]) {
michael@0 2877 return [super frameRectForContentRect:aRect styleMask:aMask];
michael@0 2878 } else {
michael@0 2879 return [NSWindow frameRectForContentRect:aRect styleMask:aMask];
michael@0 2880 }
michael@0 2881 }
michael@0 2882
michael@0 2883 - (void)setContentView:(NSView*)aView
michael@0 2884 {
michael@0 2885 [super setContentView:aView];
michael@0 2886
michael@0 2887 // Now move the contentView to the bottommost layer so that it's guaranteed
michael@0 2888 // to be under the window buttons.
michael@0 2889 NSView* frameView = [aView superview];
michael@0 2890 [aView removeFromSuperview];
michael@0 2891 [frameView addSubview:aView positioned:NSWindowBelow relativeTo:nil];
michael@0 2892 }
michael@0 2893
michael@0 2894 - (NSArray*)titlebarControls
michael@0 2895 {
michael@0 2896 // Return all subviews of the frameView which are not the content view.
michael@0 2897 NSView* frameView = [[self contentView] superview];
michael@0 2898 NSMutableArray* array = [[[frameView subviews] mutableCopy] autorelease];
michael@0 2899 [array removeObject:[self contentView]];
michael@0 2900 return array;
michael@0 2901 }
michael@0 2902
michael@0 2903 - (BOOL)respondsToSelector:(SEL)aSelector
michael@0 2904 {
michael@0 2905 // Claim the window doesn't respond to this so that the system
michael@0 2906 // doesn't steal keyboard equivalents for it. Bug 613710.
michael@0 2907 if (aSelector == @selector(cancelOperation:)) {
michael@0 2908 return NO;
michael@0 2909 }
michael@0 2910
michael@0 2911 return [super respondsToSelector:aSelector];
michael@0 2912 }
michael@0 2913
michael@0 2914 - (void) doCommandBySelector:(SEL)aSelector
michael@0 2915 {
michael@0 2916 // We override this so that it won't beep if it can't act.
michael@0 2917 // We want to control the beeping for missing or disabled
michael@0 2918 // commands ourselves.
michael@0 2919 [self tryToPerform:aSelector with:nil];
michael@0 2920 }
michael@0 2921
michael@0 2922 - (id)accessibilityAttributeValue:(NSString *)attribute
michael@0 2923 {
michael@0 2924 id retval = [super accessibilityAttributeValue:attribute];
michael@0 2925
michael@0 2926 // The following works around a problem with Text-to-Speech on OS X 10.7.
michael@0 2927 // See bug 674612 for more info.
michael@0 2928 //
michael@0 2929 // When accessibility is off, AXUIElementCopyAttributeValue(), when called
michael@0 2930 // on an AXApplication object to get its AXFocusedUIElement attribute,
michael@0 2931 // always returns an AXWindow object (the actual browser window -- never a
michael@0 2932 // mozAccessible object). This also happens with accessibility turned on,
michael@0 2933 // if no other object in the browser window has yet been focused. But if
michael@0 2934 // the browser window has a title bar (as it currently always does), the
michael@0 2935 // AXWindow object will always have four "accessible" children, one of which
michael@0 2936 // is an AXStaticText object (the title bar's "title"; the other three are
michael@0 2937 // the close, minimize and zoom buttons). This means that (for complicated
michael@0 2938 // reasons, for which see bug 674612) Text-to-Speech on OS X 10.7 will often
michael@0 2939 // "speak" the window title, no matter what text is selected, or even if no
michael@0 2940 // text at all is selected. (This always happens when accessibility is off.
michael@0 2941 // It doesn't happen in Firefox releases because Apple has (on OS X 10.7)
michael@0 2942 // special-cased the handling of apps whose CFBundleIdentifier is
michael@0 2943 // org.mozilla.firefox.)
michael@0 2944 //
michael@0 2945 // We work around this problem by only returning AXChildren that are
michael@0 2946 // mozAccessible object or are one of the titlebar's buttons (which
michael@0 2947 // instantiate subclasses of NSButtonCell).
michael@0 2948 if (nsCocoaFeatures::OnLionOrLater() && [retval isKindOfClass:[NSArray class]] &&
michael@0 2949 [attribute isEqualToString:@"AXChildren"]) {
michael@0 2950 NSMutableArray *holder = [NSMutableArray arrayWithCapacity:10];
michael@0 2951 [holder addObjectsFromArray:(NSArray *)retval];
michael@0 2952 NSUInteger count = [holder count];
michael@0 2953 for (NSInteger i = count - 1; i >= 0; --i) {
michael@0 2954 id item = [holder objectAtIndex:i];
michael@0 2955 // Remove anything from holder that isn't one of the titlebar's buttons
michael@0 2956 // (which instantiate subclasses of NSButtonCell) or a mozAccessible
michael@0 2957 // object (or one of its subclasses).
michael@0 2958 if (![item isKindOfClass:[NSButtonCell class]] &&
michael@0 2959 ![item respondsToSelector:@selector(hasRepresentedView)]) {
michael@0 2960 [holder removeObjectAtIndex:i];
michael@0 2961 }
michael@0 2962 }
michael@0 2963 retval = [NSArray arrayWithArray:holder];
michael@0 2964 }
michael@0 2965
michael@0 2966 return retval;
michael@0 2967 }
michael@0 2968
michael@0 2969 // If we were built on OS X 10.6 or with the 10.6 SDK and are running on Lion,
michael@0 2970 // the OS (specifically -[NSWindow sendEvent:]) won't send NSEventTypeGesture
michael@0 2971 // events to -[ChildView magnifyWithEvent:] as it should. The following code
michael@0 2972 // gets around this. See bug 863841.
michael@0 2973 #if !defined( MAC_OS_X_VERSION_10_7 ) || \
michael@0 2974 ( MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 )
michael@0 2975 - (void)sendEvent:(NSEvent *)anEvent
michael@0 2976 {
michael@0 2977 if ([ChildView isLionSmartMagnifyEvent: anEvent]) {
michael@0 2978 [[self mainChildView] magnifyWithEvent:anEvent];
michael@0 2979 return;
michael@0 2980 }
michael@0 2981 [super sendEvent:anEvent];
michael@0 2982 }
michael@0 2983 #endif
michael@0 2984
michael@0 2985 @end
michael@0 2986
michael@0 2987 // This class allows us to exercise control over the window's title bar. This
michael@0 2988 // allows for a "unified toolbar" look without having to extend the content
michael@0 2989 // area into the title bar. It works like this:
michael@0 2990 // 1) We set the window's style to textured.
michael@0 2991 // 2) Because of this, the background color applies to the entire window, including
michael@0 2992 // the titlebar area. For normal textured windows, the default pattern is a
michael@0 2993 // "brushed metal" image on Tiger and a unified gradient on Leopard.
michael@0 2994 // 3) We set the background color to a custom NSColor subclass that knows how tall the window is.
michael@0 2995 // When -set is called on it, it sets a pattern (with a draw callback) as the fill. In that callback,
michael@0 2996 // it paints the the titlebar and background colors in the correct areas of the context it's given,
michael@0 2997 // which will fill the entire window (CG will tile it horizontally for us).
michael@0 2998 // 4) Whenever the window's main state changes and when [window display] is called,
michael@0 2999 // Cocoa redraws the titlebar using the patternDraw callback function.
michael@0 3000 //
michael@0 3001 // This class also provides us with a pill button to show/hide the toolbar up to 10.6.
michael@0 3002 //
michael@0 3003 // Drawing the unified gradient in the titlebar and the toolbar works like this:
michael@0 3004 // 1) In the style sheet we set the toolbar's -moz-appearance to toolbar or
michael@0 3005 // -moz-mac-unified-toolbar.
michael@0 3006 // 2) When the toolbar is visible and we paint the application chrome
michael@0 3007 // window, the array that Gecko passes nsChildView::UpdateThemeGeometries
michael@0 3008 // will contain an entry for the widget type NS_THEME_TOOLBAR or
michael@0 3009 // NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR.
michael@0 3010 // 3) nsChildView::UpdateThemeGeometries finds the toolbar frame's ToolbarWindow
michael@0 3011 // and passes the toolbar frame's height to setUnifiedToolbarHeight.
michael@0 3012 // 4) If the toolbar height has changed, a titlebar redraw is triggered and the
michael@0 3013 // upper part of the unified gradient is drawn in the titlebar.
michael@0 3014 // 5) The lower part of the unified gradient in the toolbar is drawn during
michael@0 3015 // normal window content painting in nsNativeThemeCocoa::DrawUnifiedToolbar.
michael@0 3016 //
michael@0 3017 // Whenever the unified gradient is drawn in the titlebar or the toolbar, both
michael@0 3018 // titlebar height and toolbar height must be known in order to construct the
michael@0 3019 // correct gradient. But you can only get from the toolbar frame
michael@0 3020 // to the containing window - the other direction doesn't work. That's why the
michael@0 3021 // toolbar height is cached in the ToolbarWindow but nsNativeThemeCocoa can simply
michael@0 3022 // query the window for its titlebar height when drawing the toolbar.
michael@0 3023 //
michael@0 3024 // Note that in drawsContentsIntoWindowFrame mode, titlebar drawing works in a
michael@0 3025 // completely different way: In that mode, the window's mainChildView will
michael@0 3026 // cover the titlebar completely and nothing that happens in the window
michael@0 3027 // background will reach the screen.
michael@0 3028 @implementation ToolbarWindow
michael@0 3029
michael@0 3030 - (id)initWithContentRect:(NSRect)aContentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)aFlag
michael@0 3031 {
michael@0 3032 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
michael@0 3033
michael@0 3034 aStyle = aStyle | NSTexturedBackgroundWindowMask;
michael@0 3035 if ((self = [super initWithContentRect:aContentRect styleMask:aStyle backing:aBufferingType defer:aFlag])) {
michael@0 3036 mColor = [[TitlebarAndBackgroundColor alloc] initWithWindow:self];
michael@0 3037 // Bypass our guard method below.
michael@0 3038 [super setBackgroundColor:mColor];
michael@0 3039 mBackgroundColor = [[NSColor whiteColor] retain];
michael@0 3040
michael@0 3041 mUnifiedToolbarHeight = 22.0f;
michael@0 3042 mWindowButtonsRect = NSZeroRect;
michael@0 3043 mFullScreenButtonRect = NSZeroRect;
michael@0 3044
michael@0 3045 // setBottomCornerRounded: is a private API call, so we check to make sure
michael@0 3046 // we respond to it just in case.
michael@0 3047 if ([self respondsToSelector:@selector(setBottomCornerRounded:)])
michael@0 3048 [self setBottomCornerRounded:nsCocoaFeatures::OnLionOrLater()];
michael@0 3049
michael@0 3050 [self setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
michael@0 3051 [self setContentBorderThickness:0.0f forEdge:NSMaxYEdge];
michael@0 3052 }
michael@0 3053 return self;
michael@0 3054
michael@0 3055 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
michael@0 3056 }
michael@0 3057
michael@0 3058 - (void)dealloc
michael@0 3059 {
michael@0 3060 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 3061
michael@0 3062 [mColor release];
michael@0 3063 [mBackgroundColor release];
michael@0 3064 [mTitlebarView release];
michael@0 3065 [super dealloc];
michael@0 3066
michael@0 3067 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 3068 }
michael@0 3069
michael@0 3070 - (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive
michael@0 3071 {
michael@0 3072 [super setTitlebarColor:aColor forActiveWindow:aActive];
michael@0 3073 [self setTitlebarNeedsDisplayInRect:[self titlebarRect]];
michael@0 3074 }
michael@0 3075
michael@0 3076 - (void)setBackgroundColor:(NSColor*)aColor
michael@0 3077 {
michael@0 3078 [aColor retain];
michael@0 3079 [mBackgroundColor release];
michael@0 3080 mBackgroundColor = aColor;
michael@0 3081 }
michael@0 3082
michael@0 3083 - (NSColor*)windowBackgroundColor
michael@0 3084 {
michael@0 3085 return mBackgroundColor;
michael@0 3086 }
michael@0 3087
michael@0 3088 - (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect
michael@0 3089 {
michael@0 3090 [self setTitlebarNeedsDisplayInRect:aRect sync:NO];
michael@0 3091 }
michael@0 3092
michael@0 3093 - (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect sync:(BOOL)aSync
michael@0 3094 {
michael@0 3095 NSRect titlebarRect = [self titlebarRect];
michael@0 3096 NSRect rect = NSIntersectionRect(titlebarRect, aRect);
michael@0 3097 if (NSIsEmptyRect(rect))
michael@0 3098 return;
michael@0 3099
michael@0 3100 NSView* borderView = [[self contentView] superview];
michael@0 3101 if (!borderView)
michael@0 3102 return;
michael@0 3103
michael@0 3104 if (aSync) {
michael@0 3105 [borderView displayRect:rect];
michael@0 3106 } else {
michael@0 3107 [borderView setNeedsDisplayInRect:rect];
michael@0 3108 }
michael@0 3109 }
michael@0 3110
michael@0 3111 - (NSRect)titlebarRect
michael@0 3112 {
michael@0 3113 CGFloat titlebarHeight = [self titlebarHeight];
michael@0 3114 return NSMakeRect(0, [self frame].size.height - titlebarHeight,
michael@0 3115 [self frame].size.width, titlebarHeight);
michael@0 3116 }
michael@0 3117
michael@0 3118 // Returns the unified height of titlebar + toolbar.
michael@0 3119 - (CGFloat)unifiedToolbarHeight
michael@0 3120 {
michael@0 3121 return mUnifiedToolbarHeight;
michael@0 3122 }
michael@0 3123
michael@0 3124 - (CGFloat)titlebarHeight
michael@0 3125 {
michael@0 3126 // We use the original content rect here, not what we return from
michael@0 3127 // [self contentRectForFrameRect:], because that would give us a
michael@0 3128 // titlebarHeight of zero in drawsContentsIntoWindowFrame mode.
michael@0 3129 NSRect frameRect = [self frame];
michael@0 3130 NSRect originalContentRect = [NSWindow contentRectForFrameRect:frameRect styleMask:[self styleMask]];
michael@0 3131 return NSMaxY(frameRect) - NSMaxY(originalContentRect);
michael@0 3132 }
michael@0 3133
michael@0 3134 // Stores the complete height of titlebar + toolbar.
michael@0 3135 - (void)setUnifiedToolbarHeight:(CGFloat)aHeight
michael@0 3136 {
michael@0 3137 if (aHeight == mUnifiedToolbarHeight)
michael@0 3138 return;
michael@0 3139
michael@0 3140 mUnifiedToolbarHeight = aHeight;
michael@0 3141
michael@0 3142 // Update sheet positioning hint
michael@0 3143 CGFloat topMargin = mUnifiedToolbarHeight - [self titlebarHeight];
michael@0 3144 [self setContentBorderThickness:topMargin forEdge:NSMaxYEdge];
michael@0 3145
michael@0 3146 // Redraw the title bar. If we're inside painting, we'll do it right now,
michael@0 3147 // otherwise we'll just invalidate it.
michael@0 3148 BOOL needSyncRedraw = ([NSView focusView] != nil);
michael@0 3149 [self setTitlebarNeedsDisplayInRect:[self titlebarRect] sync:needSyncRedraw];
michael@0 3150 }
michael@0 3151
michael@0 3152 // Extending the content area into the title bar works by resizing the
michael@0 3153 // mainChildView so that it covers the titlebar.
michael@0 3154 - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState
michael@0 3155 {
michael@0 3156 BOOL stateChanged = ([self drawsContentsIntoWindowFrame] != aState);
michael@0 3157 [super setDrawsContentsIntoWindowFrame:aState];
michael@0 3158 if (stateChanged && [[self delegate] isKindOfClass:[WindowDelegate class]]) {
michael@0 3159 // Here we extend / shrink our mainChildView. We do that by firing a resize
michael@0 3160 // event which will cause the ChildView to be resized to the rect returned
michael@0 3161 // by nsCocoaWindow::GetClientBounds. GetClientBounds bases its return
michael@0 3162 // value on what we return from drawsContentsIntoWindowFrame.
michael@0 3163 WindowDelegate *windowDelegate = (WindowDelegate *)[self delegate];
michael@0 3164 nsCocoaWindow *geckoWindow = [windowDelegate geckoWidget];
michael@0 3165 if (geckoWindow) {
michael@0 3166 // Re-layout our contents.
michael@0 3167 geckoWindow->ReportSizeEvent();
michael@0 3168 }
michael@0 3169
michael@0 3170 // Resizing the content area causes a reflow which would send a synthesized
michael@0 3171 // mousemove event to the old mouse position relative to the top left
michael@0 3172 // corner of the content area. But the mouse has shifted relative to the
michael@0 3173 // content area, so that event would have wrong position information. So
michael@0 3174 // we'll send a mouse move event with the correct new position.
michael@0 3175 ChildViewMouseTracker::ResendLastMouseMoveEvent();
michael@0 3176 }
michael@0 3177 }
michael@0 3178
michael@0 3179 - (void)setWantsTitleDrawn:(BOOL)aDrawTitle
michael@0 3180 {
michael@0 3181 [super setWantsTitleDrawn:aDrawTitle];
michael@0 3182 [self setTitlebarNeedsDisplayInRect:[self titlebarRect]];
michael@0 3183 }
michael@0 3184
michael@0 3185 - (void)placeWindowButtons:(NSRect)aRect
michael@0 3186 {
michael@0 3187 if (!NSEqualRects(mWindowButtonsRect, aRect)) {
michael@0 3188 mWindowButtonsRect = aRect;
michael@0 3189 [self reflowTitlebarElements];
michael@0 3190 }
michael@0 3191 }
michael@0 3192
michael@0 3193 - (NSPoint)windowButtonsPositionWithDefaultPosition:(NSPoint)aDefaultPosition
michael@0 3194 {
michael@0 3195 if ([self drawsContentsIntoWindowFrame] && !NSIsEmptyRect(mWindowButtonsRect)) {
michael@0 3196 return NSMakePoint(std::max(mWindowButtonsRect.origin.x, aDefaultPosition.x),
michael@0 3197 std::min(mWindowButtonsRect.origin.y, aDefaultPosition.y));
michael@0 3198 }
michael@0 3199 return aDefaultPosition;
michael@0 3200 }
michael@0 3201
michael@0 3202 - (void)placeFullScreenButton:(NSRect)aRect
michael@0 3203 {
michael@0 3204 if (!NSEqualRects(mFullScreenButtonRect, aRect)) {
michael@0 3205 mFullScreenButtonRect = aRect;
michael@0 3206 [self reflowTitlebarElements];
michael@0 3207 }
michael@0 3208 }
michael@0 3209
michael@0 3210 - (NSPoint)fullScreenButtonPositionWithDefaultPosition:(NSPoint)aDefaultPosition;
michael@0 3211 {
michael@0 3212 if ([self drawsContentsIntoWindowFrame] && !NSIsEmptyRect(mFullScreenButtonRect)) {
michael@0 3213 return NSMakePoint(std::min(mFullScreenButtonRect.origin.x, aDefaultPosition.x),
michael@0 3214 std::min(mFullScreenButtonRect.origin.y, aDefaultPosition.y));
michael@0 3215 }
michael@0 3216 return aDefaultPosition;
michael@0 3217 }
michael@0 3218
michael@0 3219 // Returning YES here makes the setShowsToolbarButton method work even though
michael@0 3220 // the window doesn't contain an NSToolbar.
michael@0 3221 - (BOOL)_hasToolbar
michael@0 3222 {
michael@0 3223 return YES;
michael@0 3224 }
michael@0 3225
michael@0 3226 // Dispatch a toolbar pill button clicked message to Gecko.
michael@0 3227 - (void)_toolbarPillButtonClicked:(id)sender
michael@0 3228 {
michael@0 3229 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 3230
michael@0 3231 RollUpPopups();
michael@0 3232
michael@0 3233 if ([[self delegate] isKindOfClass:[WindowDelegate class]]) {
michael@0 3234 WindowDelegate *windowDelegate = (WindowDelegate *)[self delegate];
michael@0 3235 nsCocoaWindow *geckoWindow = [windowDelegate geckoWidget];
michael@0 3236 if (!geckoWindow)
michael@0 3237 return;
michael@0 3238
michael@0 3239 nsIWidgetListener* listener = geckoWindow->GetWidgetListener();
michael@0 3240 if (listener)
michael@0 3241 listener->OSToolbarButtonPressed();
michael@0 3242 }
michael@0 3243
michael@0 3244 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 3245 }
michael@0 3246
michael@0 3247 // Retain and release "self" to avoid crashes when our widget (and its native
michael@0 3248 // window) is closed as a result of processing a key equivalent (e.g.
michael@0 3249 // Command+w or Command+q). This workaround is only needed for a window
michael@0 3250 // that can become key.
michael@0 3251 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent
michael@0 3252 {
michael@0 3253 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 3254
michael@0 3255 NSWindow *nativeWindow = [self retain];
michael@0 3256 BOOL retval = [super performKeyEquivalent:theEvent];
michael@0 3257 [nativeWindow release];
michael@0 3258 return retval;
michael@0 3259
michael@0 3260 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
michael@0 3261 }
michael@0 3262
michael@0 3263 - (void)sendEvent:(NSEvent *)anEvent
michael@0 3264 {
michael@0 3265 NSEventType type = [anEvent type];
michael@0 3266
michael@0 3267 switch (type) {
michael@0 3268 case NSScrollWheel:
michael@0 3269 case NSLeftMouseDown:
michael@0 3270 case NSLeftMouseUp:
michael@0 3271 case NSRightMouseDown:
michael@0 3272 case NSRightMouseUp:
michael@0 3273 case NSOtherMouseDown:
michael@0 3274 case NSOtherMouseUp:
michael@0 3275 case NSMouseMoved:
michael@0 3276 case NSLeftMouseDragged:
michael@0 3277 case NSRightMouseDragged:
michael@0 3278 case NSOtherMouseDragged:
michael@0 3279 {
michael@0 3280 // Drop all mouse events if a modal window has appeared above us.
michael@0 3281 // This helps make us behave as if the OS were running a "real" modal
michael@0 3282 // event loop.
michael@0 3283 id delegate = [self delegate];
michael@0 3284 if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
michael@0 3285 nsCocoaWindow *widget = [(WindowDelegate *)delegate geckoWidget];
michael@0 3286 if (widget) {
michael@0 3287 if (type == NSMouseMoved) {
michael@0 3288 [[self mainChildView] updateWindowDraggableStateOnMouseMove:anEvent];
michael@0 3289 }
michael@0 3290 if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window))
michael@0 3291 return;
michael@0 3292 if (widget->HasModalDescendents())
michael@0 3293 return;
michael@0 3294 }
michael@0 3295 }
michael@0 3296 break;
michael@0 3297 }
michael@0 3298 default:
michael@0 3299 break;
michael@0 3300 }
michael@0 3301
michael@0 3302 [super sendEvent:anEvent];
michael@0 3303 }
michael@0 3304
michael@0 3305 @end
michael@0 3306
michael@0 3307 // Custom NSColor subclass where most of the work takes place for drawing in
michael@0 3308 // the titlebar area. Not used in drawsContentsIntoWindowFrame mode.
michael@0 3309 @implementation TitlebarAndBackgroundColor
michael@0 3310
michael@0 3311 - (id)initWithWindow:(ToolbarWindow*)aWindow
michael@0 3312 {
michael@0 3313 if ((self = [super init])) {
michael@0 3314 mWindow = aWindow; // weak ref to avoid a cycle
michael@0 3315 }
michael@0 3316 return self;
michael@0 3317 }
michael@0 3318
michael@0 3319 static void
michael@0 3320 DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
michael@0 3321 CGFloat aUnifiedToolbarHeight, BOOL aIsMain)
michael@0 3322 {
michael@0 3323 if (aTitlebarRect.size.width * aTitlebarRect.size.height > CUIDRAW_MAX_AREA) {
michael@0 3324 return;
michael@0 3325 }
michael@0 3326
michael@0 3327 CUIDraw([NSWindow coreUIRenderer], aTitlebarRect, aContext,
michael@0 3328 (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
michael@0 3329 @"kCUIWidgetWindowFrame", @"widget",
michael@0 3330 @"regularwin", @"windowtype",
michael@0 3331 (aIsMain ? @"normal" : @"inactive"), @"state",
michael@0 3332 [NSNumber numberWithDouble:aUnifiedToolbarHeight], @"kCUIWindowFrameUnifiedTitleBarHeightKey",
michael@0 3333 [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawTitleSeparatorKey",
michael@0 3334 nil],
michael@0 3335 nil);
michael@0 3336
michael@0 3337 if (nsCocoaFeatures::OnLionOrLater()) {
michael@0 3338 // On Lion the call to CUIDraw doesn't draw the top pixel strip at some
michael@0 3339 // window widths. We don't want to have a flickering transparent line, so
michael@0 3340 // we overdraw it.
michael@0 3341 CGContextSetRGBFillColor(aContext, 0.95, 0.95, 0.95, 1);
michael@0 3342 CGContextFillRect(aContext, CGRectMake(0, CGRectGetMaxY(aTitlebarRect) - 1,
michael@0 3343 aTitlebarRect.size.width, 1));
michael@0 3344 }
michael@0 3345 }
michael@0 3346
michael@0 3347 // Pattern draw callback for standard titlebar gradients and solid titlebar colors
michael@0 3348 static void
michael@0 3349 TitlebarDrawCallback(void* aInfo, CGContextRef aContext)
michael@0 3350 {
michael@0 3351 ToolbarWindow *window = (ToolbarWindow*)aInfo;
michael@0 3352 if (![window drawsContentsIntoWindowFrame]) {
michael@0 3353 NSRect titlebarRect = [window titlebarRect];
michael@0 3354 BOOL isMain = [window isMainWindow];
michael@0 3355 NSColor *titlebarColor = [window titlebarColorForActiveWindow:isMain];
michael@0 3356 if (!titlebarColor) {
michael@0 3357 // If the titlebar color is nil, draw the default titlebar shading.
michael@0 3358 DrawNativeTitlebar(aContext, NSRectToCGRect(titlebarRect),
michael@0 3359 [window unifiedToolbarHeight], isMain);
michael@0 3360 } else {
michael@0 3361 // If the titlebar color is not nil, just set and draw it normally.
michael@0 3362 [NSGraphicsContext saveGraphicsState];
michael@0 3363 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:aContext flipped:NO]];
michael@0 3364 [titlebarColor set];
michael@0 3365 NSRectFill(titlebarRect);
michael@0 3366 [NSGraphicsContext restoreGraphicsState];
michael@0 3367 }
michael@0 3368 }
michael@0 3369 }
michael@0 3370
michael@0 3371 - (void)setFill
michael@0 3372 {
michael@0 3373 float patternWidth = [mWindow frame].size.width;
michael@0 3374
michael@0 3375 CGPatternCallbacks callbacks = {0, &TitlebarDrawCallback, NULL};
michael@0 3376 CGPatternRef pattern = CGPatternCreate(mWindow, CGRectMake(0.0f, 0.0f, patternWidth, [mWindow frame].size.height),
michael@0 3377 CGAffineTransformIdentity, patternWidth, [mWindow frame].size.height,
michael@0 3378 kCGPatternTilingConstantSpacing, true, &callbacks);
michael@0 3379
michael@0 3380 // Set the pattern as the fill, which is what we were asked to do. All our
michael@0 3381 // drawing will take place in the patternDraw callback.
michael@0 3382 CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
michael@0 3383 CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
michael@0 3384 CGContextSetFillColorSpace(context, patternSpace);
michael@0 3385 CGColorSpaceRelease(patternSpace);
michael@0 3386 CGFloat component = 1.0f;
michael@0 3387 CGContextSetFillPattern(context, pattern, &component);
michael@0 3388 CGPatternRelease(pattern);
michael@0 3389 }
michael@0 3390
michael@0 3391 - (void)set
michael@0 3392 {
michael@0 3393 [self setFill];
michael@0 3394 }
michael@0 3395
michael@0 3396 - (NSString*)colorSpaceName
michael@0 3397 {
michael@0 3398 return NSDeviceRGBColorSpace;
michael@0 3399 }
michael@0 3400
michael@0 3401 @end
michael@0 3402
michael@0 3403 @implementation PopupWindow
michael@0 3404
michael@0 3405 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
michael@0 3406 backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation
michael@0 3407 {
michael@0 3408 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
michael@0 3409
michael@0 3410 mIsContextMenu = false;
michael@0 3411 return [super initWithContentRect:contentRect styleMask:styleMask
michael@0 3412 backing:bufferingType defer:deferCreation];
michael@0 3413
michael@0 3414 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
michael@0 3415 }
michael@0 3416
michael@0 3417 - (BOOL)isContextMenu
michael@0 3418 {
michael@0 3419 return mIsContextMenu;
michael@0 3420 }
michael@0 3421
michael@0 3422 - (void)setIsContextMenu:(BOOL)flag
michael@0 3423 {
michael@0 3424 mIsContextMenu = flag;
michael@0 3425 }
michael@0 3426
michael@0 3427 - (BOOL)canBecomeMainWindow
michael@0 3428 {
michael@0 3429 // This is overriden because the default is 'yes' when a titlebar is present.
michael@0 3430 return NO;
michael@0 3431 }
michael@0 3432
michael@0 3433 @end
michael@0 3434
michael@0 3435 // According to Apple's docs on [NSWindow canBecomeKeyWindow] and [NSWindow
michael@0 3436 // canBecomeMainWindow], windows without a title bar or resize bar can't (by
michael@0 3437 // default) become key or main. But if a window can't become key, it can't
michael@0 3438 // accept keyboard input (bmo bug 393250). And it should also be possible for
michael@0 3439 // an otherwise "ordinary" window to become main. We need to override these
michael@0 3440 // two methods to make this happen.
michael@0 3441 @implementation BorderlessWindow
michael@0 3442
michael@0 3443 - (BOOL)canBecomeKeyWindow
michael@0 3444 {
michael@0 3445 return YES;
michael@0 3446 }
michael@0 3447
michael@0 3448 - (void)sendEvent:(NSEvent *)anEvent
michael@0 3449 {
michael@0 3450 NSEventType type = [anEvent type];
michael@0 3451
michael@0 3452 switch (type) {
michael@0 3453 case NSScrollWheel:
michael@0 3454 case NSLeftMouseDown:
michael@0 3455 case NSLeftMouseUp:
michael@0 3456 case NSRightMouseDown:
michael@0 3457 case NSRightMouseUp:
michael@0 3458 case NSOtherMouseDown:
michael@0 3459 case NSOtherMouseUp:
michael@0 3460 case NSMouseMoved:
michael@0 3461 case NSLeftMouseDragged:
michael@0 3462 case NSRightMouseDragged:
michael@0 3463 case NSOtherMouseDragged:
michael@0 3464 {
michael@0 3465 // Drop all mouse events if a modal window has appeared above us.
michael@0 3466 // This helps make us behave as if the OS were running a "real" modal
michael@0 3467 // event loop.
michael@0 3468 id delegate = [self delegate];
michael@0 3469 if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
michael@0 3470 nsCocoaWindow *widget = [(WindowDelegate *)delegate geckoWidget];
michael@0 3471 if (widget) {
michael@0 3472 if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window))
michael@0 3473 return;
michael@0 3474 if (widget->HasModalDescendents())
michael@0 3475 return;
michael@0 3476 }
michael@0 3477 }
michael@0 3478 break;
michael@0 3479 }
michael@0 3480 default:
michael@0 3481 break;
michael@0 3482 }
michael@0 3483
michael@0 3484 [super sendEvent:anEvent];
michael@0 3485 }
michael@0 3486
michael@0 3487 // Apple's doc on this method says that the NSWindow class's default is not to
michael@0 3488 // become main if the window isn't "visible" -- so we should replicate that
michael@0 3489 // behavior here. As best I can tell, the [NSWindow isVisible] method is an
michael@0 3490 // accurate test of what Apple means by "visibility".
michael@0 3491 - (BOOL)canBecomeMainWindow
michael@0 3492 {
michael@0 3493 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 3494
michael@0 3495 if (![self isVisible])
michael@0 3496 return NO;
michael@0 3497 return YES;
michael@0 3498
michael@0 3499 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
michael@0 3500 }
michael@0 3501
michael@0 3502 // Retain and release "self" to avoid crashes when our widget (and its native
michael@0 3503 // window) is closed as a result of processing a key equivalent (e.g.
michael@0 3504 // Command+w or Command+q). This workaround is only needed for a window
michael@0 3505 // that can become key.
michael@0 3506 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent
michael@0 3507 {
michael@0 3508 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 3509
michael@0 3510 NSWindow *nativeWindow = [self retain];
michael@0 3511 BOOL retval = [super performKeyEquivalent:theEvent];
michael@0 3512 [nativeWindow release];
michael@0 3513 return retval;
michael@0 3514
michael@0 3515 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
michael@0 3516 }
michael@0 3517
michael@0 3518 @end

mercurial