1// Copyright (c) 2011 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/glue/webcursor.h"
6
7#import <AppKit/AppKit.h>
8#include <Carbon/Carbon.h>
9
10#include "app/mac/nsimage_cache.h"
11#include "base/logging.h"
12#include "base/mac/scoped_cftyperef.h"
13#include "third_party/WebKit/Source/WebKit/chromium/public/WebCursorInfo.h"
14#include "third_party/WebKit/Source/WebKit/chromium/public/WebImage.h"
15#include "third_party/WebKit/Source/WebKit/chromium/public/WebSize.h"
16
17using WebKit::WebCursorInfo;
18using WebKit::WebImage;
19using WebKit::WebSize;
20
21namespace {
22
23// TODO: This image fetch can (and probably should) be serviced by the resource
24// resource bundle instead of going through the image cache.
25NSCursor* LoadCursor(const char* name, int x, int y) {
26  NSString* file_name = [NSString stringWithUTF8String:name];
27  DCHECK(file_name);
28  NSImage* cursor_image = app::mac::GetCachedImageWithName(file_name);
29  DCHECK(cursor_image);
30  return [[[NSCursor alloc] initWithImage:cursor_image
31                                  hotSpot:NSMakePoint(x, y)] autorelease];
32}
33
34CGImageRef CreateCGImageFromCustomData(const std::vector<char>& custom_data,
35                                       const gfx::Size& custom_size) {
36  base::mac::ScopedCFTypeRef<CGColorSpaceRef> cg_color(
37      CGColorSpaceCreateDeviceRGB());
38  // This is safe since we're not going to draw into the context we're creating.
39  void* data = const_cast<char*>(&custom_data[0]);
40  // The settings here match SetCustomData() below; keep in sync.
41  base::mac::ScopedCFTypeRef<CGContextRef> context(
42      CGBitmapContextCreate(data,
43                            custom_size.width(),
44                            custom_size.height(),
45                            8,
46                            custom_size.width()*4,
47                            cg_color.get(),
48                            kCGImageAlphaPremultipliedLast |
49                            kCGBitmapByteOrder32Big));
50  return CGBitmapContextCreateImage(context.get());
51}
52
53NSCursor* CreateCustomCursor(const std::vector<char>& custom_data,
54                             const gfx::Size& custom_size,
55                             const gfx::Point& hotspot) {
56  // CG throws a cocoa exception if we try to create an empty image, which
57  // results in an infinite loop.  This CHECK ensures that we crash instead.
58  CHECK(!custom_data.empty());
59
60  base::mac::ScopedCFTypeRef<CGImageRef> cg_image(
61      CreateCGImageFromCustomData(custom_data, custom_size));
62
63  NSBitmapImageRep* ns_bitmap =
64      [[NSBitmapImageRep alloc] initWithCGImage:cg_image.get()];
65  NSImage* cursor_image = [[NSImage alloc] init];
66  DCHECK(cursor_image);
67  [cursor_image addRepresentation:ns_bitmap];
68  [ns_bitmap release];
69
70  NSCursor* cursor = [[NSCursor alloc] initWithImage:cursor_image
71                                             hotSpot:NSMakePoint(hotspot.x(),
72                                                                 hotspot.y())];
73  [cursor_image release];
74
75  return [cursor autorelease];
76}
77
78}  // namespace
79
80// We're matching Safari's cursor choices; see platform/mac/CursorMac.mm
81NSCursor* WebCursor::GetCursor() const {
82  switch (type_) {
83    case WebCursorInfo::TypePointer:
84      return [NSCursor arrowCursor];
85    case WebCursorInfo::TypeCross:
86      return LoadCursor("crossHairCursor", 11, 11);
87    case WebCursorInfo::TypeHand:
88      return LoadCursor("linkCursor", 6, 1);
89    case WebCursorInfo::TypeIBeam:
90      return [NSCursor IBeamCursor];
91    case WebCursorInfo::TypeWait:
92      return LoadCursor("waitCursor", 7, 7);
93    case WebCursorInfo::TypeHelp:
94      return LoadCursor("helpCursor", 8, 8);
95    case WebCursorInfo::TypeEastResize:
96    case WebCursorInfo::TypeEastPanning:
97      return LoadCursor("eastResizeCursor", 14, 7);
98    case WebCursorInfo::TypeNorthResize:
99    case WebCursorInfo::TypeNorthPanning:
100      return LoadCursor("northResizeCursor", 7, 1);
101    case WebCursorInfo::TypeNorthEastResize:
102    case WebCursorInfo::TypeNorthEastPanning:
103      return LoadCursor("northEastResizeCursor", 14, 1);
104    case WebCursorInfo::TypeNorthWestResize:
105    case WebCursorInfo::TypeNorthWestPanning:
106      return LoadCursor("northWestResizeCursor", 0, 0);
107    case WebCursorInfo::TypeSouthResize:
108    case WebCursorInfo::TypeSouthPanning:
109      return LoadCursor("southResizeCursor", 7, 14);
110    case WebCursorInfo::TypeSouthEastResize:
111    case WebCursorInfo::TypeSouthEastPanning:
112      return LoadCursor("southEastResizeCursor", 14, 14);
113    case WebCursorInfo::TypeSouthWestResize:
114    case WebCursorInfo::TypeSouthWestPanning:
115      return LoadCursor("southWestResizeCursor", 1, 14);
116    case WebCursorInfo::TypeWestResize:
117    case WebCursorInfo::TypeWestPanning:
118      return LoadCursor("westResizeCursor", 1, 7);
119    case WebCursorInfo::TypeNorthSouthResize:
120      return LoadCursor("northSouthResizeCursor", 7, 7);
121    case WebCursorInfo::TypeEastWestResize:
122      return LoadCursor("eastWestResizeCursor", 7, 7);
123    case WebCursorInfo::TypeNorthEastSouthWestResize:
124      return LoadCursor("northEastSouthWestResizeCursor", 7, 7);
125    case WebCursorInfo::TypeNorthWestSouthEastResize:
126      return LoadCursor("northWestSouthEastResizeCursor", 7, 7);
127    case WebCursorInfo::TypeColumnResize:
128      return [NSCursor resizeLeftRightCursor];
129    case WebCursorInfo::TypeRowResize:
130      return [NSCursor resizeUpDownCursor];
131    case WebCursorInfo::TypeMiddlePanning:
132    case WebCursorInfo::TypeMove:
133      return LoadCursor("moveCursor", 7, 7);
134    case WebCursorInfo::TypeVerticalText:
135      return LoadCursor("verticalTextCursor", 7, 7);
136    case WebCursorInfo::TypeCell:
137      return LoadCursor("cellCursor", 7, 7);
138    case WebCursorInfo::TypeContextMenu:
139      return LoadCursor("contextMenuCursor", 3, 2);
140    case WebCursorInfo::TypeAlias:
141      return LoadCursor("aliasCursor", 11, 3);
142    case WebCursorInfo::TypeProgress:
143      return LoadCursor("progressCursor", 3, 2);
144    case WebCursorInfo::TypeNoDrop:
145      return LoadCursor("noDropCursor", 3, 1);
146    case WebCursorInfo::TypeCopy:
147      return LoadCursor("copyCursor", 3, 2);
148    case WebCursorInfo::TypeNone:
149      return LoadCursor("noneCursor", 7, 7);
150    case WebCursorInfo::TypeNotAllowed:
151      return LoadCursor("notAllowedCursor", 11, 11);
152    case WebCursorInfo::TypeZoomIn:
153      return LoadCursor("zoomInCursor", 7, 7);
154    case WebCursorInfo::TypeZoomOut:
155      return LoadCursor("zoomOutCursor", 7, 7);
156    case WebCursorInfo::TypeGrab:
157      return [NSCursor openHandCursor];
158    case WebCursorInfo::TypeGrabbing:
159      return [NSCursor closedHandCursor];
160    case WebCursorInfo::TypeCustom:
161      return CreateCustomCursor(custom_data_, custom_size_, hotspot_);
162  }
163  NOTREACHED();
164  return nil;
165}
166
167gfx::NativeCursor WebCursor::GetNativeCursor() {
168  return GetCursor();
169}
170
171void WebCursor::InitFromThemeCursor(ThemeCursor cursor) {
172  WebKit::WebCursorInfo cursor_info;
173
174  switch (cursor) {
175    case kThemeArrowCursor:
176      cursor_info.type = WebCursorInfo::TypePointer;
177      break;
178    case kThemeCopyArrowCursor:
179      cursor_info.type = WebCursorInfo::TypeCopy;
180      break;
181    case kThemeAliasArrowCursor:
182      cursor_info.type = WebCursorInfo::TypeAlias;
183      break;
184    case kThemeContextualMenuArrowCursor:
185      cursor_info.type = WebCursorInfo::TypeContextMenu;
186      break;
187    case kThemeIBeamCursor:
188      cursor_info.type = WebCursorInfo::TypeIBeam;
189      break;
190    case kThemeCrossCursor:
191    case kThemePlusCursor:
192      cursor_info.type = WebCursorInfo::TypeCross;
193      break;
194    case kThemeWatchCursor:
195    case kThemeSpinningCursor:
196      cursor_info.type = WebCursorInfo::TypeWait;
197      break;
198    case kThemeClosedHandCursor:
199      cursor_info.type = WebCursorInfo::TypeGrabbing;
200      break;
201    case kThemeOpenHandCursor:
202      cursor_info.type = WebCursorInfo::TypeGrab;
203      break;
204    case kThemePointingHandCursor:
205    case kThemeCountingUpHandCursor:
206    case kThemeCountingDownHandCursor:
207    case kThemeCountingUpAndDownHandCursor:
208      cursor_info.type = WebCursorInfo::TypeHand;
209      break;
210    case kThemeResizeLeftCursor:
211      cursor_info.type = WebCursorInfo::TypeWestResize;
212      break;
213    case kThemeResizeRightCursor:
214      cursor_info.type = WebCursorInfo::TypeEastResize;
215      break;
216    case kThemeResizeLeftRightCursor:
217      cursor_info.type = WebCursorInfo::TypeEastWestResize;
218      break;
219    case kThemeNotAllowedCursor:
220      cursor_info.type = WebCursorInfo::TypeNotAllowed;
221      break;
222    case kThemeResizeUpCursor:
223      cursor_info.type = WebCursorInfo::TypeNorthResize;
224      break;
225    case kThemeResizeDownCursor:
226      cursor_info.type = WebCursorInfo::TypeSouthResize;
227      break;
228    case kThemeResizeUpDownCursor:
229      cursor_info.type = WebCursorInfo::TypeNorthSouthResize;
230      break;
231    case kThemePoofCursor:  // *shrug*
232    default:
233      cursor_info.type = WebCursorInfo::TypePointer;
234      break;
235  }
236
237  InitFromCursorInfo(cursor_info);
238}
239
240void WebCursor::InitFromCursor(const Cursor* cursor) {
241  // This conversion isn't perfect (in particular, the inversion effect of
242  // data==1, mask==0 won't work). Not planning on fixing it.
243
244  gfx::Size custom_size(16, 16);
245  std::vector<char> raw_data;
246  for (int row = 0; row < 16; ++row) {
247    unsigned short data = cursor->data[row];
248    unsigned short mask = cursor->mask[row];
249
250    // The Core Endian flipper callback for 'CURS' doesn't flip Bits16 as if it
251    // were a short (which it is), so we flip it here.
252    data = ((data << 8) & 0xFF00) | ((data >> 8) & 0x00FF);
253    mask = ((mask << 8) & 0xFF00) | ((mask >> 8) & 0x00FF);
254
255    for (int bit = 0; bit < 16; ++bit) {
256      if (data & 0x8000) {
257        raw_data.push_back(0x00);
258        raw_data.push_back(0x00);
259        raw_data.push_back(0x00);
260      } else {
261        raw_data.push_back(0xFF);
262        raw_data.push_back(0xFF);
263        raw_data.push_back(0xFF);
264      }
265      if (mask & 0x8000)
266        raw_data.push_back(0xFF);
267      else
268        raw_data.push_back(0x00);
269      data <<= 1;
270      mask <<= 1;
271    }
272  }
273
274  base::mac::ScopedCFTypeRef<CGImageRef> cg_image(
275      CreateCGImageFromCustomData(raw_data, custom_size));
276
277  WebKit::WebCursorInfo cursor_info;
278  cursor_info.type = WebCursorInfo::TypeCustom;
279  cursor_info.hotSpot = WebKit::WebPoint(cursor->hotSpot.h, cursor->hotSpot.v);
280  cursor_info.customImage = cg_image.get();
281
282  InitFromCursorInfo(cursor_info);
283}
284
285void WebCursor::InitFromNSCursor(NSCursor* cursor) {
286  WebKit::WebCursorInfo cursor_info;
287
288  if ([cursor isEqual:[NSCursor arrowCursor]]) {
289    cursor_info.type = WebCursorInfo::TypePointer;
290  } else if ([cursor isEqual:[NSCursor IBeamCursor]]) {
291    cursor_info.type = WebCursorInfo::TypeIBeam;
292  } else if ([cursor isEqual:[NSCursor crosshairCursor]]) {
293    cursor_info.type = WebCursorInfo::TypeCross;
294  } else if ([cursor isEqual:[NSCursor pointingHandCursor]]) {
295    cursor_info.type = WebCursorInfo::TypeHand;
296  } else if ([cursor isEqual:[NSCursor resizeLeftCursor]]) {
297    cursor_info.type = WebCursorInfo::TypeWestResize;
298  } else if ([cursor isEqual:[NSCursor resizeRightCursor]]) {
299    cursor_info.type = WebCursorInfo::TypeEastResize;
300  } else if ([cursor isEqual:[NSCursor resizeLeftRightCursor]]) {
301    cursor_info.type = WebCursorInfo::TypeEastWestResize;
302  } else if ([cursor isEqual:[NSCursor resizeUpCursor]]) {
303    cursor_info.type = WebCursorInfo::TypeNorthResize;
304  } else if ([cursor isEqual:[NSCursor resizeDownCursor]]) {
305    cursor_info.type = WebCursorInfo::TypeSouthResize;
306  } else if ([cursor isEqual:[NSCursor resizeUpDownCursor]]) {
307    cursor_info.type = WebCursorInfo::TypeNorthSouthResize;
308  } else if ([cursor isEqual:[NSCursor openHandCursor]]) {
309    cursor_info.type = WebCursorInfo::TypeGrab;
310  } else if ([cursor isEqual:[NSCursor closedHandCursor]]) {
311    cursor_info.type = WebCursorInfo::TypeGrabbing;
312  } else {
313    // Also handles the [NSCursor disappearingItemCursor] case. Quick-and-dirty
314    // image conversion; TODO(avi): do better.
315    CGImageRef cg_image = nil;
316    NSImage* image = [cursor image];
317    for (id rep in [image representations]) {
318      if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
319        cg_image = [rep CGImage];
320        break;
321      }
322    }
323
324    if (cg_image) {
325      cursor_info.type = WebCursorInfo::TypeCustom;
326      NSPoint hot_spot = [cursor hotSpot];
327      cursor_info.hotSpot = WebKit::WebPoint(hot_spot.x, hot_spot.y);
328      cursor_info.customImage = cg_image;
329    } else {
330      cursor_info.type = WebCursorInfo::TypePointer;
331    }
332  }
333
334  InitFromCursorInfo(cursor_info);
335}
336
337void WebCursor::SetCustomData(const WebImage& image) {
338  if (image.isNull())
339    return;
340
341  base::mac::ScopedCFTypeRef<CGColorSpaceRef> cg_color(
342      CGColorSpaceCreateDeviceRGB());
343
344  const WebSize& image_dimensions = image.size();
345  int image_width = image_dimensions.width;
346  int image_height = image_dimensions.height;
347
348  size_t size = image_height * image_width * 4;
349  custom_data_.resize(size);
350  custom_size_.set_width(image_width);
351  custom_size_.set_height(image_height);
352
353  // These settings match up with the code in CreateCustomCursor() above; keep
354  // them in sync.
355  // TODO(avi): test to ensure that the flags here are correct for RGBA
356  base::mac::ScopedCFTypeRef<CGContextRef> context(
357      CGBitmapContextCreate(&custom_data_[0],
358                            image_width,
359                            image_height,
360                            8,
361                            image_width * 4,
362                            cg_color.get(),
363                            kCGImageAlphaPremultipliedLast |
364                            kCGBitmapByteOrder32Big));
365  CGRect rect = CGRectMake(0, 0, image_width, image_height);
366  CGContextDrawImage(context.get(), rect, image.getCGImageRef());
367}
368
369void WebCursor::ImageFromCustomData(WebImage* image) const {
370  if (custom_data_.empty())
371    return;
372
373  base::mac::ScopedCFTypeRef<CGImageRef> cg_image(
374      CreateCGImageFromCustomData(custom_data_, custom_size_));
375  *image = cg_image.get();
376}
377
378void WebCursor::InitPlatformData() {
379  return;
380}
381
382bool WebCursor::SerializePlatformData(Pickle* pickle) const {
383  return true;
384}
385
386bool WebCursor::DeserializePlatformData(const Pickle* pickle, void** iter) {
387  return true;
388}
389
390bool WebCursor::IsPlatformDataEqual(const WebCursor& other) const {
391  return true;
392}
393
394void WebCursor::CleanupPlatformData() {
395  return;
396}
397
398void WebCursor::CopyPlatformData(const WebCursor& other) {
399  return;
400}
401