1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5#include "imgIContainer.h"
6#include "nsCocoaUtils.h"
7#include "nsCursorManager.h"
8#include "nsObjCExceptions.h"
9#include <math.h>
10
11static nsCursorManager* gInstance;
12static CGFloat sCurrentCursorScaleFactor = 0.0f;
13static nsIWidget::Cursor sCurrentCursor;
14static constexpr nsCursor kCustomCursor = eCursorCount;
15
16/*! @category nsCursorManager(PrivateMethods)
17    Private methods for the cursor manager class.
18*/
19@interface nsCursorManager (PrivateMethods)
20/*! @method     getCursor:
21    @abstract   Get a reference to the native Mac representation of a cursor.
22    @discussion Gets a reference to the Mac native implementation of a cursor.
23                If the cursor has been requested before, it is retreived from the cursor cache,
24                otherwise it is created and cached.
25    @param      aCursor the cursor to get
26    @result     the Mac native implementation of the cursor
27*/
28- (nsMacCursor*)getCursor:(nsCursor)aCursor;
29
30/*! @method     setMacCursor:
31 @abstract   Set the current Mac native cursor
32 @discussion Sets the current cursor - this routine is what actually causes the cursor to change.
33 The argument is retained and the old cursor is released.
34 @param      aMacCursor the cursor to set
35 @result     NS_OK
36 */
37- (nsresult)setMacCursor:(nsMacCursor*)aMacCursor;
38
39/*! @method     createCursor:
40    @abstract   Create a Mac native representation of a cursor.
41    @discussion Creates a version of the Mac native representation of this cursor
42    @param      aCursor the cursor to create
43    @result     the Mac native implementation of the cursor
44*/
45+ (nsMacCursor*)createCursor:(enum nsCursor)aCursor;
46
47@end
48
49@implementation nsCursorManager
50
51+ (nsCursorManager*)sharedInstance {
52  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
53
54  if (!gInstance) {
55    gInstance = [[nsCursorManager alloc] init];
56  }
57  return gInstance;
58
59  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
60}
61
62+ (void)dispose {
63  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
64
65  [gInstance release];
66  gInstance = nil;
67
68  NS_OBJC_END_TRY_IGNORE_BLOCK;
69}
70
71+ (nsMacCursor*)createCursor:(enum nsCursor)aCursor {
72  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
73
74  switch (aCursor) {
75    SEL cursorSelector;
76    case eCursor_standard:
77      return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor];
78    case eCursor_wait:
79    case eCursor_spinning: {
80      return [nsMacCursor cursorWithCursor:[NSCursor busyButClickableCursor] type:aCursor];
81    }
82    case eCursor_select:
83      return [nsMacCursor cursorWithCursor:[NSCursor IBeamCursor] type:aCursor];
84    case eCursor_hyperlink:
85      return [nsMacCursor cursorWithCursor:[NSCursor pointingHandCursor] type:aCursor];
86    case eCursor_crosshair:
87      return [nsMacCursor cursorWithCursor:[NSCursor crosshairCursor] type:aCursor];
88    case eCursor_move:
89      return [nsMacCursor cursorWithImageNamed:@"move" hotSpot:NSMakePoint(12, 12) type:aCursor];
90    case eCursor_help:
91      return [nsMacCursor cursorWithImageNamed:@"help" hotSpot:NSMakePoint(12, 12) type:aCursor];
92    case eCursor_copy:
93      cursorSelector = @selector(dragCopyCursor);
94      return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
95                                               ? [NSCursor performSelector:cursorSelector]
96                                               : [NSCursor arrowCursor]
97                                      type:aCursor];
98    case eCursor_alias:
99      cursorSelector = @selector(dragLinkCursor);
100      return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
101                                               ? [NSCursor performSelector:cursorSelector]
102                                               : [NSCursor arrowCursor]
103                                      type:aCursor];
104    case eCursor_context_menu:
105      cursorSelector = @selector(contextualMenuCursor);
106      return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
107                                               ? [NSCursor performSelector:cursorSelector]
108                                               : [NSCursor arrowCursor]
109                                      type:aCursor];
110    case eCursor_cell:
111      return [nsMacCursor cursorWithImageNamed:@"cell" hotSpot:NSMakePoint(12, 12) type:aCursor];
112    case eCursor_grab:
113      return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor] type:aCursor];
114    case eCursor_grabbing:
115      return [nsMacCursor cursorWithCursor:[NSCursor closedHandCursor] type:aCursor];
116    case eCursor_zoom_in:
117      return [nsMacCursor cursorWithImageNamed:@"zoomIn" hotSpot:NSMakePoint(10, 10) type:aCursor];
118    case eCursor_zoom_out:
119      return [nsMacCursor cursorWithImageNamed:@"zoomOut" hotSpot:NSMakePoint(10, 10) type:aCursor];
120    case eCursor_vertical_text:
121      return [nsMacCursor cursorWithImageNamed:@"vtIBeam" hotSpot:NSMakePoint(12, 11) type:aCursor];
122    case eCursor_all_scroll:
123      return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor] type:aCursor];
124    case eCursor_not_allowed:
125    case eCursor_no_drop:
126      cursorSelector = @selector(operationNotAllowedCursor);
127      return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
128                                               ? [NSCursor performSelector:cursorSelector]
129                                               : [NSCursor arrowCursor]
130                                      type:aCursor];
131    // Resize Cursors:
132    // North
133    case eCursor_n_resize:
134      return [nsMacCursor cursorWithCursor:[NSCursor resizeUpCursor] type:aCursor];
135    // North East
136    case eCursor_ne_resize:
137      return [nsMacCursor cursorWithImageNamed:@"sizeNE" hotSpot:NSMakePoint(12, 11) type:aCursor];
138    // East
139    case eCursor_e_resize:
140      return [nsMacCursor cursorWithCursor:[NSCursor resizeRightCursor] type:aCursor];
141    // South East
142    case eCursor_se_resize:
143      return [nsMacCursor cursorWithImageNamed:@"sizeSE" hotSpot:NSMakePoint(12, 12) type:aCursor];
144    // South
145    case eCursor_s_resize:
146      return [nsMacCursor cursorWithCursor:[NSCursor resizeDownCursor] type:aCursor];
147    // South West
148    case eCursor_sw_resize:
149      return [nsMacCursor cursorWithImageNamed:@"sizeSW" hotSpot:NSMakePoint(10, 12) type:aCursor];
150    // West
151    case eCursor_w_resize:
152      return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftCursor] type:aCursor];
153    // North West
154    case eCursor_nw_resize:
155      return [nsMacCursor cursorWithImageNamed:@"sizeNW" hotSpot:NSMakePoint(11, 11) type:aCursor];
156    // North & South
157    case eCursor_ns_resize:
158      return [nsMacCursor cursorWithCursor:[NSCursor resizeUpDownCursor] type:aCursor];
159    // East & West
160    case eCursor_ew_resize:
161      return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftRightCursor] type:aCursor];
162    // North East & South West
163    case eCursor_nesw_resize:
164      return [nsMacCursor cursorWithImageNamed:@"sizeNESW"
165                                       hotSpot:NSMakePoint(12, 12)
166                                          type:aCursor];
167    // North West & South East
168    case eCursor_nwse_resize:
169      return [nsMacCursor cursorWithImageNamed:@"sizeNWSE"
170                                       hotSpot:NSMakePoint(12, 12)
171                                          type:aCursor];
172    // Column Resize
173    case eCursor_col_resize:
174      return [nsMacCursor cursorWithImageNamed:@"colResize"
175                                       hotSpot:NSMakePoint(12, 12)
176                                          type:aCursor];
177    // Row Resize
178    case eCursor_row_resize:
179      return [nsMacCursor cursorWithImageNamed:@"rowResize"
180                                       hotSpot:NSMakePoint(12, 12)
181                                          type:aCursor];
182    default:
183      return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor];
184  }
185
186  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
187}
188
189- (id)init {
190  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
191
192  if ((self = [super init])) {
193    mCursors = [[NSMutableDictionary alloc] initWithCapacity:25];
194  }
195  return self;
196
197  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
198}
199
200- (nsresult)setNonCustomCursor:(const nsIWidget::Cursor&)aCursor {
201  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
202
203  [self setMacCursor:[self getCursor:aCursor.mDefaultCursor]];
204
205  sCurrentCursor = aCursor;
206  return NS_OK;
207
208  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
209}
210
211- (nsresult)setMacCursor:(nsMacCursor*)aMacCursor {
212  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
213
214  nsCursor oldType = [mCurrentMacCursor type];
215  nsCursor newType = [aMacCursor type];
216  if (oldType != newType) {
217    if (newType == eCursor_none) {
218      [NSCursor hide];
219    } else if (oldType == eCursor_none) {
220      [NSCursor unhide];
221    }
222  }
223
224  if (mCurrentMacCursor != aMacCursor || ![mCurrentMacCursor isSet]) {
225    [aMacCursor retain];
226    [mCurrentMacCursor unset];
227    [aMacCursor set];
228    [mCurrentMacCursor release];
229    mCurrentMacCursor = aMacCursor;
230  }
231
232  return NS_OK;
233
234  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
235}
236
237- (nsresult)setCustomCursor:(const nsIWidget::Cursor&)aCursor
238          widgetScaleFactor:(CGFloat)scaleFactor {
239  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
240
241  // As the user moves the mouse, this gets called repeatedly with the same aCursorImage
242  if (sCurrentCursor == aCursor && sCurrentCursorScaleFactor == scaleFactor && mCurrentMacCursor) {
243    // Native dragging can unset our cursor apparently (see bug 1739352).
244    if (MOZ_UNLIKELY(![mCurrentMacCursor isSet])) {
245      [mCurrentMacCursor set];
246    }
247    return NS_OK;
248  }
249
250  sCurrentCursor = aCursor;
251  sCurrentCursorScaleFactor = scaleFactor;
252
253  if (!aCursor.IsCustom()) {
254    return NS_ERROR_FAILURE;
255  }
256
257  nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
258  // prevent DoS attacks
259  if (size.width > 128 || size.height > 128) {
260    return NS_ERROR_FAILURE;
261  }
262
263  NSImage* cursorImage;
264  nsresult rv = nsCocoaUtils::CreateNSImageFromImageContainer(
265      aCursor.mContainer, imgIContainer::FRAME_FIRST, nullptr, &cursorImage, scaleFactor);
266  if (NS_FAILED(rv) || !cursorImage) {
267    return NS_ERROR_FAILURE;
268  }
269
270  {
271    NSSize cocoaSize = NSMakeSize(size.width, size.height);
272    [cursorImage setSize:cocoaSize];
273    [[[cursorImage representations] objectAtIndex:0] setSize:cocoaSize];
274  }
275
276  // if the hotspot is nonsensical, make it 0,0
277  uint32_t hotspotX = aCursor.mHotspotX > (uint32_t(size.width) - 1) ? 0 : aCursor.mHotspotX;
278  uint32_t hotspotY = aCursor.mHotspotY > (uint32_t(size.height) - 1) ? 0 : aCursor.mHotspotY;
279  NSPoint hotSpot = ::NSMakePoint(hotspotX, hotspotY);
280  [self setMacCursor:[nsMacCursor cursorWithCursor:[[NSCursor alloc] initWithImage:cursorImage
281                                                                           hotSpot:hotSpot]
282                                              type:kCustomCursor]];
283  [cursorImage release];
284  return NS_OK;
285
286  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
287}
288
289- (nsMacCursor*)getCursor:(enum nsCursor)aCursor {
290  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
291
292  nsMacCursor* result = [mCursors objectForKey:[NSNumber numberWithInt:aCursor]];
293  if (!result) {
294    result = [nsCursorManager createCursor:aCursor];
295    [mCursors setObject:result forKey:[NSNumber numberWithInt:aCursor]];
296  }
297  return result;
298
299  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
300}
301
302- (void)dealloc {
303  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
304
305  [mCurrentMacCursor unset];
306  [mCurrentMacCursor release];
307  [mCursors release];
308  sCurrentCursor = {};
309  [super dealloc];
310
311  NS_OBJC_END_TRY_IGNORE_BLOCK;
312}
313
314@end
315