blob: da80efa35e058f909959d1525ea5c53bb98651d6 [file] [log] [blame]
// 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;
}