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/blink_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
21using WebKit::WebCursorInfo;
22using 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
37enum {
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};
78typedef 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
115namespace {
116
117NSCursor* 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.
128NSCursor* 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().
143CGImageRef 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
174NSCursor* 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  NSImage* cursor_image = gfx::SkBitmapToNSImage(bitmap);
212  [cursor_image setSize:dip_size];
213
214  NSCursor* cursor = [[NSCursor alloc] initWithImage:cursor_image
215                                             hotSpot:dip_hotspot];
216
217  return [cursor autorelease];
218}
219
220}  // namespace
221
222// Match Safari's cursor choices; see platform/mac/CursorMac.mm .
223gfx::NativeCursor WebCursor::GetNativeCursor() {
224  switch (type_) {
225    case WebCursorInfo::TypePointer:
226      return [NSCursor arrowCursor];
227    case WebCursorInfo::TypeCross:
228      return [NSCursor crosshairCursor];
229    case WebCursorInfo::TypeHand:
230      // If >= 10.7, the pointingHandCursor has a shadow so use it. Otherwise
231      // use the custom one.
232      if (base::mac::IsOSLionOrLater())
233        return [NSCursor pointingHandCursor];
234      else
235        return LoadCursor(IDR_LINK_CURSOR, 6, 1);
236    case WebCursorInfo::TypeIBeam:
237      return [NSCursor IBeamCursor];
238    case WebCursorInfo::TypeWait:
239      return GetCoreCursorWithFallback(kBusyButClickableCursor,
240                                       IDR_WAIT_CURSOR, 7, 7);
241    case WebCursorInfo::TypeHelp:
242      return GetCoreCursorWithFallback(kHelpCursor,
243                                       IDR_HELP_CURSOR, 8, 8);
244    case WebCursorInfo::TypeEastResize:
245    case WebCursorInfo::TypeEastPanning:
246      return GetCoreCursorWithFallback(kResizeEastCursor,
247                                       IDR_EAST_RESIZE_CURSOR, 14, 7);
248    case WebCursorInfo::TypeNorthResize:
249    case WebCursorInfo::TypeNorthPanning:
250      return GetCoreCursorWithFallback(kResizeNorthCursor,
251                                       IDR_NORTH_RESIZE_CURSOR, 7, 1);
252    case WebCursorInfo::TypeNorthEastResize:
253    case WebCursorInfo::TypeNorthEastPanning:
254      return GetCoreCursorWithFallback(kResizeNortheastCursor,
255                                       IDR_NORTHEAST_RESIZE_CURSOR, 14, 1);
256    case WebCursorInfo::TypeNorthWestResize:
257    case WebCursorInfo::TypeNorthWestPanning:
258      return GetCoreCursorWithFallback(kResizeNorthwestCursor,
259                                       IDR_NORTHWEST_RESIZE_CURSOR, 0, 0);
260    case WebCursorInfo::TypeSouthResize:
261    case WebCursorInfo::TypeSouthPanning:
262      return GetCoreCursorWithFallback(kResizeSouthCursor,
263                                       IDR_SOUTH_RESIZE_CURSOR, 7, 14);
264    case WebCursorInfo::TypeSouthEastResize:
265    case WebCursorInfo::TypeSouthEastPanning:
266      return GetCoreCursorWithFallback(kResizeSoutheastCursor,
267                                       IDR_SOUTHEAST_RESIZE_CURSOR, 14, 14);
268    case WebCursorInfo::TypeSouthWestResize:
269    case WebCursorInfo::TypeSouthWestPanning:
270      return GetCoreCursorWithFallback(kResizeSouthwestCursor,
271                                       IDR_SOUTHWEST_RESIZE_CURSOR, 1, 14);
272    case WebCursorInfo::TypeWestResize:
273    case WebCursorInfo::TypeWestPanning:
274      return GetCoreCursorWithFallback(kResizeWestCursor,
275                                       IDR_WEST_RESIZE_CURSOR, 1, 7);
276    case WebCursorInfo::TypeNorthSouthResize:
277      return GetCoreCursorWithFallback(kResizeNorthSouthCursor,
278                                       IDR_NORTHSOUTH_RESIZE_CURSOR, 7, 7);
279    case WebCursorInfo::TypeEastWestResize:
280      return GetCoreCursorWithFallback(kResizeEastWestCursor,
281                                       IDR_EASTWEST_RESIZE_CURSOR, 7, 7);
282    case WebCursorInfo::TypeNorthEastSouthWestResize:
283      return GetCoreCursorWithFallback(kResizeNortheastSouthwestCursor,
284                                       IDR_NORTHEASTSOUTHWEST_RESIZE_CURSOR,
285                                       7, 7);
286    case WebCursorInfo::TypeNorthWestSouthEastResize:
287      return GetCoreCursorWithFallback(kResizeNorthwestSoutheastCursor,
288                                       IDR_NORTHWESTSOUTHEAST_RESIZE_CURSOR,
289                                       7, 7);
290    case WebCursorInfo::TypeColumnResize:
291      return [NSCursor resizeLeftRightCursor];
292    case WebCursorInfo::TypeRowResize:
293      return [NSCursor resizeUpDownCursor];
294    case WebCursorInfo::TypeMiddlePanning:
295    case WebCursorInfo::TypeMove:
296      return GetCoreCursorWithFallback(kMoveCursor,
297                                       IDR_MOVE_CURSOR, 7, 7);
298    case WebCursorInfo::TypeVerticalText:
299      // IBeamCursorForVerticalLayout is >= 10.7.
300      if ([NSCursor respondsToSelector:@selector(IBeamCursorForVerticalLayout)])
301        return [NSCursor IBeamCursorForVerticalLayout];
302      else
303        return LoadCursor(IDR_VERTICALTEXT_CURSOR, 7, 7);
304    case WebCursorInfo::TypeCell:
305      return GetCoreCursorWithFallback(kCellCursor,
306                                       IDR_CELL_CURSOR, 7, 7);
307    case WebCursorInfo::TypeContextMenu:
308      return [NSCursor contextualMenuCursor];
309    case WebCursorInfo::TypeAlias:
310      return GetCoreCursorWithFallback(kMakeAliasCursor,
311                                       IDR_ALIAS_CURSOR, 11, 3);
312    case WebCursorInfo::TypeProgress:
313      return GetCoreCursorWithFallback(kBusyButClickableCursor,
314                                       IDR_PROGRESS_CURSOR, 3, 2);
315    case WebCursorInfo::TypeNoDrop:
316    case WebCursorInfo::TypeNotAllowed:
317      return [NSCursor operationNotAllowedCursor];
318    case WebCursorInfo::TypeCopy:
319      return [NSCursor dragCopyCursor];
320    case WebCursorInfo::TypeNone:
321      return LoadCursor(IDR_NONE_CURSOR, 7, 7);
322    case WebCursorInfo::TypeZoomIn:
323      return GetCoreCursorWithFallback(kZoomInCursor,
324                                       IDR_ZOOMIN_CURSOR, 7, 7);
325    case WebCursorInfo::TypeZoomOut:
326      return GetCoreCursorWithFallback(kZoomOutCursor,
327                                       IDR_ZOOMOUT_CURSOR, 7, 7);
328    case WebCursorInfo::TypeGrab:
329      return [NSCursor openHandCursor];
330    case WebCursorInfo::TypeGrabbing:
331      return [NSCursor closedHandCursor];
332    case WebCursorInfo::TypeCustom:
333      return CreateCustomCursor(
334          custom_data_, custom_size_, custom_scale_, hotspot_);
335  }
336  NOTREACHED();
337  return nil;
338}
339
340void WebCursor::InitFromNSCursor(NSCursor* cursor) {
341  CursorInfo cursor_info;
342
343  if ([cursor isEqual:[NSCursor arrowCursor]]) {
344    cursor_info.type = WebCursorInfo::TypePointer;
345  } else if ([cursor isEqual:[NSCursor IBeamCursor]]) {
346    cursor_info.type = WebCursorInfo::TypeIBeam;
347  } else if ([cursor isEqual:[NSCursor crosshairCursor]]) {
348    cursor_info.type = WebCursorInfo::TypeCross;
349  } else if ([cursor isEqual:[NSCursor pointingHandCursor]]) {
350    cursor_info.type = WebCursorInfo::TypeHand;
351  } else if ([cursor isEqual:[NSCursor resizeLeftCursor]]) {
352    cursor_info.type = WebCursorInfo::TypeWestResize;
353  } else if ([cursor isEqual:[NSCursor resizeRightCursor]]) {
354    cursor_info.type = WebCursorInfo::TypeEastResize;
355  } else if ([cursor isEqual:[NSCursor resizeLeftRightCursor]]) {
356    cursor_info.type = WebCursorInfo::TypeEastWestResize;
357  } else if ([cursor isEqual:[NSCursor resizeUpCursor]]) {
358    cursor_info.type = WebCursorInfo::TypeNorthResize;
359  } else if ([cursor isEqual:[NSCursor resizeDownCursor]]) {
360    cursor_info.type = WebCursorInfo::TypeSouthResize;
361  } else if ([cursor isEqual:[NSCursor resizeUpDownCursor]]) {
362    cursor_info.type = WebCursorInfo::TypeNorthSouthResize;
363  } else if ([cursor isEqual:[NSCursor openHandCursor]]) {
364    cursor_info.type = WebCursorInfo::TypeGrab;
365  } else if ([cursor isEqual:[NSCursor closedHandCursor]]) {
366    cursor_info.type = WebCursorInfo::TypeGrabbing;
367  } else if ([cursor isEqual:[NSCursor operationNotAllowedCursor]]) {
368    cursor_info.type = WebCursorInfo::TypeNotAllowed;
369  } else if ([cursor isEqual:[NSCursor dragCopyCursor]]) {
370    cursor_info.type = WebCursorInfo::TypeCopy;
371  } else if ([cursor isEqual:[NSCursor contextualMenuCursor]]) {
372    cursor_info.type = WebCursorInfo::TypeContextMenu;
373  } else if (
374      [NSCursor respondsToSelector:@selector(IBeamCursorForVerticalLayout)] &&
375      [cursor isEqual:[NSCursor IBeamCursorForVerticalLayout]]) {
376    cursor_info.type = WebCursorInfo::TypeVerticalText;
377  } else {
378    // Also handles the [NSCursor disappearingItemCursor] case. Quick-and-dirty
379    // image conversion; TODO(avi): do better.
380    CGImageRef cg_image = nil;
381    NSImage* image = [cursor image];
382    for (id rep in [image representations]) {
383      if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
384        cg_image = [rep CGImage];
385        break;
386      }
387    }
388
389    if (cg_image) {
390      cursor_info.type = WebCursorInfo::TypeCustom;
391      NSPoint hot_spot = [cursor hotSpot];
392      cursor_info.hotspot = gfx::Point(hot_spot.x, hot_spot.y);
393      cursor_info.custom_image = gfx::CGImageToSkBitmap(cg_image);
394    } else {
395      cursor_info.type = WebCursorInfo::TypePointer;
396    }
397  }
398
399  InitFromCursorInfo(cursor_info);
400}
401
402void WebCursor::InitPlatformData() {
403  return;
404}
405
406bool WebCursor::SerializePlatformData(Pickle* pickle) const {
407  return true;
408}
409
410bool WebCursor::DeserializePlatformData(PickleIterator* iter) {
411  return true;
412}
413
414bool WebCursor::IsPlatformDataEqual(const WebCursor& other) const {
415  return true;
416}
417
418void WebCursor::CleanupPlatformData() {
419  return;
420}
421
422void WebCursor::CopyPlatformData(const WebCursor& other) {
423  return;
424}
425