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