- add sources.
[platform/framework/web/crosswalk.git] / src / webkit / common / cursors / webcursor_mac.mm
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "webkit/common/cursors/webcursor.h"
6
7 #import <AppKit/AppKit.h>
8
9 #include "base/logging.h"
10 #include "base/mac/mac_util.h"
11 #include "base/mac/scoped_cftyperef.h"
12 #include "grit/webkit_resources.h"
13 #include "skia/ext/skia_utils_mac.h"
14 #include "third_party/WebKit/public/platform/WebSize.h"
15 #include "third_party/WebKit/public/web/WebCursorInfo.h"
16 #include "ui/base/resource/resource_bundle.h"
17 #include "ui/gfx/point_conversions.h"
18 #include "ui/gfx/size_conversions.h"
19
20
21 using WebKit::WebCursorInfo;
22 using WebKit::WebSize;
23
24 // Declare symbols that are part of the 10.7 SDK.
25 #if !defined(MAC_OS_X_VERSION_10_7) || \
26     MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
27
28 @interface NSCursor (LionSDKDeclarations)
29 + (NSCursor*)IBeamCursorForVerticalLayout;
30 @end
31
32 #endif  // MAC_OS_X_VERSION_10_7
33
34 // Private interface to CoreCursor, as of Mac OS X 10.7. This is essentially the
35 // implementation of WKCursor in WebKitSystemInterface.
36
37 enum {
38   kArrowCursor = 0,
39   kIBeamCursor = 1,
40   kMakeAliasCursor = 2,
41   kOperationNotAllowedCursor = 3,
42   kBusyButClickableCursor = 4,
43   kCopyCursor = 5,
44   kClosedHandCursor = 11,
45   kOpenHandCursor = 12,
46   kPointingHandCursor = 13,
47   kCountingUpHandCursor = 14,
48   kCountingDownHandCursor = 15,
49   kCountingUpAndDownHandCursor = 16,
50   kResizeLeftCursor = 17,
51   kResizeRightCursor = 18,
52   kResizeLeftRightCursor = 19,
53   kCrosshairCursor = 20,
54   kResizeUpCursor = 21,
55   kResizeDownCursor = 22,
56   kResizeUpDownCursor = 23,
57   kContextualMenuCursor = 24,
58   kDisappearingItemCursor = 25,
59   kVerticalIBeamCursor = 26,
60   kResizeEastCursor = 27,
61   kResizeEastWestCursor = 28,
62   kResizeNortheastCursor = 29,
63   kResizeNortheastSouthwestCursor = 30,
64   kResizeNorthCursor = 31,
65   kResizeNorthSouthCursor = 32,
66   kResizeNorthwestCursor = 33,
67   kResizeNorthwestSoutheastCursor = 34,
68   kResizeSoutheastCursor = 35,
69   kResizeSouthCursor = 36,
70   kResizeSouthwestCursor = 37,
71   kResizeWestCursor = 38,
72   kMoveCursor = 39,
73   kHelpCursor = 40,  // Present on >= 10.7.3.
74   kCellCursor = 41,  // Present on >= 10.7.3.
75   kZoomInCursor = 42,  // Present on >= 10.7.3.
76   kZoomOutCursor = 43  // Present on >= 10.7.3.
77 };
78 typedef long long CrCoreCursorType;
79
80 @interface CrCoreCursor : NSCursor {
81  @private
82   CrCoreCursorType type_;
83 }
84
85 + (id)cursorWithType:(CrCoreCursorType)type;
86 - (id)initWithType:(CrCoreCursorType)type;
87 - (CrCoreCursorType)_coreCursorType;
88
89 @end
90
91 @implementation CrCoreCursor
92
93 + (id)cursorWithType:(CrCoreCursorType)type {
94   NSCursor* cursor = [[CrCoreCursor alloc] initWithType:type];
95   if ([cursor image])
96     return [cursor autorelease];
97
98   [cursor release];
99   return nil;
100 }
101
102 - (id)initWithType:(CrCoreCursorType)type {
103   if ((self = [super init])) {
104     type_ = type;
105   }
106   return self;
107 }
108
109 - (CrCoreCursorType)_coreCursorType {
110   return type_;
111 }
112
113 @end
114
115 namespace {
116
117 NSCursor* LoadCursor(int resource_id, int hotspot_x, int hotspot_y) {
118   const gfx::Image& cursor_image =
119       ResourceBundle::GetSharedInstance().GetNativeImageNamed(resource_id);
120   DCHECK(!cursor_image.IsEmpty());
121   return [[[NSCursor alloc] initWithImage:cursor_image.ToNSImage()
122                                   hotSpot:NSMakePoint(hotspot_x,
123                                                       hotspot_y)] autorelease];
124 }
125
126 // Gets a specified cursor from CoreCursor, falling back to loading it from the
127 // image cache if CoreCursor cannot provide it.
128 NSCursor* GetCoreCursorWithFallback(CrCoreCursorType type,
129                                     int resource_id,
130                                     int hotspot_x,
131                                     int hotspot_y) {
132   if (base::mac::IsOSLionOrLater()) {
133     NSCursor* cursor = [CrCoreCursor cursorWithType:type];
134     if (cursor)
135       return cursor;
136   }
137
138   return LoadCursor(resource_id, hotspot_x, hotspot_y);
139 }
140
141 // TODO(avi): When Skia becomes default, fold this function into the remaining
142 // caller, InitFromCursor().
143 CGImageRef CreateCGImageFromCustomData(const std::vector<char>& custom_data,
144                                        const gfx::Size& custom_size) {
145   // If the data is missing, leave the backing transparent.
146   void* data = NULL;
147   if (!custom_data.empty()) {
148     // This is safe since we're not going to draw into the context we're
149     // creating.
150     data = const_cast<char*>(&custom_data[0]);
151   }
152
153   // If the size is empty, use a 1x1 transparent image.
154   gfx::Size size = custom_size;
155   if (size.IsEmpty()) {
156     size.SetSize(1, 1);
157     data = NULL;
158   }
159
160   base::ScopedCFTypeRef<CGColorSpaceRef> cg_color(
161       CGColorSpaceCreateDeviceRGB());
162   // The settings here match SetCustomData() below; keep in sync.
163   base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate(
164       data,
165       size.width(),
166       size.height(),
167       8,
168       size.width() * 4,
169       cg_color.get(),
170       kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big));
171   return CGBitmapContextCreateImage(context.get());
172 }
173
174 NSCursor* CreateCustomCursor(const std::vector<char>& custom_data,
175                              const gfx::Size& custom_size,
176                              float custom_scale,
177                              const gfx::Point& hotspot) {
178   // If the data is missing, leave the backing transparent.
179   void* data = NULL;
180   size_t data_size = 0;
181   if (!custom_data.empty()) {
182     // This is safe since we're not going to draw into the context we're
183     // creating.
184     data = const_cast<char*>(&custom_data[0]);
185     data_size = custom_data.size();
186   }
187
188   // If the size is empty, use a 1x1 transparent image.
189   gfx::Size size = custom_size;
190   if (size.IsEmpty()) {
191     size.SetSize(1, 1);
192     data = NULL;
193   }
194
195   SkBitmap bitmap;
196   bitmap.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height());
197   bitmap.allocPixels();
198   if (data)
199     memcpy(bitmap.getAddr32(0, 0), data, data_size);
200   else
201     bitmap.eraseARGB(0, 0, 0, 0);
202
203   // Convert from pixels to view units.
204   if (custom_scale == 0)
205     custom_scale = 1;
206   NSSize dip_size = NSSizeFromCGSize(gfx::ToFlooredSize(
207       gfx::ScaleSize(custom_size, 1 / custom_scale)).ToCGSize());
208   NSPoint dip_hotspot = NSPointFromCGPoint(gfx::ToFlooredPoint(
209       gfx::ScalePoint(hotspot, 1 / custom_scale)).ToCGPoint());
210
211   // Both the image and its representation need to have the same size for
212   // cursors to appear in high resolution on retina displays. Note that the
213   // size of a representation is not the same as pixelsWide or pixelsHigh.
214   NSImage* cursor_image = gfx::SkBitmapToNSImage(bitmap);
215   [cursor_image setSize:dip_size];
216   [[[cursor_image representations] objectAtIndex:0] setSize:dip_size];
217
218   NSCursor* cursor = [[NSCursor alloc] initWithImage:cursor_image
219                                              hotSpot:dip_hotspot];
220
221   return [cursor autorelease];
222 }
223
224 }  // namespace
225
226 // Match Safari's cursor choices; see platform/mac/CursorMac.mm .
227 gfx::NativeCursor WebCursor::GetNativeCursor() {
228   switch (type_) {
229     case WebCursorInfo::TypePointer:
230       return [NSCursor arrowCursor];
231     case WebCursorInfo::TypeCross:
232       return [NSCursor crosshairCursor];
233     case WebCursorInfo::TypeHand:
234       // If >= 10.7, the pointingHandCursor has a shadow so use it. Otherwise
235       // use the custom one.
236       if (base::mac::IsOSLionOrLater())
237         return [NSCursor pointingHandCursor];
238       else
239         return LoadCursor(IDR_LINK_CURSOR, 6, 1);
240     case WebCursorInfo::TypeIBeam:
241       return [NSCursor IBeamCursor];
242     case WebCursorInfo::TypeWait:
243       return GetCoreCursorWithFallback(kBusyButClickableCursor,
244                                        IDR_WAIT_CURSOR, 7, 7);
245     case WebCursorInfo::TypeHelp:
246       return GetCoreCursorWithFallback(kHelpCursor,
247                                        IDR_HELP_CURSOR, 8, 8);
248     case WebCursorInfo::TypeEastResize:
249     case WebCursorInfo::TypeEastPanning:
250       return GetCoreCursorWithFallback(kResizeEastCursor,
251                                        IDR_EAST_RESIZE_CURSOR, 14, 7);
252     case WebCursorInfo::TypeNorthResize:
253     case WebCursorInfo::TypeNorthPanning:
254       return GetCoreCursorWithFallback(kResizeNorthCursor,
255                                        IDR_NORTH_RESIZE_CURSOR, 7, 1);
256     case WebCursorInfo::TypeNorthEastResize:
257     case WebCursorInfo::TypeNorthEastPanning:
258       return GetCoreCursorWithFallback(kResizeNortheastCursor,
259                                        IDR_NORTHEAST_RESIZE_CURSOR, 14, 1);
260     case WebCursorInfo::TypeNorthWestResize:
261     case WebCursorInfo::TypeNorthWestPanning:
262       return GetCoreCursorWithFallback(kResizeNorthwestCursor,
263                                        IDR_NORTHWEST_RESIZE_CURSOR, 0, 0);
264     case WebCursorInfo::TypeSouthResize:
265     case WebCursorInfo::TypeSouthPanning:
266       return GetCoreCursorWithFallback(kResizeSouthCursor,
267                                        IDR_SOUTH_RESIZE_CURSOR, 7, 14);
268     case WebCursorInfo::TypeSouthEastResize:
269     case WebCursorInfo::TypeSouthEastPanning:
270       return GetCoreCursorWithFallback(kResizeSoutheastCursor,
271                                        IDR_SOUTHEAST_RESIZE_CURSOR, 14, 14);
272     case WebCursorInfo::TypeSouthWestResize:
273     case WebCursorInfo::TypeSouthWestPanning:
274       return GetCoreCursorWithFallback(kResizeSouthwestCursor,
275                                        IDR_SOUTHWEST_RESIZE_CURSOR, 1, 14);
276     case WebCursorInfo::TypeWestResize:
277     case WebCursorInfo::TypeWestPanning:
278       return GetCoreCursorWithFallback(kResizeWestCursor,
279                                        IDR_WEST_RESIZE_CURSOR, 1, 7);
280     case WebCursorInfo::TypeNorthSouthResize:
281       return GetCoreCursorWithFallback(kResizeNorthSouthCursor,
282                                        IDR_NORTHSOUTH_RESIZE_CURSOR, 7, 7);
283     case WebCursorInfo::TypeEastWestResize:
284       return GetCoreCursorWithFallback(kResizeEastWestCursor,
285                                        IDR_EASTWEST_RESIZE_CURSOR, 7, 7);
286     case WebCursorInfo::TypeNorthEastSouthWestResize:
287       return GetCoreCursorWithFallback(kResizeNortheastSouthwestCursor,
288                                        IDR_NORTHEASTSOUTHWEST_RESIZE_CURSOR,
289                                        7, 7);
290     case WebCursorInfo::TypeNorthWestSouthEastResize:
291       return GetCoreCursorWithFallback(kResizeNorthwestSoutheastCursor,
292                                        IDR_NORTHWESTSOUTHEAST_RESIZE_CURSOR,
293                                        7, 7);
294     case WebCursorInfo::TypeColumnResize:
295       return [NSCursor resizeLeftRightCursor];
296     case WebCursorInfo::TypeRowResize:
297       return [NSCursor resizeUpDownCursor];
298     case WebCursorInfo::TypeMiddlePanning:
299     case WebCursorInfo::TypeMove:
300       return GetCoreCursorWithFallback(kMoveCursor,
301                                        IDR_MOVE_CURSOR, 7, 7);
302     case WebCursorInfo::TypeVerticalText:
303       // IBeamCursorForVerticalLayout is >= 10.7.
304       if ([NSCursor respondsToSelector:@selector(IBeamCursorForVerticalLayout)])
305         return [NSCursor IBeamCursorForVerticalLayout];
306       else
307         return LoadCursor(IDR_VERTICALTEXT_CURSOR, 7, 7);
308     case WebCursorInfo::TypeCell:
309       return GetCoreCursorWithFallback(kCellCursor,
310                                        IDR_CELL_CURSOR, 7, 7);
311     case WebCursorInfo::TypeContextMenu:
312       return [NSCursor contextualMenuCursor];
313     case WebCursorInfo::TypeAlias:
314       return GetCoreCursorWithFallback(kMakeAliasCursor,
315                                        IDR_ALIAS_CURSOR, 11, 3);
316     case WebCursorInfo::TypeProgress:
317       return GetCoreCursorWithFallback(kBusyButClickableCursor,
318                                        IDR_PROGRESS_CURSOR, 3, 2);
319     case WebCursorInfo::TypeNoDrop:
320     case WebCursorInfo::TypeNotAllowed:
321       return [NSCursor operationNotAllowedCursor];
322     case WebCursorInfo::TypeCopy:
323       return [NSCursor dragCopyCursor];
324     case WebCursorInfo::TypeNone:
325       return LoadCursor(IDR_NONE_CURSOR, 7, 7);
326     case WebCursorInfo::TypeZoomIn:
327       return GetCoreCursorWithFallback(kZoomInCursor,
328                                        IDR_ZOOMIN_CURSOR, 7, 7);
329     case WebCursorInfo::TypeZoomOut:
330       return GetCoreCursorWithFallback(kZoomOutCursor,
331                                        IDR_ZOOMOUT_CURSOR, 7, 7);
332     case WebCursorInfo::TypeGrab:
333       return [NSCursor openHandCursor];
334     case WebCursorInfo::TypeGrabbing:
335       return [NSCursor closedHandCursor];
336     case WebCursorInfo::TypeCustom:
337       return CreateCustomCursor(
338           custom_data_, custom_size_, custom_scale_, hotspot_);
339   }
340   NOTREACHED();
341   return nil;
342 }
343
344 void WebCursor::InitFromNSCursor(NSCursor* cursor) {
345   CursorInfo cursor_info;
346
347   if ([cursor isEqual:[NSCursor arrowCursor]]) {
348     cursor_info.type = WebCursorInfo::TypePointer;
349   } else if ([cursor isEqual:[NSCursor IBeamCursor]]) {
350     cursor_info.type = WebCursorInfo::TypeIBeam;
351   } else if ([cursor isEqual:[NSCursor crosshairCursor]]) {
352     cursor_info.type = WebCursorInfo::TypeCross;
353   } else if ([cursor isEqual:[NSCursor pointingHandCursor]]) {
354     cursor_info.type = WebCursorInfo::TypeHand;
355   } else if ([cursor isEqual:[NSCursor resizeLeftCursor]]) {
356     cursor_info.type = WebCursorInfo::TypeWestResize;
357   } else if ([cursor isEqual:[NSCursor resizeRightCursor]]) {
358     cursor_info.type = WebCursorInfo::TypeEastResize;
359   } else if ([cursor isEqual:[NSCursor resizeLeftRightCursor]]) {
360     cursor_info.type = WebCursorInfo::TypeEastWestResize;
361   } else if ([cursor isEqual:[NSCursor resizeUpCursor]]) {
362     cursor_info.type = WebCursorInfo::TypeNorthResize;
363   } else if ([cursor isEqual:[NSCursor resizeDownCursor]]) {
364     cursor_info.type = WebCursorInfo::TypeSouthResize;
365   } else if ([cursor isEqual:[NSCursor resizeUpDownCursor]]) {
366     cursor_info.type = WebCursorInfo::TypeNorthSouthResize;
367   } else if ([cursor isEqual:[NSCursor openHandCursor]]) {
368     cursor_info.type = WebCursorInfo::TypeGrab;
369   } else if ([cursor isEqual:[NSCursor closedHandCursor]]) {
370     cursor_info.type = WebCursorInfo::TypeGrabbing;
371   } else if ([cursor isEqual:[NSCursor operationNotAllowedCursor]]) {
372     cursor_info.type = WebCursorInfo::TypeNotAllowed;
373   } else if ([cursor isEqual:[NSCursor dragCopyCursor]]) {
374     cursor_info.type = WebCursorInfo::TypeCopy;
375   } else if ([cursor isEqual:[NSCursor contextualMenuCursor]]) {
376     cursor_info.type = WebCursorInfo::TypeContextMenu;
377   } else if (
378       [NSCursor respondsToSelector:@selector(IBeamCursorForVerticalLayout)] &&
379       [cursor isEqual:[NSCursor IBeamCursorForVerticalLayout]]) {
380     cursor_info.type = WebCursorInfo::TypeVerticalText;
381   } else {
382     // Also handles the [NSCursor disappearingItemCursor] case. Quick-and-dirty
383     // image conversion; TODO(avi): do better.
384     CGImageRef cg_image = nil;
385     NSImage* image = [cursor image];
386     for (id rep in [image representations]) {
387       if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
388         cg_image = [rep CGImage];
389         break;
390       }
391     }
392
393     if (cg_image) {
394       cursor_info.type = WebCursorInfo::TypeCustom;
395       NSPoint hot_spot = [cursor hotSpot];
396       cursor_info.hotspot = gfx::Point(hot_spot.x, hot_spot.y);
397       cursor_info.custom_image = gfx::CGImageToSkBitmap(cg_image);
398     } else {
399       cursor_info.type = WebCursorInfo::TypePointer;
400     }
401   }
402
403   InitFromCursorInfo(cursor_info);
404 }
405
406 void WebCursor::InitPlatformData() {
407   return;
408 }
409
410 bool WebCursor::SerializePlatformData(Pickle* pickle) const {
411   return true;
412 }
413
414 bool WebCursor::DeserializePlatformData(PickleIterator* iter) {
415   return true;
416 }
417
418 bool WebCursor::IsPlatformDataEqual(const WebCursor& other) const {
419   return true;
420 }
421
422 void WebCursor::CleanupPlatformData() {
423   return;
424 }
425
426 void WebCursor::CopyPlatformData(const WebCursor& other) {
427   return;
428 }