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