Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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 |