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 17 using WebKit::WebCursorInfo; 18 using WebKit::WebImage; 19 using WebKit::WebSize; 20 21 namespace { 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. 25 NSCursor* 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 34 CGImageRef 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 53 NSCursor* 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 81 NSCursor* 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 167 gfx::NativeCursor WebCursor::GetNativeCursor() { 168 return GetCursor(); 169 } 170 171 void 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 240 void 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 285 void 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 337 void 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 369 void 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 378 void WebCursor::InitPlatformData() { 379 return; 380 } 381 382 bool WebCursor::SerializePlatformData(Pickle* pickle) const { 383 return true; 384 } 385 386 bool WebCursor::DeserializePlatformData(const Pickle* pickle, void** iter) { 387 return true; 388 } 389 390 bool WebCursor::IsPlatformDataEqual(const WebCursor& other) const { 391 return true; 392 } 393 394 void WebCursor::CleanupPlatformData() { 395 return; 396 } 397 398 void WebCursor::CopyPlatformData(const WebCursor& other) { 399 return; 400 } 401