Home | History | Annotate | Download | only in cursors
      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