// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "webkit/glue/webcursor.h" #import <AppKit/AppKit.h> #include <Carbon/Carbon.h> #include "app/mac/nsimage_cache.h" #include "base/logging.h" #include "base/mac/scoped_cftyperef.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebCursorInfo.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebImage.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebSize.h" using WebKit::WebCursorInfo; using WebKit::WebImage; using WebKit::WebSize; namespace { // TODO: This image fetch can (and probably should) be serviced by the resource // resource bundle instead of going through the image cache. NSCursor* LoadCursor(const char* name, int x, int y) { NSString* file_name = [NSString stringWithUTF8String:name]; DCHECK(file_name); NSImage* cursor_image = app::mac::GetCachedImageWithName(file_name); DCHECK(cursor_image); return [[[NSCursor alloc] initWithImage:cursor_image hotSpot:NSMakePoint(x, y)] autorelease]; } CGImageRef CreateCGImageFromCustomData(const std::vector<char>& custom_data, const gfx::Size& custom_size) { base::mac::ScopedCFTypeRef<CGColorSpaceRef> cg_color( CGColorSpaceCreateDeviceRGB()); // This is safe since we're not going to draw into the context we're creating. void* data = const_cast<char*>(&custom_data[0]); // The settings here match SetCustomData() below; keep in sync. base::mac::ScopedCFTypeRef<CGContextRef> context( CGBitmapContextCreate(data, custom_size.width(), custom_size.height(), 8, custom_size.width()*4, cg_color.get(), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big)); return CGBitmapContextCreateImage(context.get()); } NSCursor* CreateCustomCursor(const std::vector<char>& custom_data, const gfx::Size& custom_size, const gfx::Point& hotspot) { // CG throws a cocoa exception if we try to create an empty image, which // results in an infinite loop. This CHECK ensures that we crash instead. CHECK(!custom_data.empty()); base::mac::ScopedCFTypeRef<CGImageRef> cg_image( CreateCGImageFromCustomData(custom_data, custom_size)); NSBitmapImageRep* ns_bitmap = [[NSBitmapImageRep alloc] initWithCGImage:cg_image.get()]; NSImage* cursor_image = [[NSImage alloc] init]; DCHECK(cursor_image); [cursor_image addRepresentation:ns_bitmap]; [ns_bitmap release]; NSCursor* cursor = [[NSCursor alloc] initWithImage:cursor_image hotSpot:NSMakePoint(hotspot.x(), hotspot.y())]; [cursor_image release]; return [cursor autorelease]; } } // namespace // We're matching Safari's cursor choices; see platform/mac/CursorMac.mm NSCursor* WebCursor::GetCursor() const { switch (type_) { case WebCursorInfo::TypePointer: return [NSCursor arrowCursor]; case WebCursorInfo::TypeCross: return LoadCursor("crossHairCursor", 11, 11); case WebCursorInfo::TypeHand: return LoadCursor("linkCursor", 6, 1); case WebCursorInfo::TypeIBeam: return [NSCursor IBeamCursor]; case WebCursorInfo::TypeWait: return LoadCursor("waitCursor", 7, 7); case WebCursorInfo::TypeHelp: return LoadCursor("helpCursor", 8, 8); case WebCursorInfo::TypeEastResize: case WebCursorInfo::TypeEastPanning: return LoadCursor("eastResizeCursor", 14, 7); case WebCursorInfo::TypeNorthResize: case WebCursorInfo::TypeNorthPanning: return LoadCursor("northResizeCursor", 7, 1); case WebCursorInfo::TypeNorthEastResize: case WebCursorInfo::TypeNorthEastPanning: return LoadCursor("northEastResizeCursor", 14, 1); case WebCursorInfo::TypeNorthWestResize: case WebCursorInfo::TypeNorthWestPanning: return LoadCursor("northWestResizeCursor", 0, 0); case WebCursorInfo::TypeSouthResize: case WebCursorInfo::TypeSouthPanning: return LoadCursor("southResizeCursor", 7, 14); case WebCursorInfo::TypeSouthEastResize: case WebCursorInfo::TypeSouthEastPanning: return LoadCursor("southEastResizeCursor", 14, 14); case WebCursorInfo::TypeSouthWestResize: case WebCursorInfo::TypeSouthWestPanning: return LoadCursor("southWestResizeCursor", 1, 14); case WebCursorInfo::TypeWestResize: case WebCursorInfo::TypeWestPanning: return LoadCursor("westResizeCursor", 1, 7); case WebCursorInfo::TypeNorthSouthResize: return LoadCursor("northSouthResizeCursor", 7, 7); case WebCursorInfo::TypeEastWestResize: return LoadCursor("eastWestResizeCursor", 7, 7); case WebCursorInfo::TypeNorthEastSouthWestResize: return LoadCursor("northEastSouthWestResizeCursor", 7, 7); case WebCursorInfo::TypeNorthWestSouthEastResize: return LoadCursor("northWestSouthEastResizeCursor", 7, 7); case WebCursorInfo::TypeColumnResize: return [NSCursor resizeLeftRightCursor]; case WebCursorInfo::TypeRowResize: return [NSCursor resizeUpDownCursor]; case WebCursorInfo::TypeMiddlePanning: case WebCursorInfo::TypeMove: return LoadCursor("moveCursor", 7, 7); case WebCursorInfo::TypeVerticalText: return LoadCursor("verticalTextCursor", 7, 7); case WebCursorInfo::TypeCell: return LoadCursor("cellCursor", 7, 7); case WebCursorInfo::TypeContextMenu: return LoadCursor("contextMenuCursor", 3, 2); case WebCursorInfo::TypeAlias: return LoadCursor("aliasCursor", 11, 3); case WebCursorInfo::TypeProgress: return LoadCursor("progressCursor", 3, 2); case WebCursorInfo::TypeNoDrop: return LoadCursor("noDropCursor", 3, 1); case WebCursorInfo::TypeCopy: return LoadCursor("copyCursor", 3, 2); case WebCursorInfo::TypeNone: return LoadCursor("noneCursor", 7, 7); case WebCursorInfo::TypeNotAllowed: return LoadCursor("notAllowedCursor", 11, 11); case WebCursorInfo::TypeZoomIn: return LoadCursor("zoomInCursor", 7, 7); case WebCursorInfo::TypeZoomOut: return LoadCursor("zoomOutCursor", 7, 7); case WebCursorInfo::TypeGrab: return [NSCursor openHandCursor]; case WebCursorInfo::TypeGrabbing: return [NSCursor closedHandCursor]; case WebCursorInfo::TypeCustom: return CreateCustomCursor(custom_data_, custom_size_, hotspot_); } NOTREACHED(); return nil; } gfx::NativeCursor WebCursor::GetNativeCursor() { return GetCursor(); } void WebCursor::InitFromThemeCursor(ThemeCursor cursor) { WebKit::WebCursorInfo cursor_info; switch (cursor) { case kThemeArrowCursor: cursor_info.type = WebCursorInfo::TypePointer; break; case kThemeCopyArrowCursor: cursor_info.type = WebCursorInfo::TypeCopy; break; case kThemeAliasArrowCursor: cursor_info.type = WebCursorInfo::TypeAlias; break; case kThemeContextualMenuArrowCursor: cursor_info.type = WebCursorInfo::TypeContextMenu; break; case kThemeIBeamCursor: cursor_info.type = WebCursorInfo::TypeIBeam; break; case kThemeCrossCursor: case kThemePlusCursor: cursor_info.type = WebCursorInfo::TypeCross; break; case kThemeWatchCursor: case kThemeSpinningCursor: cursor_info.type = WebCursorInfo::TypeWait; break; case kThemeClosedHandCursor: cursor_info.type = WebCursorInfo::TypeGrabbing; break; case kThemeOpenHandCursor: cursor_info.type = WebCursorInfo::TypeGrab; break; case kThemePointingHandCursor: case kThemeCountingUpHandCursor: case kThemeCountingDownHandCursor: case kThemeCountingUpAndDownHandCursor: cursor_info.type = WebCursorInfo::TypeHand; break; case kThemeResizeLeftCursor: cursor_info.type = WebCursorInfo::TypeWestResize; break; case kThemeResizeRightCursor: cursor_info.type = WebCursorInfo::TypeEastResize; break; case kThemeResizeLeftRightCursor: cursor_info.type = WebCursorInfo::TypeEastWestResize; break; case kThemeNotAllowedCursor: cursor_info.type = WebCursorInfo::TypeNotAllowed; break; case kThemeResizeUpCursor: cursor_info.type = WebCursorInfo::TypeNorthResize; break; case kThemeResizeDownCursor: cursor_info.type = WebCursorInfo::TypeSouthResize; break; case kThemeResizeUpDownCursor: cursor_info.type = WebCursorInfo::TypeNorthSouthResize; break; case kThemePoofCursor: // *shrug* default: cursor_info.type = WebCursorInfo::TypePointer; break; } InitFromCursorInfo(cursor_info); } void WebCursor::InitFromCursor(const Cursor* cursor) { // This conversion isn't perfect (in particular, the inversion effect of // data==1, mask==0 won't work). Not planning on fixing it. gfx::Size custom_size(16, 16); std::vector<char> raw_data; for (int row = 0; row < 16; ++row) { unsigned short data = cursor->data[row]; unsigned short mask = cursor->mask[row]; // The Core Endian flipper callback for 'CURS' doesn't flip Bits16 as if it // were a short (which it is), so we flip it here. data = ((data << 8) & 0xFF00) | ((data >> 8) & 0x00FF); mask = ((mask << 8) & 0xFF00) | ((mask >> 8) & 0x00FF); for (int bit = 0; bit < 16; ++bit) { if (data & 0x8000) { raw_data.push_back(0x00); raw_data.push_back(0x00); raw_data.push_back(0x00); } else { raw_data.push_back(0xFF); raw_data.push_back(0xFF); raw_data.push_back(0xFF); } if (mask & 0x8000) raw_data.push_back(0xFF); else raw_data.push_back(0x00); data <<= 1; mask <<= 1; } } base::mac::ScopedCFTypeRef<CGImageRef> cg_image( CreateCGImageFromCustomData(raw_data, custom_size)); WebKit::WebCursorInfo cursor_info; cursor_info.type = WebCursorInfo::TypeCustom; cursor_info.hotSpot = WebKit::WebPoint(cursor->hotSpot.h, cursor->hotSpot.v); cursor_info.customImage = cg_image.get(); InitFromCursorInfo(cursor_info); } void WebCursor::InitFromNSCursor(NSCursor* cursor) { WebKit::WebCursorInfo cursor_info; if ([cursor isEqual:[NSCursor arrowCursor]]) { cursor_info.type = WebCursorInfo::TypePointer; } else if ([cursor isEqual:[NSCursor IBeamCursor]]) { cursor_info.type = WebCursorInfo::TypeIBeam; } else if ([cursor isEqual:[NSCursor crosshairCursor]]) { cursor_info.type = WebCursorInfo::TypeCross; } else if ([cursor isEqual:[NSCursor pointingHandCursor]]) { cursor_info.type = WebCursorInfo::TypeHand; } else if ([cursor isEqual:[NSCursor resizeLeftCursor]]) { cursor_info.type = WebCursorInfo::TypeWestResize; } else if ([cursor isEqual:[NSCursor resizeRightCursor]]) { cursor_info.type = WebCursorInfo::TypeEastResize; } else if ([cursor isEqual:[NSCursor resizeLeftRightCursor]]) { cursor_info.type = WebCursorInfo::TypeEastWestResize; } else if ([cursor isEqual:[NSCursor resizeUpCursor]]) { cursor_info.type = WebCursorInfo::TypeNorthResize; } else if ([cursor isEqual:[NSCursor resizeDownCursor]]) { cursor_info.type = WebCursorInfo::TypeSouthResize; } else if ([cursor isEqual:[NSCursor resizeUpDownCursor]]) { cursor_info.type = WebCursorInfo::TypeNorthSouthResize; } else if ([cursor isEqual:[NSCursor openHandCursor]]) { cursor_info.type = WebCursorInfo::TypeGrab; } else if ([cursor isEqual:[NSCursor closedHandCursor]]) { cursor_info.type = WebCursorInfo::TypeGrabbing; } else { // Also handles the [NSCursor disappearingItemCursor] case. Quick-and-dirty // image conversion; TODO(avi): do better. CGImageRef cg_image = nil; NSImage* image = [cursor image]; for (id rep in [image representations]) { if ([rep isKindOfClass:[NSBitmapImageRep class]]) { cg_image = [rep CGImage]; break; } } if (cg_image) { cursor_info.type = WebCursorInfo::TypeCustom; NSPoint hot_spot = [cursor hotSpot]; cursor_info.hotSpot = WebKit::WebPoint(hot_spot.x, hot_spot.y); cursor_info.customImage = cg_image; } else { cursor_info.type = WebCursorInfo::TypePointer; } } InitFromCursorInfo(cursor_info); } void WebCursor::SetCustomData(const WebImage& image) { if (image.isNull()) return; base::mac::ScopedCFTypeRef<CGColorSpaceRef> cg_color( CGColorSpaceCreateDeviceRGB()); const WebSize& image_dimensions = image.size(); int image_width = image_dimensions.width; int image_height = image_dimensions.height; size_t size = image_height * image_width * 4; custom_data_.resize(size); custom_size_.set_width(image_width); custom_size_.set_height(image_height); // These settings match up with the code in CreateCustomCursor() above; keep // them in sync. // TODO(avi): test to ensure that the flags here are correct for RGBA base::mac::ScopedCFTypeRef<CGContextRef> context( CGBitmapContextCreate(&custom_data_[0], image_width, image_height, 8, image_width * 4, cg_color.get(), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big)); CGRect rect = CGRectMake(0, 0, image_width, image_height); CGContextDrawImage(context.get(), rect, image.getCGImageRef()); } void WebCursor::ImageFromCustomData(WebImage* image) const { if (custom_data_.empty()) return; base::mac::ScopedCFTypeRef<CGImageRef> cg_image( CreateCGImageFromCustomData(custom_data_, custom_size_)); *image = cg_image.get(); } void WebCursor::InitPlatformData() { return; } bool WebCursor::SerializePlatformData(Pickle* pickle) const { return true; } bool WebCursor::DeserializePlatformData(const Pickle* pickle, void** iter) { return true; } bool WebCursor::IsPlatformDataEqual(const WebCursor& other) const { return true; } void WebCursor::CleanupPlatformData() { return; } void WebCursor::CopyPlatformData(const WebCursor& other) { return; }