|
1 /* -*- Mode: C++; tab-width: 20; 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 "gfxImageSurface.h" |
|
7 #include "gfxPlatform.h" |
|
8 #include "gfxUtils.h" |
|
9 #include "nsCocoaUtils.h" |
|
10 #include "nsChildView.h" |
|
11 #include "nsMenuBarX.h" |
|
12 #include "nsCocoaWindow.h" |
|
13 #include "nsCOMPtr.h" |
|
14 #include "nsIInterfaceRequestorUtils.h" |
|
15 #include "nsIAppShellService.h" |
|
16 #include "nsIXULWindow.h" |
|
17 #include "nsIBaseWindow.h" |
|
18 #include "nsIServiceManager.h" |
|
19 #include "nsMenuUtilsX.h" |
|
20 #include "nsToolkit.h" |
|
21 #include "nsCRT.h" |
|
22 #include "mozilla/gfx/2D.h" |
|
23 #include "mozilla/MiscEvents.h" |
|
24 #include "mozilla/Preferences.h" |
|
25 #include "mozilla/TextEvents.h" |
|
26 |
|
27 using namespace mozilla; |
|
28 using namespace mozilla::widget; |
|
29 |
|
30 using mozilla::gfx::BackendType; |
|
31 using mozilla::gfx::DataSourceSurface; |
|
32 using mozilla::gfx::DrawTarget; |
|
33 using mozilla::gfx::Factory; |
|
34 using mozilla::gfx::IntPoint; |
|
35 using mozilla::gfx::IntRect; |
|
36 using mozilla::gfx::IntSize; |
|
37 using mozilla::gfx::SurfaceFormat; |
|
38 using mozilla::gfx::SourceSurface; |
|
39 |
|
40 static float |
|
41 MenuBarScreenHeight() |
|
42 { |
|
43 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; |
|
44 |
|
45 NSArray* allScreens = [NSScreen screens]; |
|
46 if ([allScreens count]) { |
|
47 return [[allScreens objectAtIndex:0] frame].size.height; |
|
48 } |
|
49 |
|
50 return 0.0; |
|
51 |
|
52 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0.0); |
|
53 } |
|
54 |
|
55 float |
|
56 nsCocoaUtils::FlippedScreenY(float y) |
|
57 { |
|
58 return MenuBarScreenHeight() - y; |
|
59 } |
|
60 |
|
61 NSRect nsCocoaUtils::GeckoRectToCocoaRect(const nsIntRect &geckoRect) |
|
62 { |
|
63 // We only need to change the Y coordinate by starting with the primary screen |
|
64 // height and subtracting the gecko Y coordinate of the bottom of the rect. |
|
65 return NSMakeRect(geckoRect.x, |
|
66 MenuBarScreenHeight() - geckoRect.YMost(), |
|
67 geckoRect.width, |
|
68 geckoRect.height); |
|
69 } |
|
70 |
|
71 NSRect nsCocoaUtils::GeckoRectToCocoaRectDevPix(const nsIntRect &aGeckoRect, |
|
72 CGFloat aBackingScale) |
|
73 { |
|
74 return NSMakeRect(aGeckoRect.x / aBackingScale, |
|
75 MenuBarScreenHeight() - aGeckoRect.YMost() / aBackingScale, |
|
76 aGeckoRect.width / aBackingScale, |
|
77 aGeckoRect.height / aBackingScale); |
|
78 } |
|
79 |
|
80 nsIntRect nsCocoaUtils::CocoaRectToGeckoRect(const NSRect &cocoaRect) |
|
81 { |
|
82 // We only need to change the Y coordinate by starting with the primary screen |
|
83 // height and subtracting both the cocoa y origin and the height of the |
|
84 // cocoa rect. |
|
85 nsIntRect rect; |
|
86 rect.x = NSToIntRound(cocoaRect.origin.x); |
|
87 rect.y = NSToIntRound(FlippedScreenY(cocoaRect.origin.y + cocoaRect.size.height)); |
|
88 rect.width = NSToIntRound(cocoaRect.origin.x + cocoaRect.size.width) - rect.x; |
|
89 rect.height = NSToIntRound(FlippedScreenY(cocoaRect.origin.y)) - rect.y; |
|
90 return rect; |
|
91 } |
|
92 |
|
93 nsIntRect nsCocoaUtils::CocoaRectToGeckoRectDevPix(const NSRect &aCocoaRect, |
|
94 CGFloat aBackingScale) |
|
95 { |
|
96 nsIntRect rect; |
|
97 rect.x = NSToIntRound(aCocoaRect.origin.x * aBackingScale); |
|
98 rect.y = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y + aCocoaRect.size.height) * aBackingScale); |
|
99 rect.width = NSToIntRound((aCocoaRect.origin.x + aCocoaRect.size.width) * aBackingScale) - rect.x; |
|
100 rect.height = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y) * aBackingScale) - rect.y; |
|
101 return rect; |
|
102 } |
|
103 |
|
104 NSPoint nsCocoaUtils::ScreenLocationForEvent(NSEvent* anEvent) |
|
105 { |
|
106 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; |
|
107 |
|
108 // Don't trust mouse locations of mouse move events, see bug 443178. |
|
109 if (!anEvent || [anEvent type] == NSMouseMoved) |
|
110 return [NSEvent mouseLocation]; |
|
111 |
|
112 // Pin momentum scroll events to the location of the last user-controlled |
|
113 // scroll event. |
|
114 if (IsMomentumScrollEvent(anEvent)) |
|
115 return ChildViewMouseTracker::sLastScrollEventScreenLocation; |
|
116 |
|
117 return [[anEvent window] convertBaseToScreen:[anEvent locationInWindow]]; |
|
118 |
|
119 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakePoint(0.0, 0.0)); |
|
120 } |
|
121 |
|
122 BOOL nsCocoaUtils::IsEventOverWindow(NSEvent* anEvent, NSWindow* aWindow) |
|
123 { |
|
124 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; |
|
125 |
|
126 return NSPointInRect(ScreenLocationForEvent(anEvent), [aWindow frame]); |
|
127 |
|
128 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); |
|
129 } |
|
130 |
|
131 NSPoint nsCocoaUtils::EventLocationForWindow(NSEvent* anEvent, NSWindow* aWindow) |
|
132 { |
|
133 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; |
|
134 |
|
135 return [aWindow convertScreenToBase:ScreenLocationForEvent(anEvent)]; |
|
136 |
|
137 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakePoint(0.0, 0.0)); |
|
138 } |
|
139 |
|
140 BOOL nsCocoaUtils::IsMomentumScrollEvent(NSEvent* aEvent) |
|
141 { |
|
142 if ([aEvent type] != NSScrollWheel) |
|
143 return NO; |
|
144 |
|
145 if ([aEvent respondsToSelector:@selector(momentumPhase)]) |
|
146 return ([aEvent momentumPhase] & NSEventPhaseChanged) != 0; |
|
147 |
|
148 if ([aEvent respondsToSelector:@selector(_scrollPhase)]) |
|
149 return [aEvent _scrollPhase] != 0; |
|
150 |
|
151 return NO; |
|
152 } |
|
153 |
|
154 void nsCocoaUtils::HideOSChromeOnScreen(bool aShouldHide, NSScreen* aScreen) |
|
155 { |
|
156 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
157 |
|
158 // Keep track of how many hiding requests have been made, so that they can |
|
159 // be nested. |
|
160 static int sMenuBarHiddenCount = 0, sDockHiddenCount = 0; |
|
161 |
|
162 // Always hide the Dock, since it's not necessarily on the primary screen. |
|
163 sDockHiddenCount += aShouldHide ? 1 : -1; |
|
164 NS_ASSERTION(sMenuBarHiddenCount >= 0, "Unbalanced HideMenuAndDockForWindow calls"); |
|
165 |
|
166 // Only hide the menu bar if the window is on the same screen. |
|
167 // The menu bar is always on the first screen in the screen list. |
|
168 if (aScreen == [[NSScreen screens] objectAtIndex:0]) { |
|
169 sMenuBarHiddenCount += aShouldHide ? 1 : -1; |
|
170 NS_ASSERTION(sDockHiddenCount >= 0, "Unbalanced HideMenuAndDockForWindow calls"); |
|
171 } |
|
172 |
|
173 // TODO This should be upgraded to use [NSApplication setPresentationOptions:] |
|
174 // when support for 10.5 is dropped. |
|
175 if (sMenuBarHiddenCount > 0) { |
|
176 ::SetSystemUIMode(kUIModeAllHidden, 0); |
|
177 } else if (sDockHiddenCount > 0) { |
|
178 ::SetSystemUIMode(kUIModeContentHidden, 0); |
|
179 } else { |
|
180 ::SetSystemUIMode(kUIModeNormal, 0); |
|
181 } |
|
182 |
|
183 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
184 } |
|
185 |
|
186 |
|
187 #define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1" |
|
188 nsIWidget* nsCocoaUtils::GetHiddenWindowWidget() |
|
189 { |
|
190 nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); |
|
191 if (!appShell) { |
|
192 NS_WARNING("Couldn't get AppShellService in order to get hidden window ref"); |
|
193 return nullptr; |
|
194 } |
|
195 |
|
196 nsCOMPtr<nsIXULWindow> hiddenWindow; |
|
197 appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow)); |
|
198 if (!hiddenWindow) { |
|
199 // Don't warn, this happens during shutdown, bug 358607. |
|
200 return nullptr; |
|
201 } |
|
202 |
|
203 nsCOMPtr<nsIBaseWindow> baseHiddenWindow; |
|
204 baseHiddenWindow = do_GetInterface(hiddenWindow); |
|
205 if (!baseHiddenWindow) { |
|
206 NS_WARNING("Couldn't get nsIBaseWindow from hidden window (nsIXULWindow)"); |
|
207 return nullptr; |
|
208 } |
|
209 |
|
210 nsCOMPtr<nsIWidget> hiddenWindowWidget; |
|
211 if (NS_FAILED(baseHiddenWindow->GetMainWidget(getter_AddRefs(hiddenWindowWidget)))) { |
|
212 NS_WARNING("Couldn't get nsIWidget from hidden window (nsIBaseWindow)"); |
|
213 return nullptr; |
|
214 } |
|
215 |
|
216 return hiddenWindowWidget; |
|
217 } |
|
218 |
|
219 void nsCocoaUtils::PrepareForNativeAppModalDialog() |
|
220 { |
|
221 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
222 |
|
223 // Don't do anything if this is embedding. We'll assume that if there is no hidden |
|
224 // window we shouldn't do anything, and that should cover the embedding case. |
|
225 nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar(); |
|
226 if (!hiddenWindowMenuBar) |
|
227 return; |
|
228 |
|
229 // First put up the hidden window menu bar so that app menu event handling is correct. |
|
230 hiddenWindowMenuBar->Paint(); |
|
231 |
|
232 NSMenu* mainMenu = [NSApp mainMenu]; |
|
233 NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!"); |
|
234 |
|
235 // Create new menu bar for use with modal dialog |
|
236 NSMenu* newMenuBar = [[NSMenu alloc] initWithTitle:@""]; |
|
237 |
|
238 // Swap in our app menu. Note that the event target is whatever window is up when |
|
239 // the app modal dialog goes up. |
|
240 NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain]; |
|
241 [mainMenu removeItemAtIndex:0]; |
|
242 [newMenuBar insertItem:firstMenuItem atIndex:0]; |
|
243 [firstMenuItem release]; |
|
244 |
|
245 // Add standard edit menu |
|
246 [newMenuBar addItem:nsMenuUtilsX::GetStandardEditMenuItem()]; |
|
247 |
|
248 // Show the new menu bar |
|
249 [NSApp setMainMenu:newMenuBar]; |
|
250 [newMenuBar release]; |
|
251 |
|
252 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
253 } |
|
254 |
|
255 void nsCocoaUtils::CleanUpAfterNativeAppModalDialog() |
|
256 { |
|
257 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
258 |
|
259 // Don't do anything if this is embedding. We'll assume that if there is no hidden |
|
260 // window we shouldn't do anything, and that should cover the embedding case. |
|
261 nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar(); |
|
262 if (!hiddenWindowMenuBar) |
|
263 return; |
|
264 |
|
265 NSWindow* mainWindow = [NSApp mainWindow]; |
|
266 if (!mainWindow) |
|
267 hiddenWindowMenuBar->Paint(); |
|
268 else |
|
269 [WindowDelegate paintMenubarForWindow:mainWindow]; |
|
270 |
|
271 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
272 } |
|
273 |
|
274 void data_ss_release_callback(void *aDataSourceSurface, |
|
275 const void *data, |
|
276 size_t size) |
|
277 { |
|
278 if (aDataSourceSurface) { |
|
279 static_cast<DataSourceSurface*>(aDataSourceSurface)->Unmap(); |
|
280 static_cast<DataSourceSurface*>(aDataSourceSurface)->Release(); |
|
281 } |
|
282 } |
|
283 |
|
284 nsresult nsCocoaUtils::CreateCGImageFromSurface(SourceSurface* aSurface, |
|
285 CGImageRef* aResult) |
|
286 { |
|
287 RefPtr<DataSourceSurface> dataSurface; |
|
288 |
|
289 if (aSurface->GetFormat() == SurfaceFormat::B8G8R8A8) { |
|
290 dataSurface = aSurface->GetDataSurface(); |
|
291 } else { |
|
292 // CGImageCreate only supports 16- and 32-bit bit-depth |
|
293 // Convert format to SurfaceFormat::B8G8R8A8 |
|
294 dataSurface = gfxUtils:: |
|
295 CopySurfaceToDataSourceSurfaceWithFormat(aSurface, |
|
296 SurfaceFormat::B8G8R8A8); |
|
297 } |
|
298 |
|
299 NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); |
|
300 |
|
301 int32_t width = dataSurface->GetSize().width; |
|
302 int32_t height = dataSurface->GetSize().height; |
|
303 if (height < 1 || width < 1) { |
|
304 return NS_ERROR_FAILURE; |
|
305 } |
|
306 |
|
307 DataSourceSurface::MappedSurface map; |
|
308 if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) { |
|
309 return NS_ERROR_FAILURE; |
|
310 } |
|
311 // The Unmap() call happens in data_ss_release_callback |
|
312 |
|
313 // Create a CGImageRef with the bits from the image, taking into account |
|
314 // the alpha ordering and endianness of the machine so we don't have to |
|
315 // touch the bits ourselves. |
|
316 CGDataProviderRef dataProvider = ::CGDataProviderCreateWithData(dataSurface.forget().drop(), |
|
317 map.mData, |
|
318 map.mStride * height, |
|
319 data_ss_release_callback); |
|
320 CGColorSpaceRef colorSpace = ::CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); |
|
321 *aResult = ::CGImageCreate(width, |
|
322 height, |
|
323 8, |
|
324 32, |
|
325 map.mStride, |
|
326 colorSpace, |
|
327 kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst, |
|
328 dataProvider, |
|
329 NULL, |
|
330 0, |
|
331 kCGRenderingIntentDefault); |
|
332 ::CGColorSpaceRelease(colorSpace); |
|
333 ::CGDataProviderRelease(dataProvider); |
|
334 return *aResult ? NS_OK : NS_ERROR_FAILURE; |
|
335 } |
|
336 |
|
337 nsresult nsCocoaUtils::CreateNSImageFromCGImage(CGImageRef aInputImage, NSImage **aResult) |
|
338 { |
|
339 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; |
|
340 |
|
341 // Be very careful when creating the NSImage that the backing NSImageRep is |
|
342 // exactly 1:1 with the input image. On a retina display, both [NSImage |
|
343 // lockFocus] and [NSImage initWithCGImage:size:] will create an image with a |
|
344 // 2x backing NSImageRep. This prevents NSCursor from recognizing a retina |
|
345 // cursor, which only occurs if pixelsWide and pixelsHigh are exactly 2x the |
|
346 // size of the NSImage. |
|
347 // |
|
348 // For example, if a 32x32 SVG cursor is rendered on a retina display, then |
|
349 // aInputImage will be 64x64. The resulting NSImage will be scaled back down |
|
350 // to 32x32 so it stays the correct size on the screen by changing its size |
|
351 // (resizing a NSImage only scales the image and doesn't resample the data). |
|
352 // If aInputImage is converted using [NSImage initWithCGImage:size:] then the |
|
353 // bitmap will be 128x128 and NSCursor won't recognize a retina cursor, since |
|
354 // it will expect a 64x64 bitmap. |
|
355 |
|
356 int32_t width = ::CGImageGetWidth(aInputImage); |
|
357 int32_t height = ::CGImageGetHeight(aInputImage); |
|
358 NSRect imageRect = ::NSMakeRect(0.0, 0.0, width, height); |
|
359 |
|
360 NSBitmapImageRep *offscreenRep = [[NSBitmapImageRep alloc] |
|
361 initWithBitmapDataPlanes:NULL |
|
362 pixelsWide:width |
|
363 pixelsHigh:height |
|
364 bitsPerSample:8 |
|
365 samplesPerPixel:4 |
|
366 hasAlpha:YES |
|
367 isPlanar:NO |
|
368 colorSpaceName:NSDeviceRGBColorSpace |
|
369 bitmapFormat:NSAlphaFirstBitmapFormat |
|
370 bytesPerRow:0 |
|
371 bitsPerPixel:0]; |
|
372 |
|
373 NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep]; |
|
374 [NSGraphicsContext saveGraphicsState]; |
|
375 [NSGraphicsContext setCurrentContext:context]; |
|
376 |
|
377 // Get the Quartz context and draw. |
|
378 CGContextRef imageContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; |
|
379 ::CGContextDrawImage(imageContext, *(CGRect*)&imageRect, aInputImage); |
|
380 |
|
381 [NSGraphicsContext restoreGraphicsState]; |
|
382 |
|
383 *aResult = [[NSImage alloc] initWithSize:NSMakeSize(width, height)]; |
|
384 [*aResult addRepresentation:offscreenRep]; |
|
385 [offscreenRep release]; |
|
386 return NS_OK; |
|
387 |
|
388 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; |
|
389 } |
|
390 |
|
391 nsresult nsCocoaUtils::CreateNSImageFromImageContainer(imgIContainer *aImage, uint32_t aWhichFrame, NSImage **aResult, CGFloat scaleFactor) |
|
392 { |
|
393 RefPtr<SourceSurface> surface; |
|
394 int32_t width = 0, height = 0; |
|
395 aImage->GetWidth(&width); |
|
396 aImage->GetHeight(&height); |
|
397 |
|
398 // Render a vector image at the correct resolution on a retina display |
|
399 if (aImage->GetType() == imgIContainer::TYPE_VECTOR && scaleFactor != 1.0f) { |
|
400 int scaledWidth = (int)ceilf(width * scaleFactor); |
|
401 int scaledHeight = (int)ceilf(height * scaleFactor); |
|
402 |
|
403 RefPtr<DrawTarget> drawTarget = gfxPlatform::GetPlatform()-> |
|
404 CreateOffscreenContentDrawTarget(IntSize(scaledWidth, scaledHeight), |
|
405 SurfaceFormat::B8G8R8A8); |
|
406 if (!drawTarget) { |
|
407 NS_ERROR("Failed to create DrawTarget"); |
|
408 return NS_ERROR_FAILURE; |
|
409 } |
|
410 |
|
411 nsRefPtr<gfxContext> context = new gfxContext(drawTarget); |
|
412 if (!context) { |
|
413 NS_ERROR("Failed to create gfxContext"); |
|
414 return NS_ERROR_FAILURE; |
|
415 } |
|
416 |
|
417 aImage->Draw(context, GraphicsFilter::FILTER_NEAREST, gfxMatrix(), |
|
418 gfxRect(0.0f, 0.0f, scaledWidth, scaledHeight), |
|
419 nsIntRect(0, 0, width, height), |
|
420 nsIntSize(scaledWidth, scaledHeight), |
|
421 nullptr, aWhichFrame, imgIContainer::FLAG_SYNC_DECODE); |
|
422 |
|
423 surface = drawTarget->Snapshot(); |
|
424 } else { |
|
425 surface = aImage->GetFrame(aWhichFrame, imgIContainer::FLAG_SYNC_DECODE); |
|
426 } |
|
427 |
|
428 NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); |
|
429 |
|
430 CGImageRef imageRef = NULL; |
|
431 nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef); |
|
432 if (NS_FAILED(rv) || !imageRef) { |
|
433 return NS_ERROR_FAILURE; |
|
434 } |
|
435 |
|
436 rv = nsCocoaUtils::CreateNSImageFromCGImage(imageRef, aResult); |
|
437 if (NS_FAILED(rv) || !aResult) { |
|
438 return NS_ERROR_FAILURE; |
|
439 } |
|
440 ::CGImageRelease(imageRef); |
|
441 |
|
442 // Ensure the image will be rendered the correct size on a retina display |
|
443 NSSize size = NSMakeSize(width, height); |
|
444 [*aResult setSize:size]; |
|
445 [[[*aResult representations] objectAtIndex:0] setSize:size]; |
|
446 return NS_OK; |
|
447 } |
|
448 |
|
449 // static |
|
450 void |
|
451 nsCocoaUtils::GetStringForNSString(const NSString *aSrc, nsAString& aDist) |
|
452 { |
|
453 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
454 |
|
455 if (!aSrc) { |
|
456 aDist.Truncate(); |
|
457 return; |
|
458 } |
|
459 |
|
460 aDist.SetLength([aSrc length]); |
|
461 [aSrc getCharacters: reinterpret_cast<unichar*>(aDist.BeginWriting())]; |
|
462 |
|
463 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
464 } |
|
465 |
|
466 // static |
|
467 NSString* |
|
468 nsCocoaUtils::ToNSString(const nsAString& aString) |
|
469 { |
|
470 if (aString.IsEmpty()) { |
|
471 return [NSString string]; |
|
472 } |
|
473 return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(aString.BeginReading()) |
|
474 length:aString.Length()]; |
|
475 } |
|
476 |
|
477 // static |
|
478 void |
|
479 nsCocoaUtils::GeckoRectToNSRect(const nsIntRect& aGeckoRect, |
|
480 NSRect& aOutCocoaRect) |
|
481 { |
|
482 aOutCocoaRect.origin.x = aGeckoRect.x; |
|
483 aOutCocoaRect.origin.y = aGeckoRect.y; |
|
484 aOutCocoaRect.size.width = aGeckoRect.width; |
|
485 aOutCocoaRect.size.height = aGeckoRect.height; |
|
486 } |
|
487 |
|
488 // static |
|
489 void |
|
490 nsCocoaUtils::NSRectToGeckoRect(const NSRect& aCocoaRect, |
|
491 nsIntRect& aOutGeckoRect) |
|
492 { |
|
493 aOutGeckoRect.x = NSToIntRound(aCocoaRect.origin.x); |
|
494 aOutGeckoRect.y = NSToIntRound(aCocoaRect.origin.y); |
|
495 aOutGeckoRect.width = NSToIntRound(aCocoaRect.origin.x + aCocoaRect.size.width) - aOutGeckoRect.x; |
|
496 aOutGeckoRect.height = NSToIntRound(aCocoaRect.origin.y + aCocoaRect.size.height) - aOutGeckoRect.y; |
|
497 } |
|
498 |
|
499 // static |
|
500 NSEvent* |
|
501 nsCocoaUtils::MakeNewCocoaEventWithType(NSEventType aEventType, NSEvent *aEvent) |
|
502 { |
|
503 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; |
|
504 |
|
505 NSEvent* newEvent = |
|
506 [NSEvent keyEventWithType:aEventType |
|
507 location:[aEvent locationInWindow] |
|
508 modifierFlags:[aEvent modifierFlags] |
|
509 timestamp:[aEvent timestamp] |
|
510 windowNumber:[aEvent windowNumber] |
|
511 context:[aEvent context] |
|
512 characters:[aEvent characters] |
|
513 charactersIgnoringModifiers:[aEvent charactersIgnoringModifiers] |
|
514 isARepeat:[aEvent isARepeat] |
|
515 keyCode:[aEvent keyCode]]; |
|
516 return newEvent; |
|
517 |
|
518 NS_OBJC_END_TRY_ABORT_BLOCK_NIL; |
|
519 } |
|
520 |
|
521 // static |
|
522 void |
|
523 nsCocoaUtils::InitNPCocoaEvent(NPCocoaEvent* aNPCocoaEvent) |
|
524 { |
|
525 memset(aNPCocoaEvent, 0, sizeof(NPCocoaEvent)); |
|
526 } |
|
527 |
|
528 // static |
|
529 void |
|
530 nsCocoaUtils::InitPluginEvent(WidgetPluginEvent &aPluginEvent, |
|
531 NPCocoaEvent &aCocoaEvent) |
|
532 { |
|
533 aPluginEvent.time = PR_IntervalNow(); |
|
534 aPluginEvent.pluginEvent = (void*)&aCocoaEvent; |
|
535 aPluginEvent.retargetToFocusedDocument = false; |
|
536 } |
|
537 |
|
538 // static |
|
539 void |
|
540 nsCocoaUtils::InitInputEvent(WidgetInputEvent& aInputEvent, |
|
541 NSEvent* aNativeEvent) |
|
542 { |
|
543 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
544 |
|
545 NSUInteger modifiers = |
|
546 aNativeEvent ? [aNativeEvent modifierFlags] : [NSEvent modifierFlags]; |
|
547 InitInputEvent(aInputEvent, modifiers); |
|
548 |
|
549 aInputEvent.time = PR_IntervalNow(); |
|
550 |
|
551 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
552 } |
|
553 |
|
554 // static |
|
555 void |
|
556 nsCocoaUtils::InitInputEvent(WidgetInputEvent& aInputEvent, |
|
557 NSUInteger aModifiers) |
|
558 { |
|
559 aInputEvent.modifiers = 0; |
|
560 if (aModifiers & NSShiftKeyMask) { |
|
561 aInputEvent.modifiers |= MODIFIER_SHIFT; |
|
562 } |
|
563 if (aModifiers & NSControlKeyMask) { |
|
564 aInputEvent.modifiers |= MODIFIER_CONTROL; |
|
565 } |
|
566 if (aModifiers & NSAlternateKeyMask) { |
|
567 aInputEvent.modifiers |= MODIFIER_ALT; |
|
568 // Mac's option key is similar to other platforms' AltGr key. |
|
569 // Let's set AltGr flag when option key is pressed for consistency with |
|
570 // other platforms. |
|
571 aInputEvent.modifiers |= MODIFIER_ALTGRAPH; |
|
572 } |
|
573 if (aModifiers & NSCommandKeyMask) { |
|
574 aInputEvent.modifiers |= MODIFIER_META; |
|
575 } |
|
576 |
|
577 if (aModifiers & NSAlphaShiftKeyMask) { |
|
578 aInputEvent.modifiers |= MODIFIER_CAPSLOCK; |
|
579 } |
|
580 // Mac doesn't have NumLock key. We can assume that NumLock is always locked |
|
581 // if user is using a keyboard which has numpad. Otherwise, if user is using |
|
582 // a keyboard which doesn't have numpad, e.g., MacBook's keyboard, we can |
|
583 // assume that NumLock is always unlocked. |
|
584 // Unfortunately, we cannot know whether current keyboard has numpad or not. |
|
585 // We should notify locked state only when keys in numpad are pressed. |
|
586 // By this, web applications may not be confused by unexpected numpad key's |
|
587 // key event with unlocked state. |
|
588 if (aModifiers & NSNumericPadKeyMask) { |
|
589 aInputEvent.modifiers |= MODIFIER_NUMLOCK; |
|
590 } |
|
591 |
|
592 // Be aware, NSFunctionKeyMask is included when arrow keys, home key or some |
|
593 // other keys are pressed. We cannot check whether 'fn' key is pressed or |
|
594 // not by the flag. |
|
595 |
|
596 } |
|
597 |
|
598 // static |
|
599 UInt32 |
|
600 nsCocoaUtils::ConvertToCarbonModifier(NSUInteger aCocoaModifier) |
|
601 { |
|
602 UInt32 carbonModifier = 0; |
|
603 if (aCocoaModifier & NSAlphaShiftKeyMask) { |
|
604 carbonModifier |= alphaLock; |
|
605 } |
|
606 if (aCocoaModifier & NSControlKeyMask) { |
|
607 carbonModifier |= controlKey; |
|
608 } |
|
609 if (aCocoaModifier & NSAlternateKeyMask) { |
|
610 carbonModifier |= optionKey; |
|
611 } |
|
612 if (aCocoaModifier & NSShiftKeyMask) { |
|
613 carbonModifier |= shiftKey; |
|
614 } |
|
615 if (aCocoaModifier & NSCommandKeyMask) { |
|
616 carbonModifier |= cmdKey; |
|
617 } |
|
618 if (aCocoaModifier & NSNumericPadKeyMask) { |
|
619 carbonModifier |= kEventKeyModifierNumLockMask; |
|
620 } |
|
621 if (aCocoaModifier & NSFunctionKeyMask) { |
|
622 carbonModifier |= kEventKeyModifierFnMask; |
|
623 } |
|
624 return carbonModifier; |
|
625 } |
|
626 |
|
627 // While HiDPI support is not 100% complete and tested, we'll have a pref |
|
628 // to allow it to be turned off in case of problems (or for testing purposes). |
|
629 |
|
630 // gfx.hidpi.enabled is an integer with the meaning: |
|
631 // <= 0 : HiDPI support is disabled |
|
632 // 1 : HiDPI enabled provided all screens have the same backing resolution |
|
633 // > 1 : HiDPI enabled even if there are a mixture of screen modes |
|
634 |
|
635 // All the following code is to be removed once HiDPI work is more complete. |
|
636 |
|
637 static bool sHiDPIEnabled = false; |
|
638 static bool sHiDPIPrefInitialized = false; |
|
639 |
|
640 // static |
|
641 bool |
|
642 nsCocoaUtils::HiDPIEnabled() |
|
643 { |
|
644 if (!sHiDPIPrefInitialized) { |
|
645 sHiDPIPrefInitialized = true; |
|
646 |
|
647 int prefSetting = Preferences::GetInt("gfx.hidpi.enabled", 1); |
|
648 if (prefSetting <= 0) { |
|
649 return false; |
|
650 } |
|
651 |
|
652 // prefSetting is at least 1, need to check attached screens... |
|
653 |
|
654 int scaleFactors = 0; // used as a bitset to track the screen types found |
|
655 NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator]; |
|
656 while (NSScreen *screen = [screenEnum nextObject]) { |
|
657 NSDictionary *desc = [screen deviceDescription]; |
|
658 if ([desc objectForKey:NSDeviceIsScreen] == nil) { |
|
659 continue; |
|
660 } |
|
661 CGFloat scale = |
|
662 [screen respondsToSelector:@selector(backingScaleFactor)] ? |
|
663 [screen backingScaleFactor] : 1.0; |
|
664 // Currently, we only care about differentiating "1.0" and "2.0", |
|
665 // so we set one of the two low bits to record which. |
|
666 if (scale > 1.0) { |
|
667 scaleFactors |= 2; |
|
668 } else { |
|
669 scaleFactors |= 1; |
|
670 } |
|
671 } |
|
672 |
|
673 // Now scaleFactors will be: |
|
674 // 0 if no screens (supporting backingScaleFactor) found |
|
675 // 1 if only lo-DPI screens |
|
676 // 2 if only hi-DPI screens |
|
677 // 3 if both lo- and hi-DPI screens |
|
678 // We'll enable HiDPI support if there's only a single screen type, |
|
679 // OR if the pref setting is explicitly greater than 1. |
|
680 sHiDPIEnabled = (scaleFactors <= 2) || (prefSetting > 1); |
|
681 } |
|
682 |
|
683 return sHiDPIEnabled; |
|
684 } |
|
685 |
|
686 void |
|
687 nsCocoaUtils::GetCommandsFromKeyEvent(NSEvent* aEvent, |
|
688 nsTArray<KeyBindingsCommand>& aCommands) |
|
689 { |
|
690 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
691 |
|
692 MOZ_ASSERT(aEvent); |
|
693 |
|
694 static NativeKeyBindingsRecorder* sNativeKeyBindingsRecorder; |
|
695 if (!sNativeKeyBindingsRecorder) { |
|
696 sNativeKeyBindingsRecorder = [NativeKeyBindingsRecorder new]; |
|
697 } |
|
698 |
|
699 [sNativeKeyBindingsRecorder startRecording:aCommands]; |
|
700 |
|
701 // This will trigger 0 - N calls to doCommandBySelector: and insertText: |
|
702 [sNativeKeyBindingsRecorder |
|
703 interpretKeyEvents:[NSArray arrayWithObject:aEvent]]; |
|
704 |
|
705 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
706 } |
|
707 |
|
708 @implementation NativeKeyBindingsRecorder |
|
709 |
|
710 - (void)startRecording:(nsTArray<KeyBindingsCommand>&)aCommands |
|
711 { |
|
712 mCommands = &aCommands; |
|
713 mCommands->Clear(); |
|
714 } |
|
715 |
|
716 - (void)doCommandBySelector:(SEL)aSelector |
|
717 { |
|
718 KeyBindingsCommand command = { |
|
719 aSelector, |
|
720 nil |
|
721 }; |
|
722 |
|
723 mCommands->AppendElement(command); |
|
724 } |
|
725 |
|
726 - (void)insertText:(id)aString |
|
727 { |
|
728 KeyBindingsCommand command = { |
|
729 @selector(insertText:), |
|
730 aString |
|
731 }; |
|
732 |
|
733 mCommands->AppendElement(command); |
|
734 } |
|
735 |
|
736 @end // NativeKeyBindingsRecorder |
|
737 |
|
738 struct KeyConversionData |
|
739 { |
|
740 const char* str; |
|
741 size_t strLength; |
|
742 uint32_t geckoKeyCode; |
|
743 uint32_t charCode; |
|
744 }; |
|
745 |
|
746 static const KeyConversionData gKeyConversions[] = { |
|
747 |
|
748 #define KEYCODE_ENTRY(aStr, aCode) \ |
|
749 {#aStr, sizeof(#aStr) - 1, NS_##aStr, aCode} |
|
750 |
|
751 // Some keycodes may have different name in nsIDOMKeyEvent from its key name. |
|
752 #define KEYCODE_ENTRY2(aStr, aNSName, aCode) \ |
|
753 {#aStr, sizeof(#aStr) - 1, NS_##aNSName, aCode} |
|
754 |
|
755 KEYCODE_ENTRY(VK_CANCEL, 0x001B), |
|
756 KEYCODE_ENTRY(VK_DELETE, NSDeleteFunctionKey), |
|
757 KEYCODE_ENTRY(VK_BACK, NSBackspaceCharacter), |
|
758 KEYCODE_ENTRY2(VK_BACK_SPACE, VK_BACK, NSBackspaceCharacter), |
|
759 KEYCODE_ENTRY(VK_TAB, NSTabCharacter), |
|
760 KEYCODE_ENTRY(VK_CLEAR, NSClearLineFunctionKey), |
|
761 KEYCODE_ENTRY(VK_RETURN, NSEnterCharacter), |
|
762 KEYCODE_ENTRY(VK_SHIFT, 0), |
|
763 KEYCODE_ENTRY(VK_CONTROL, 0), |
|
764 KEYCODE_ENTRY(VK_ALT, 0), |
|
765 KEYCODE_ENTRY(VK_PAUSE, NSPauseFunctionKey), |
|
766 KEYCODE_ENTRY(VK_CAPS_LOCK, 0), |
|
767 KEYCODE_ENTRY(VK_ESCAPE, 0), |
|
768 KEYCODE_ENTRY(VK_SPACE, ' '), |
|
769 KEYCODE_ENTRY(VK_PAGE_UP, NSPageUpFunctionKey), |
|
770 KEYCODE_ENTRY(VK_PAGE_DOWN, NSPageDownFunctionKey), |
|
771 KEYCODE_ENTRY(VK_END, NSEndFunctionKey), |
|
772 KEYCODE_ENTRY(VK_HOME, NSHomeFunctionKey), |
|
773 KEYCODE_ENTRY(VK_LEFT, NSLeftArrowFunctionKey), |
|
774 KEYCODE_ENTRY(VK_UP, NSUpArrowFunctionKey), |
|
775 KEYCODE_ENTRY(VK_RIGHT, NSRightArrowFunctionKey), |
|
776 KEYCODE_ENTRY(VK_DOWN, NSDownArrowFunctionKey), |
|
777 KEYCODE_ENTRY(VK_PRINTSCREEN, NSPrintScreenFunctionKey), |
|
778 KEYCODE_ENTRY(VK_INSERT, NSInsertFunctionKey), |
|
779 KEYCODE_ENTRY(VK_HELP, NSHelpFunctionKey), |
|
780 KEYCODE_ENTRY(VK_0, '0'), |
|
781 KEYCODE_ENTRY(VK_1, '1'), |
|
782 KEYCODE_ENTRY(VK_2, '2'), |
|
783 KEYCODE_ENTRY(VK_3, '3'), |
|
784 KEYCODE_ENTRY(VK_4, '4'), |
|
785 KEYCODE_ENTRY(VK_5, '5'), |
|
786 KEYCODE_ENTRY(VK_6, '6'), |
|
787 KEYCODE_ENTRY(VK_7, '7'), |
|
788 KEYCODE_ENTRY(VK_8, '8'), |
|
789 KEYCODE_ENTRY(VK_9, '9'), |
|
790 KEYCODE_ENTRY(VK_SEMICOLON, ':'), |
|
791 KEYCODE_ENTRY(VK_EQUALS, '='), |
|
792 KEYCODE_ENTRY(VK_A, 'A'), |
|
793 KEYCODE_ENTRY(VK_B, 'B'), |
|
794 KEYCODE_ENTRY(VK_C, 'C'), |
|
795 KEYCODE_ENTRY(VK_D, 'D'), |
|
796 KEYCODE_ENTRY(VK_E, 'E'), |
|
797 KEYCODE_ENTRY(VK_F, 'F'), |
|
798 KEYCODE_ENTRY(VK_G, 'G'), |
|
799 KEYCODE_ENTRY(VK_H, 'H'), |
|
800 KEYCODE_ENTRY(VK_I, 'I'), |
|
801 KEYCODE_ENTRY(VK_J, 'J'), |
|
802 KEYCODE_ENTRY(VK_K, 'K'), |
|
803 KEYCODE_ENTRY(VK_L, 'L'), |
|
804 KEYCODE_ENTRY(VK_M, 'M'), |
|
805 KEYCODE_ENTRY(VK_N, 'N'), |
|
806 KEYCODE_ENTRY(VK_O, 'O'), |
|
807 KEYCODE_ENTRY(VK_P, 'P'), |
|
808 KEYCODE_ENTRY(VK_Q, 'Q'), |
|
809 KEYCODE_ENTRY(VK_R, 'R'), |
|
810 KEYCODE_ENTRY(VK_S, 'S'), |
|
811 KEYCODE_ENTRY(VK_T, 'T'), |
|
812 KEYCODE_ENTRY(VK_U, 'U'), |
|
813 KEYCODE_ENTRY(VK_V, 'V'), |
|
814 KEYCODE_ENTRY(VK_W, 'W'), |
|
815 KEYCODE_ENTRY(VK_X, 'X'), |
|
816 KEYCODE_ENTRY(VK_Y, 'Y'), |
|
817 KEYCODE_ENTRY(VK_Z, 'Z'), |
|
818 KEYCODE_ENTRY(VK_CONTEXT_MENU, NSMenuFunctionKey), |
|
819 KEYCODE_ENTRY(VK_NUMPAD0, '0'), |
|
820 KEYCODE_ENTRY(VK_NUMPAD1, '1'), |
|
821 KEYCODE_ENTRY(VK_NUMPAD2, '2'), |
|
822 KEYCODE_ENTRY(VK_NUMPAD3, '3'), |
|
823 KEYCODE_ENTRY(VK_NUMPAD4, '4'), |
|
824 KEYCODE_ENTRY(VK_NUMPAD5, '5'), |
|
825 KEYCODE_ENTRY(VK_NUMPAD6, '6'), |
|
826 KEYCODE_ENTRY(VK_NUMPAD7, '7'), |
|
827 KEYCODE_ENTRY(VK_NUMPAD8, '8'), |
|
828 KEYCODE_ENTRY(VK_NUMPAD9, '9'), |
|
829 KEYCODE_ENTRY(VK_MULTIPLY, '*'), |
|
830 KEYCODE_ENTRY(VK_ADD, '+'), |
|
831 KEYCODE_ENTRY(VK_SEPARATOR, 0), |
|
832 KEYCODE_ENTRY(VK_SUBTRACT, '-'), |
|
833 KEYCODE_ENTRY(VK_DECIMAL, '.'), |
|
834 KEYCODE_ENTRY(VK_DIVIDE, '/'), |
|
835 KEYCODE_ENTRY(VK_F1, NSF1FunctionKey), |
|
836 KEYCODE_ENTRY(VK_F2, NSF2FunctionKey), |
|
837 KEYCODE_ENTRY(VK_F3, NSF3FunctionKey), |
|
838 KEYCODE_ENTRY(VK_F4, NSF4FunctionKey), |
|
839 KEYCODE_ENTRY(VK_F5, NSF5FunctionKey), |
|
840 KEYCODE_ENTRY(VK_F6, NSF6FunctionKey), |
|
841 KEYCODE_ENTRY(VK_F7, NSF7FunctionKey), |
|
842 KEYCODE_ENTRY(VK_F8, NSF8FunctionKey), |
|
843 KEYCODE_ENTRY(VK_F9, NSF9FunctionKey), |
|
844 KEYCODE_ENTRY(VK_F10, NSF10FunctionKey), |
|
845 KEYCODE_ENTRY(VK_F11, NSF11FunctionKey), |
|
846 KEYCODE_ENTRY(VK_F12, NSF12FunctionKey), |
|
847 KEYCODE_ENTRY(VK_F13, NSF13FunctionKey), |
|
848 KEYCODE_ENTRY(VK_F14, NSF14FunctionKey), |
|
849 KEYCODE_ENTRY(VK_F15, NSF15FunctionKey), |
|
850 KEYCODE_ENTRY(VK_F16, NSF16FunctionKey), |
|
851 KEYCODE_ENTRY(VK_F17, NSF17FunctionKey), |
|
852 KEYCODE_ENTRY(VK_F18, NSF18FunctionKey), |
|
853 KEYCODE_ENTRY(VK_F19, NSF19FunctionKey), |
|
854 KEYCODE_ENTRY(VK_F20, NSF20FunctionKey), |
|
855 KEYCODE_ENTRY(VK_F21, NSF21FunctionKey), |
|
856 KEYCODE_ENTRY(VK_F22, NSF22FunctionKey), |
|
857 KEYCODE_ENTRY(VK_F23, NSF23FunctionKey), |
|
858 KEYCODE_ENTRY(VK_F24, NSF24FunctionKey), |
|
859 KEYCODE_ENTRY(VK_NUM_LOCK, NSClearLineFunctionKey), |
|
860 KEYCODE_ENTRY(VK_SCROLL_LOCK, NSScrollLockFunctionKey), |
|
861 KEYCODE_ENTRY(VK_COMMA, ','), |
|
862 KEYCODE_ENTRY(VK_PERIOD, '.'), |
|
863 KEYCODE_ENTRY(VK_SLASH, '/'), |
|
864 KEYCODE_ENTRY(VK_BACK_QUOTE, '`'), |
|
865 KEYCODE_ENTRY(VK_OPEN_BRACKET, '['), |
|
866 KEYCODE_ENTRY(VK_BACK_SLASH, '\\'), |
|
867 KEYCODE_ENTRY(VK_CLOSE_BRACKET, ']'), |
|
868 KEYCODE_ENTRY(VK_QUOTE, '\'') |
|
869 |
|
870 #undef KEYCODE_ENTRY |
|
871 |
|
872 }; |
|
873 |
|
874 uint32_t |
|
875 nsCocoaUtils::ConvertGeckoNameToMacCharCode(const nsAString& aKeyCodeName) |
|
876 { |
|
877 if (aKeyCodeName.IsEmpty()) { |
|
878 return 0; |
|
879 } |
|
880 |
|
881 nsAutoCString keyCodeName; |
|
882 keyCodeName.AssignWithConversion(aKeyCodeName); |
|
883 // We want case-insensitive comparison with data stored as uppercase. |
|
884 ToUpperCase(keyCodeName); |
|
885 |
|
886 uint32_t keyCodeNameLength = keyCodeName.Length(); |
|
887 const char* keyCodeNameStr = keyCodeName.get(); |
|
888 for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) { |
|
889 if (keyCodeNameLength == gKeyConversions[i].strLength && |
|
890 nsCRT::strcmp(gKeyConversions[i].str, keyCodeNameStr) == 0) { |
|
891 return gKeyConversions[i].charCode; |
|
892 } |
|
893 } |
|
894 |
|
895 return 0; |
|
896 } |
|
897 |
|
898 uint32_t |
|
899 nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(uint32_t aKeyCode) |
|
900 { |
|
901 if (!aKeyCode) { |
|
902 return 0; |
|
903 } |
|
904 |
|
905 for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) { |
|
906 if (gKeyConversions[i].geckoKeyCode == aKeyCode) { |
|
907 return gKeyConversions[i].charCode; |
|
908 } |
|
909 } |
|
910 |
|
911 return 0; |
|
912 } |