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.
5 #include "content/common/cursors/webcursor.h"
7 #import <AppKit/AppKit.h>
9 #include "base/logging.h"
10 #include "base/mac/mac_util.h"
11 #include "base/mac/scoped_cftyperef.h"
12 #include "base/mac/sdk_forward_declarations.h"
13 #include "content/public/common/content_client.h"
14 #include "grit/webkit_resources.h"
15 #include "skia/ext/skia_utils_mac.h"
16 #include "third_party/WebKit/public/platform/WebCursorInfo.h"
17 #include "third_party/WebKit/public/platform/WebSize.h"
18 #include "ui/gfx/image/image.h"
19 #include "ui/gfx/point_conversions.h"
20 #include "ui/gfx/size_conversions.h"
23 using blink::WebCursorInfo;
26 // Private interface to CoreCursor, as of Mac OS X 10.7. This is essentially the
27 // implementation of WKCursor in WebKitSystemInterface.
33 kOperationNotAllowedCursor = 3,
34 kBusyButClickableCursor = 4,
36 kClosedHandCursor = 11,
38 kPointingHandCursor = 13,
39 kCountingUpHandCursor = 14,
40 kCountingDownHandCursor = 15,
41 kCountingUpAndDownHandCursor = 16,
42 kResizeLeftCursor = 17,
43 kResizeRightCursor = 18,
44 kResizeLeftRightCursor = 19,
45 kCrosshairCursor = 20,
47 kResizeDownCursor = 22,
48 kResizeUpDownCursor = 23,
49 kContextualMenuCursor = 24,
50 kDisappearingItemCursor = 25,
51 kVerticalIBeamCursor = 26,
52 kResizeEastCursor = 27,
53 kResizeEastWestCursor = 28,
54 kResizeNortheastCursor = 29,
55 kResizeNortheastSouthwestCursor = 30,
56 kResizeNorthCursor = 31,
57 kResizeNorthSouthCursor = 32,
58 kResizeNorthwestCursor = 33,
59 kResizeNorthwestSoutheastCursor = 34,
60 kResizeSoutheastCursor = 35,
61 kResizeSouthCursor = 36,
62 kResizeSouthwestCursor = 37,
63 kResizeWestCursor = 38,
65 kHelpCursor = 40, // Present on >= 10.7.3.
66 kCellCursor = 41, // Present on >= 10.7.3.
67 kZoomInCursor = 42, // Present on >= 10.7.3.
68 kZoomOutCursor = 43 // Present on >= 10.7.3.
70 typedef long long CrCoreCursorType;
72 @interface CrCoreCursor : NSCursor {
74 CrCoreCursorType type_;
77 + (id)cursorWithType:(CrCoreCursorType)type;
78 - (id)initWithType:(CrCoreCursorType)type;
79 - (CrCoreCursorType)_coreCursorType;
83 @implementation CrCoreCursor
85 + (id)cursorWithType:(CrCoreCursorType)type {
86 NSCursor* cursor = [[CrCoreCursor alloc] initWithType:type];
88 return [cursor autorelease];
94 - (id)initWithType:(CrCoreCursorType)type {
95 if ((self = [super init])) {
101 - (CrCoreCursorType)_coreCursorType {
109 NSCursor* LoadCursor(int resource_id, int hotspot_x, int hotspot_y) {
110 const gfx::Image& cursor_image =
111 content::GetContentClient()->GetNativeImageNamed(resource_id);
112 DCHECK(!cursor_image.IsEmpty());
113 return [[[NSCursor alloc] initWithImage:cursor_image.ToNSImage()
114 hotSpot:NSMakePoint(hotspot_x,
115 hotspot_y)] autorelease];
118 // Gets a specified cursor from CoreCursor, falling back to loading it from the
119 // image cache if CoreCursor cannot provide it.
120 NSCursor* GetCoreCursorWithFallback(CrCoreCursorType type,
124 if (base::mac::IsOSLionOrLater()) {
125 NSCursor* cursor = [CrCoreCursor cursorWithType:type];
130 return LoadCursor(resource_id, hotspot_x, hotspot_y);
133 NSCursor* CreateCustomCursor(const std::vector<char>& custom_data,
134 const gfx::Size& custom_size,
136 const gfx::Point& hotspot) {
137 // If the data is missing, leave the backing transparent.
139 size_t data_size = 0;
140 if (!custom_data.empty()) {
141 // This is safe since we're not going to draw into the context we're
143 data = const_cast<char*>(&custom_data[0]);
144 data_size = custom_data.size();
147 // If the size is empty, use a 1x1 transparent image.
148 gfx::Size size = custom_size;
149 if (size.IsEmpty()) {
155 if (bitmap.allocN32Pixels(size.width(), size.height()) && data)
156 memcpy(bitmap.getAddr32(0, 0), data, data_size);
158 bitmap.eraseARGB(0, 0, 0, 0);
160 // Convert from pixels to view units.
161 if (custom_scale == 0)
163 NSSize dip_size = NSSizeFromCGSize(gfx::ToFlooredSize(
164 gfx::ScaleSize(custom_size, 1 / custom_scale)).ToCGSize());
165 NSPoint dip_hotspot = NSPointFromCGPoint(gfx::ToFlooredPoint(
166 gfx::ScalePoint(hotspot, 1 / custom_scale)).ToCGPoint());
168 // Both the image and its representation need to have the same size for
169 // cursors to appear in high resolution on retina displays. Note that the
170 // size of a representation is not the same as pixelsWide or pixelsHigh.
171 NSImage* cursor_image = gfx::SkBitmapToNSImage(bitmap);
172 [cursor_image setSize:dip_size];
173 [[[cursor_image representations] objectAtIndex:0] setSize:dip_size];
175 NSCursor* cursor = [[NSCursor alloc] initWithImage:cursor_image
176 hotSpot:dip_hotspot];
178 return [cursor autorelease];
185 // Match Safari's cursor choices; see platform/mac/CursorMac.mm .
186 gfx::NativeCursor WebCursor::GetNativeCursor() {
188 case WebCursorInfo::TypePointer:
189 return [NSCursor arrowCursor];
190 case WebCursorInfo::TypeCross:
191 return [NSCursor crosshairCursor];
192 case WebCursorInfo::TypeHand:
193 // If >= 10.7, the pointingHandCursor has a shadow so use it. Otherwise
194 // use the custom one.
195 if (base::mac::IsOSLionOrLater())
196 return [NSCursor pointingHandCursor];
198 return LoadCursor(IDR_LINK_CURSOR, 6, 1);
199 case WebCursorInfo::TypeIBeam:
200 return [NSCursor IBeamCursor];
201 case WebCursorInfo::TypeWait:
202 return GetCoreCursorWithFallback(kBusyButClickableCursor,
203 IDR_WAIT_CURSOR, 7, 7);
204 case WebCursorInfo::TypeHelp:
205 return GetCoreCursorWithFallback(kHelpCursor,
206 IDR_HELP_CURSOR, 8, 8);
207 case WebCursorInfo::TypeEastResize:
208 case WebCursorInfo::TypeEastPanning:
209 return GetCoreCursorWithFallback(kResizeEastCursor,
210 IDR_EAST_RESIZE_CURSOR, 14, 7);
211 case WebCursorInfo::TypeNorthResize:
212 case WebCursorInfo::TypeNorthPanning:
213 return GetCoreCursorWithFallback(kResizeNorthCursor,
214 IDR_NORTH_RESIZE_CURSOR, 7, 1);
215 case WebCursorInfo::TypeNorthEastResize:
216 case WebCursorInfo::TypeNorthEastPanning:
217 return GetCoreCursorWithFallback(kResizeNortheastCursor,
218 IDR_NORTHEAST_RESIZE_CURSOR, 14, 1);
219 case WebCursorInfo::TypeNorthWestResize:
220 case WebCursorInfo::TypeNorthWestPanning:
221 return GetCoreCursorWithFallback(kResizeNorthwestCursor,
222 IDR_NORTHWEST_RESIZE_CURSOR, 0, 0);
223 case WebCursorInfo::TypeSouthResize:
224 case WebCursorInfo::TypeSouthPanning:
225 return GetCoreCursorWithFallback(kResizeSouthCursor,
226 IDR_SOUTH_RESIZE_CURSOR, 7, 14);
227 case WebCursorInfo::TypeSouthEastResize:
228 case WebCursorInfo::TypeSouthEastPanning:
229 return GetCoreCursorWithFallback(kResizeSoutheastCursor,
230 IDR_SOUTHEAST_RESIZE_CURSOR, 14, 14);
231 case WebCursorInfo::TypeSouthWestResize:
232 case WebCursorInfo::TypeSouthWestPanning:
233 return GetCoreCursorWithFallback(kResizeSouthwestCursor,
234 IDR_SOUTHWEST_RESIZE_CURSOR, 1, 14);
235 case WebCursorInfo::TypeWestResize:
236 case WebCursorInfo::TypeWestPanning:
237 return GetCoreCursorWithFallback(kResizeWestCursor,
238 IDR_WEST_RESIZE_CURSOR, 1, 7);
239 case WebCursorInfo::TypeNorthSouthResize:
240 return GetCoreCursorWithFallback(kResizeNorthSouthCursor,
241 IDR_NORTHSOUTH_RESIZE_CURSOR, 7, 7);
242 case WebCursorInfo::TypeEastWestResize:
243 return GetCoreCursorWithFallback(kResizeEastWestCursor,
244 IDR_EASTWEST_RESIZE_CURSOR, 7, 7);
245 case WebCursorInfo::TypeNorthEastSouthWestResize:
246 return GetCoreCursorWithFallback(kResizeNortheastSouthwestCursor,
247 IDR_NORTHEASTSOUTHWEST_RESIZE_CURSOR,
249 case WebCursorInfo::TypeNorthWestSouthEastResize:
250 return GetCoreCursorWithFallback(kResizeNorthwestSoutheastCursor,
251 IDR_NORTHWESTSOUTHEAST_RESIZE_CURSOR,
253 case WebCursorInfo::TypeColumnResize:
254 return [NSCursor resizeLeftRightCursor];
255 case WebCursorInfo::TypeRowResize:
256 return [NSCursor resizeUpDownCursor];
257 case WebCursorInfo::TypeMiddlePanning:
258 case WebCursorInfo::TypeMove:
259 return GetCoreCursorWithFallback(kMoveCursor,
260 IDR_MOVE_CURSOR, 7, 7);
261 case WebCursorInfo::TypeVerticalText:
262 // IBeamCursorForVerticalLayout is >= 10.7.
263 if ([NSCursor respondsToSelector:@selector(IBeamCursorForVerticalLayout)])
264 return [NSCursor IBeamCursorForVerticalLayout];
266 return LoadCursor(IDR_VERTICALTEXT_CURSOR, 7, 7);
267 case WebCursorInfo::TypeCell:
268 return GetCoreCursorWithFallback(kCellCursor,
269 IDR_CELL_CURSOR, 7, 7);
270 case WebCursorInfo::TypeContextMenu:
271 return [NSCursor contextualMenuCursor];
272 case WebCursorInfo::TypeAlias:
273 return GetCoreCursorWithFallback(kMakeAliasCursor,
274 IDR_ALIAS_CURSOR, 11, 3);
275 case WebCursorInfo::TypeProgress:
276 return GetCoreCursorWithFallback(kBusyButClickableCursor,
277 IDR_PROGRESS_CURSOR, 3, 2);
278 case WebCursorInfo::TypeNoDrop:
279 case WebCursorInfo::TypeNotAllowed:
280 return [NSCursor operationNotAllowedCursor];
281 case WebCursorInfo::TypeCopy:
282 return [NSCursor dragCopyCursor];
283 case WebCursorInfo::TypeNone:
284 return LoadCursor(IDR_NONE_CURSOR, 7, 7);
285 case WebCursorInfo::TypeZoomIn:
286 return GetCoreCursorWithFallback(kZoomInCursor,
287 IDR_ZOOMIN_CURSOR, 7, 7);
288 case WebCursorInfo::TypeZoomOut:
289 return GetCoreCursorWithFallback(kZoomOutCursor,
290 IDR_ZOOMOUT_CURSOR, 7, 7);
291 case WebCursorInfo::TypeGrab:
292 return [NSCursor openHandCursor];
293 case WebCursorInfo::TypeGrabbing:
294 return [NSCursor closedHandCursor];
295 case WebCursorInfo::TypeCustom:
296 return CreateCustomCursor(
297 custom_data_, custom_size_, custom_scale_, hotspot_);
303 void WebCursor::InitFromNSCursor(NSCursor* cursor) {
304 CursorInfo cursor_info;
306 if ([cursor isEqual:[NSCursor arrowCursor]]) {
307 cursor_info.type = WebCursorInfo::TypePointer;
308 } else if ([cursor isEqual:[NSCursor IBeamCursor]]) {
309 cursor_info.type = WebCursorInfo::TypeIBeam;
310 } else if ([cursor isEqual:[NSCursor crosshairCursor]]) {
311 cursor_info.type = WebCursorInfo::TypeCross;
312 } else if ([cursor isEqual:[NSCursor pointingHandCursor]]) {
313 cursor_info.type = WebCursorInfo::TypeHand;
314 } else if ([cursor isEqual:[NSCursor resizeLeftCursor]]) {
315 cursor_info.type = WebCursorInfo::TypeWestResize;
316 } else if ([cursor isEqual:[NSCursor resizeRightCursor]]) {
317 cursor_info.type = WebCursorInfo::TypeEastResize;
318 } else if ([cursor isEqual:[NSCursor resizeLeftRightCursor]]) {
319 cursor_info.type = WebCursorInfo::TypeEastWestResize;
320 } else if ([cursor isEqual:[NSCursor resizeUpCursor]]) {
321 cursor_info.type = WebCursorInfo::TypeNorthResize;
322 } else if ([cursor isEqual:[NSCursor resizeDownCursor]]) {
323 cursor_info.type = WebCursorInfo::TypeSouthResize;
324 } else if ([cursor isEqual:[NSCursor resizeUpDownCursor]]) {
325 cursor_info.type = WebCursorInfo::TypeNorthSouthResize;
326 } else if ([cursor isEqual:[NSCursor openHandCursor]]) {
327 cursor_info.type = WebCursorInfo::TypeGrab;
328 } else if ([cursor isEqual:[NSCursor closedHandCursor]]) {
329 cursor_info.type = WebCursorInfo::TypeGrabbing;
330 } else if ([cursor isEqual:[NSCursor operationNotAllowedCursor]]) {
331 cursor_info.type = WebCursorInfo::TypeNotAllowed;
332 } else if ([cursor isEqual:[NSCursor dragCopyCursor]]) {
333 cursor_info.type = WebCursorInfo::TypeCopy;
334 } else if ([cursor isEqual:[NSCursor contextualMenuCursor]]) {
335 cursor_info.type = WebCursorInfo::TypeContextMenu;
337 [NSCursor respondsToSelector:@selector(IBeamCursorForVerticalLayout)] &&
338 [cursor isEqual:[NSCursor IBeamCursorForVerticalLayout]]) {
339 cursor_info.type = WebCursorInfo::TypeVerticalText;
341 // Also handles the [NSCursor disappearingItemCursor] case. Quick-and-dirty
342 // image conversion; TODO(avi): do better.
343 CGImageRef cg_image = nil;
344 NSImage* image = [cursor image];
345 for (id rep in [image representations]) {
346 if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
347 cg_image = [rep CGImage];
353 cursor_info.type = WebCursorInfo::TypeCustom;
354 NSPoint hot_spot = [cursor hotSpot];
355 cursor_info.hotspot = gfx::Point(hot_spot.x, hot_spot.y);
356 cursor_info.custom_image = gfx::CGImageToSkBitmap(cg_image);
358 cursor_info.type = WebCursorInfo::TypePointer;
362 InitFromCursorInfo(cursor_info);
365 void WebCursor::InitPlatformData() {
369 bool WebCursor::SerializePlatformData(Pickle* pickle) const {
373 bool WebCursor::DeserializePlatformData(PickleIterator* iter) {
377 bool WebCursor::IsPlatformDataEqual(const WebCursor& other) const {
381 void WebCursor::CleanupPlatformData() {
385 void WebCursor::CopyPlatformData(const WebCursor& other) {
389 } // namespace content