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 "content/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 "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" 21 22 23 using blink::WebCursorInfo; 24 using blink::WebSize; 25 26 // Private interface to CoreCursor, as of Mac OS X 10.7. This is essentially the 27 // implementation of WKCursor in WebKitSystemInterface. 28 29 enum { 30 kArrowCursor = 0, 31 kIBeamCursor = 1, 32 kMakeAliasCursor = 2, 33 kOperationNotAllowedCursor = 3, 34 kBusyButClickableCursor = 4, 35 kCopyCursor = 5, 36 kClosedHandCursor = 11, 37 kOpenHandCursor = 12, 38 kPointingHandCursor = 13, 39 kCountingUpHandCursor = 14, 40 kCountingDownHandCursor = 15, 41 kCountingUpAndDownHandCursor = 16, 42 kResizeLeftCursor = 17, 43 kResizeRightCursor = 18, 44 kResizeLeftRightCursor = 19, 45 kCrosshairCursor = 20, 46 kResizeUpCursor = 21, 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, 64 kMoveCursor = 39, 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. 69 }; 70 typedef long long CrCoreCursorType; 71 72 @interface CrCoreCursor : NSCursor { 73 @private 74 CrCoreCursorType type_; 75 } 76 77 + (id)cursorWithType:(CrCoreCursorType)type; 78 - (id)initWithType:(CrCoreCursorType)type; 79 - (CrCoreCursorType)_coreCursorType; 80 81 @end 82 83 @implementation CrCoreCursor 84 85 + (id)cursorWithType:(CrCoreCursorType)type { 86 NSCursor* cursor = [[CrCoreCursor alloc] initWithType:type]; 87 if ([cursor image]) 88 return [cursor autorelease]; 89 90 [cursor release]; 91 return nil; 92 } 93 94 - (id)initWithType:(CrCoreCursorType)type { 95 if ((self = [super init])) { 96 type_ = type; 97 } 98 return self; 99 } 100 101 - (CrCoreCursorType)_coreCursorType { 102 return type_; 103 } 104 105 @end 106 107 namespace { 108 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]; 116 } 117 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, 121 int resource_id, 122 int hotspot_x, 123 int hotspot_y) { 124 if (base::mac::IsOSLionOrLater()) { 125 NSCursor* cursor = [CrCoreCursor cursorWithType:type]; 126 if (cursor) 127 return cursor; 128 } 129 130 return LoadCursor(resource_id, hotspot_x, hotspot_y); 131 } 132 133 NSCursor* CreateCustomCursor(const std::vector<char>& custom_data, 134 const gfx::Size& custom_size, 135 float custom_scale, 136 const gfx::Point& hotspot) { 137 // If the data is missing, leave the backing transparent. 138 void* data = NULL; 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 142 // creating. 143 data = const_cast<char*>(&custom_data[0]); 144 data_size = custom_data.size(); 145 } 146 147 // If the size is empty, use a 1x1 transparent image. 148 gfx::Size size = custom_size; 149 if (size.IsEmpty()) { 150 size.SetSize(1, 1); 151 data = NULL; 152 } 153 154 SkBitmap bitmap; 155 bitmap.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height()); 156 bitmap.allocPixels(); 157 if (data) 158 memcpy(bitmap.getAddr32(0, 0), data, data_size); 159 else 160 bitmap.eraseARGB(0, 0, 0, 0); 161 162 // Convert from pixels to view units. 163 if (custom_scale == 0) 164 custom_scale = 1; 165 NSSize dip_size = NSSizeFromCGSize(gfx::ToFlooredSize( 166 gfx::ScaleSize(custom_size, 1 / custom_scale)).ToCGSize()); 167 NSPoint dip_hotspot = NSPointFromCGPoint(gfx::ToFlooredPoint( 168 gfx::ScalePoint(hotspot, 1 / custom_scale)).ToCGPoint()); 169 170 // Both the image and its representation need to have the same size for 171 // cursors to appear in high resolution on retina displays. Note that the 172 // size of a representation is not the same as pixelsWide or pixelsHigh. 173 NSImage* cursor_image = gfx::SkBitmapToNSImage(bitmap); 174 [cursor_image setSize:dip_size]; 175 [[[cursor_image representations] objectAtIndex:0] setSize:dip_size]; 176 177 NSCursor* cursor = [[NSCursor alloc] initWithImage:cursor_image 178 hotSpot:dip_hotspot]; 179 180 return [cursor autorelease]; 181 } 182 183 } // namespace 184 185 namespace content { 186 187 // Match Safari's cursor choices; see platform/mac/CursorMac.mm . 188 gfx::NativeCursor WebCursor::GetNativeCursor() { 189 switch (type_) { 190 case WebCursorInfo::TypePointer: 191 return [NSCursor arrowCursor]; 192 case WebCursorInfo::TypeCross: 193 return [NSCursor crosshairCursor]; 194 case WebCursorInfo::TypeHand: 195 // If >= 10.7, the pointingHandCursor has a shadow so use it. Otherwise 196 // use the custom one. 197 if (base::mac::IsOSLionOrLater()) 198 return [NSCursor pointingHandCursor]; 199 else 200 return LoadCursor(IDR_LINK_CURSOR, 6, 1); 201 case WebCursorInfo::TypeIBeam: 202 return [NSCursor IBeamCursor]; 203 case WebCursorInfo::TypeWait: 204 return GetCoreCursorWithFallback(kBusyButClickableCursor, 205 IDR_WAIT_CURSOR, 7, 7); 206 case WebCursorInfo::TypeHelp: 207 return GetCoreCursorWithFallback(kHelpCursor, 208 IDR_HELP_CURSOR, 8, 8); 209 case WebCursorInfo::TypeEastResize: 210 case WebCursorInfo::TypeEastPanning: 211 return GetCoreCursorWithFallback(kResizeEastCursor, 212 IDR_EAST_RESIZE_CURSOR, 14, 7); 213 case WebCursorInfo::TypeNorthResize: 214 case WebCursorInfo::TypeNorthPanning: 215 return GetCoreCursorWithFallback(kResizeNorthCursor, 216 IDR_NORTH_RESIZE_CURSOR, 7, 1); 217 case WebCursorInfo::TypeNorthEastResize: 218 case WebCursorInfo::TypeNorthEastPanning: 219 return GetCoreCursorWithFallback(kResizeNortheastCursor, 220 IDR_NORTHEAST_RESIZE_CURSOR, 14, 1); 221 case WebCursorInfo::TypeNorthWestResize: 222 case WebCursorInfo::TypeNorthWestPanning: 223 return GetCoreCursorWithFallback(kResizeNorthwestCursor, 224 IDR_NORTHWEST_RESIZE_CURSOR, 0, 0); 225 case WebCursorInfo::TypeSouthResize: 226 case WebCursorInfo::TypeSouthPanning: 227 return GetCoreCursorWithFallback(kResizeSouthCursor, 228 IDR_SOUTH_RESIZE_CURSOR, 7, 14); 229 case WebCursorInfo::TypeSouthEastResize: 230 case WebCursorInfo::TypeSouthEastPanning: 231 return GetCoreCursorWithFallback(kResizeSoutheastCursor, 232 IDR_SOUTHEAST_RESIZE_CURSOR, 14, 14); 233 case WebCursorInfo::TypeSouthWestResize: 234 case WebCursorInfo::TypeSouthWestPanning: 235 return GetCoreCursorWithFallback(kResizeSouthwestCursor, 236 IDR_SOUTHWEST_RESIZE_CURSOR, 1, 14); 237 case WebCursorInfo::TypeWestResize: 238 case WebCursorInfo::TypeWestPanning: 239 return GetCoreCursorWithFallback(kResizeWestCursor, 240 IDR_WEST_RESIZE_CURSOR, 1, 7); 241 case WebCursorInfo::TypeNorthSouthResize: 242 return GetCoreCursorWithFallback(kResizeNorthSouthCursor, 243 IDR_NORTHSOUTH_RESIZE_CURSOR, 7, 7); 244 case WebCursorInfo::TypeEastWestResize: 245 return GetCoreCursorWithFallback(kResizeEastWestCursor, 246 IDR_EASTWEST_RESIZE_CURSOR, 7, 7); 247 case WebCursorInfo::TypeNorthEastSouthWestResize: 248 return GetCoreCursorWithFallback(kResizeNortheastSouthwestCursor, 249 IDR_NORTHEASTSOUTHWEST_RESIZE_CURSOR, 250 7, 7); 251 case WebCursorInfo::TypeNorthWestSouthEastResize: 252 return GetCoreCursorWithFallback(kResizeNorthwestSoutheastCursor, 253 IDR_NORTHWESTSOUTHEAST_RESIZE_CURSOR, 254 7, 7); 255 case WebCursorInfo::TypeColumnResize: 256 return [NSCursor resizeLeftRightCursor]; 257 case WebCursorInfo::TypeRowResize: 258 return [NSCursor resizeUpDownCursor]; 259 case WebCursorInfo::TypeMiddlePanning: 260 case WebCursorInfo::TypeMove: 261 return GetCoreCursorWithFallback(kMoveCursor, 262 IDR_MOVE_CURSOR, 7, 7); 263 case WebCursorInfo::TypeVerticalText: 264 // IBeamCursorForVerticalLayout is >= 10.7. 265 if ([NSCursor respondsToSelector:@selector(IBeamCursorForVerticalLayout)]) 266 return [NSCursor IBeamCursorForVerticalLayout]; 267 else 268 return LoadCursor(IDR_VERTICALTEXT_CURSOR, 7, 7); 269 case WebCursorInfo::TypeCell: 270 return GetCoreCursorWithFallback(kCellCursor, 271 IDR_CELL_CURSOR, 7, 7); 272 case WebCursorInfo::TypeContextMenu: 273 return [NSCursor contextualMenuCursor]; 274 case WebCursorInfo::TypeAlias: 275 return GetCoreCursorWithFallback(kMakeAliasCursor, 276 IDR_ALIAS_CURSOR, 11, 3); 277 case WebCursorInfo::TypeProgress: 278 return GetCoreCursorWithFallback(kBusyButClickableCursor, 279 IDR_PROGRESS_CURSOR, 3, 2); 280 case WebCursorInfo::TypeNoDrop: 281 case WebCursorInfo::TypeNotAllowed: 282 return [NSCursor operationNotAllowedCursor]; 283 case WebCursorInfo::TypeCopy: 284 return [NSCursor dragCopyCursor]; 285 case WebCursorInfo::TypeNone: 286 return LoadCursor(IDR_NONE_CURSOR, 7, 7); 287 case WebCursorInfo::TypeZoomIn: 288 return GetCoreCursorWithFallback(kZoomInCursor, 289 IDR_ZOOMIN_CURSOR, 7, 7); 290 case WebCursorInfo::TypeZoomOut: 291 return GetCoreCursorWithFallback(kZoomOutCursor, 292 IDR_ZOOMOUT_CURSOR, 7, 7); 293 case WebCursorInfo::TypeGrab: 294 return [NSCursor openHandCursor]; 295 case WebCursorInfo::TypeGrabbing: 296 return [NSCursor closedHandCursor]; 297 case WebCursorInfo::TypeCustom: 298 return CreateCustomCursor( 299 custom_data_, custom_size_, custom_scale_, hotspot_); 300 } 301 NOTREACHED(); 302 return nil; 303 } 304 305 void WebCursor::InitFromNSCursor(NSCursor* cursor) { 306 CursorInfo cursor_info; 307 308 if ([cursor isEqual:[NSCursor arrowCursor]]) { 309 cursor_info.type = WebCursorInfo::TypePointer; 310 } else if ([cursor isEqual:[NSCursor IBeamCursor]]) { 311 cursor_info.type = WebCursorInfo::TypeIBeam; 312 } else if ([cursor isEqual:[NSCursor crosshairCursor]]) { 313 cursor_info.type = WebCursorInfo::TypeCross; 314 } else if ([cursor isEqual:[NSCursor pointingHandCursor]]) { 315 cursor_info.type = WebCursorInfo::TypeHand; 316 } else if ([cursor isEqual:[NSCursor resizeLeftCursor]]) { 317 cursor_info.type = WebCursorInfo::TypeWestResize; 318 } else if ([cursor isEqual:[NSCursor resizeRightCursor]]) { 319 cursor_info.type = WebCursorInfo::TypeEastResize; 320 } else if ([cursor isEqual:[NSCursor resizeLeftRightCursor]]) { 321 cursor_info.type = WebCursorInfo::TypeEastWestResize; 322 } else if ([cursor isEqual:[NSCursor resizeUpCursor]]) { 323 cursor_info.type = WebCursorInfo::TypeNorthResize; 324 } else if ([cursor isEqual:[NSCursor resizeDownCursor]]) { 325 cursor_info.type = WebCursorInfo::TypeSouthResize; 326 } else if ([cursor isEqual:[NSCursor resizeUpDownCursor]]) { 327 cursor_info.type = WebCursorInfo::TypeNorthSouthResize; 328 } else if ([cursor isEqual:[NSCursor openHandCursor]]) { 329 cursor_info.type = WebCursorInfo::TypeGrab; 330 } else if ([cursor isEqual:[NSCursor closedHandCursor]]) { 331 cursor_info.type = WebCursorInfo::TypeGrabbing; 332 } else if ([cursor isEqual:[NSCursor operationNotAllowedCursor]]) { 333 cursor_info.type = WebCursorInfo::TypeNotAllowed; 334 } else if ([cursor isEqual:[NSCursor dragCopyCursor]]) { 335 cursor_info.type = WebCursorInfo::TypeCopy; 336 } else if ([cursor isEqual:[NSCursor contextualMenuCursor]]) { 337 cursor_info.type = WebCursorInfo::TypeContextMenu; 338 } else if ( 339 [NSCursor respondsToSelector:@selector(IBeamCursorForVerticalLayout)] && 340 [cursor isEqual:[NSCursor IBeamCursorForVerticalLayout]]) { 341 cursor_info.type = WebCursorInfo::TypeVerticalText; 342 } else { 343 // Also handles the [NSCursor disappearingItemCursor] case. Quick-and-dirty 344 // image conversion; TODO(avi): do better. 345 CGImageRef cg_image = nil; 346 NSImage* image = [cursor image]; 347 for (id rep in [image representations]) { 348 if ([rep isKindOfClass:[NSBitmapImageRep class]]) { 349 cg_image = [rep CGImage]; 350 break; 351 } 352 } 353 354 if (cg_image) { 355 cursor_info.type = WebCursorInfo::TypeCustom; 356 NSPoint hot_spot = [cursor hotSpot]; 357 cursor_info.hotspot = gfx::Point(hot_spot.x, hot_spot.y); 358 cursor_info.custom_image = gfx::CGImageToSkBitmap(cg_image); 359 } else { 360 cursor_info.type = WebCursorInfo::TypePointer; 361 } 362 } 363 364 InitFromCursorInfo(cursor_info); 365 } 366 367 void WebCursor::InitPlatformData() { 368 return; 369 } 370 371 bool WebCursor::SerializePlatformData(Pickle* pickle) const { 372 return true; 373 } 374 375 bool WebCursor::DeserializePlatformData(PickleIterator* iter) { 376 return true; 377 } 378 379 bool WebCursor::IsPlatformDataEqual(const WebCursor& other) const { 380 return true; 381 } 382 383 void WebCursor::CleanupPlatformData() { 384 return; 385 } 386 387 void WebCursor::CopyPlatformData(const WebCursor& other) { 388 return; 389 } 390 391 } // namespace content 392