summaryrefslogtreecommitdiffstats
path: root/widget/cocoa/nsCursorManager.mm
blob: 6596df25a3b27ac3e9477fe4c7afed333e4012fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "imgIContainer.h"
#include "nsCocoaUtils.h"
#include "nsCursorManager.h"
#include "nsObjCExceptions.h"
#include <math.h>

static nsCursorManager* gInstance;
static CGFloat sCurrentCursorScaleFactor = 0.0f;
static nsIWidget::Cursor sCurrentCursor;
static constexpr nsCursor kCustomCursor = eCursorCount;

/*! @category nsCursorManager(PrivateMethods)
    Private methods for the cursor manager class.
*/
@interface nsCursorManager (PrivateMethods)
/*! @method     getCursor:
    @abstract   Get a reference to the native Mac representation of a cursor.
    @discussion Gets a reference to the Mac native implementation of a cursor.
                If the cursor has been requested before, it is retreived from the cursor cache,
                otherwise it is created and cached.
    @param      aCursor the cursor to get
    @result     the Mac native implementation of the cursor
*/
- (nsMacCursor*)getCursor:(nsCursor)aCursor;

/*! @method     setMacCursor:
 @abstract   Set the current Mac native cursor
 @discussion Sets the current cursor - this routine is what actually causes the cursor to change.
 The argument is retained and the old cursor is released.
 @param      aMacCursor the cursor to set
 @result     NS_OK
 */
- (nsresult)setMacCursor:(nsMacCursor*)aMacCursor;

/*! @method     createCursor:
    @abstract   Create a Mac native representation of a cursor.
    @discussion Creates a version of the Mac native representation of this cursor
    @param      aCursor the cursor to create
    @result     the Mac native implementation of the cursor
*/
+ (nsMacCursor*)createCursor:(enum nsCursor)aCursor;

@end

@implementation nsCursorManager

+ (nsCursorManager*)sharedInstance {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  if (!gInstance) {
    gInstance = [[nsCursorManager alloc] init];
  }
  return gInstance;

  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
}

+ (void)dispose {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  [gInstance release];
  gInstance = nil;

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

+ (nsMacCursor*)createCursor:(enum nsCursor)aCursor {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  switch (aCursor) {
    case eCursor_standard:
      return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor];
    case eCursor_wait:
    case eCursor_spinning: {
      return [nsMacCursor cursorWithCursor:[NSCursor busyButClickableCursor] type:aCursor];
    }
    case eCursor_select:
      return [nsMacCursor cursorWithCursor:[NSCursor IBeamCursor] type:aCursor];
    case eCursor_hyperlink:
      return [nsMacCursor cursorWithCursor:[NSCursor pointingHandCursor] type:aCursor];
    case eCursor_crosshair:
      return [nsMacCursor cursorWithCursor:[NSCursor crosshairCursor] type:aCursor];
    case eCursor_move:
      return [nsMacCursor cursorWithImageNamed:@"move" hotSpot:NSMakePoint(12, 12) type:aCursor];
    case eCursor_help:
      return [nsMacCursor cursorWithImageNamed:@"help" hotSpot:NSMakePoint(12, 12) type:aCursor];
    case eCursor_copy: {
      SEL cursorSelector = @selector(dragCopyCursor);
      return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
                                               ? [NSCursor performSelector:cursorSelector]
                                               : [NSCursor arrowCursor]
                                      type:aCursor];
    }
    case eCursor_alias: {
      SEL cursorSelector = @selector(dragLinkCursor);
      return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
                                               ? [NSCursor performSelector:cursorSelector]
                                               : [NSCursor arrowCursor]
                                      type:aCursor];
    }
    case eCursor_context_menu: {
      SEL cursorSelector = @selector(contextualMenuCursor);
      return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
                                               ? [NSCursor performSelector:cursorSelector]
                                               : [NSCursor arrowCursor]
                                      type:aCursor];
    }
    case eCursor_cell:
      return [nsMacCursor cursorWithImageNamed:@"cell" hotSpot:NSMakePoint(12, 12) type:aCursor];
    case eCursor_grab:
      return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor] type:aCursor];
    case eCursor_grabbing:
      return [nsMacCursor cursorWithCursor:[NSCursor closedHandCursor] type:aCursor];
    case eCursor_zoom_in:
      return [nsMacCursor cursorWithImageNamed:@"zoomIn" hotSpot:NSMakePoint(10, 10) type:aCursor];
    case eCursor_zoom_out:
      return [nsMacCursor cursorWithImageNamed:@"zoomOut" hotSpot:NSMakePoint(10, 10) type:aCursor];
    case eCursor_vertical_text:
      return [nsMacCursor cursorWithImageNamed:@"vtIBeam" hotSpot:NSMakePoint(12, 11) type:aCursor];
    case eCursor_all_scroll:
      return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor] type:aCursor];
    case eCursor_not_allowed:
    case eCursor_no_drop: {
      SEL cursorSelector = @selector(operationNotAllowedCursor);
      return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
                                               ? [NSCursor performSelector:cursorSelector]
                                               : [NSCursor arrowCursor]
                                      type:aCursor];
    }
    // Resize Cursors:
    // North
    case eCursor_n_resize:
      return [nsMacCursor cursorWithCursor:[NSCursor resizeUpCursor] type:aCursor];
    // North East
    case eCursor_ne_resize:
      return [nsMacCursor cursorWithImageNamed:@"sizeNE" hotSpot:NSMakePoint(12, 11) type:aCursor];
    // East
    case eCursor_e_resize:
      return [nsMacCursor cursorWithCursor:[NSCursor resizeRightCursor] type:aCursor];
    // South East
    case eCursor_se_resize:
      return [nsMacCursor cursorWithImageNamed:@"sizeSE" hotSpot:NSMakePoint(12, 12) type:aCursor];
    // South
    case eCursor_s_resize:
      return [nsMacCursor cursorWithCursor:[NSCursor resizeDownCursor] type:aCursor];
    // South West
    case eCursor_sw_resize:
      return [nsMacCursor cursorWithImageNamed:@"sizeSW" hotSpot:NSMakePoint(10, 12) type:aCursor];
    // West
    case eCursor_w_resize:
      return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftCursor] type:aCursor];
    // North West
    case eCursor_nw_resize:
      return [nsMacCursor cursorWithImageNamed:@"sizeNW" hotSpot:NSMakePoint(11, 11) type:aCursor];
    // North & South
    case eCursor_ns_resize:
      return [nsMacCursor cursorWithCursor:[NSCursor resizeUpDownCursor] type:aCursor];
    // East & West
    case eCursor_ew_resize:
      return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftRightCursor] type:aCursor];
    // North East & South West
    case eCursor_nesw_resize:
      return [nsMacCursor cursorWithImageNamed:@"sizeNESW"
                                       hotSpot:NSMakePoint(12, 12)
                                          type:aCursor];
    // North West & South East
    case eCursor_nwse_resize:
      return [nsMacCursor cursorWithImageNamed:@"sizeNWSE"
                                       hotSpot:NSMakePoint(12, 12)
                                          type:aCursor];
    // Column Resize
    case eCursor_col_resize:
      return [nsMacCursor cursorWithImageNamed:@"colResize"
                                       hotSpot:NSMakePoint(12, 12)
                                          type:aCursor];
    // Row Resize
    case eCursor_row_resize:
      return [nsMacCursor cursorWithImageNamed:@"rowResize"
                                       hotSpot:NSMakePoint(12, 12)
                                          type:aCursor];
    default:
      return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor];
  }

  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
}

- (id)init {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  if ((self = [super init])) {
    mCursors = [[NSMutableDictionary alloc] initWithCapacity:25];
  }
  return self;

  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
}

- (nsresult)setNonCustomCursor:(const nsIWidget::Cursor&)aCursor {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  [self setMacCursor:[self getCursor:aCursor.mDefaultCursor]];

  sCurrentCursor = aCursor;
  return NS_OK;

  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
}

- (nsresult)setMacCursor:(nsMacCursor*)aMacCursor {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  nsCursor oldType = [mCurrentMacCursor type];
  nsCursor newType = [aMacCursor type];
  if (oldType != newType) {
    if (newType == eCursor_none) {
      [NSCursor hide];
    } else if (oldType == eCursor_none) {
      [NSCursor unhide];
    }
  }

  if (mCurrentMacCursor != aMacCursor || ![mCurrentMacCursor isSet]) {
    [aMacCursor retain];
    [mCurrentMacCursor unset];
    [aMacCursor set];
    [mCurrentMacCursor release];
    mCurrentMacCursor = aMacCursor;
  }

  return NS_OK;

  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
}

- (nsresult)setCustomCursor:(const nsIWidget::Cursor&)aCursor
          widgetScaleFactor:(CGFloat)scaleFactor {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  // As the user moves the mouse, this gets called repeatedly with the same aCursorImage
  if (sCurrentCursor == aCursor && sCurrentCursorScaleFactor == scaleFactor && mCurrentMacCursor) {
    // Native dragging can unset our cursor apparently (see bug 1739352).
    if (MOZ_UNLIKELY(![mCurrentMacCursor isSet])) {
      [mCurrentMacCursor set];
    }
    return NS_OK;
  }

  sCurrentCursor = aCursor;
  sCurrentCursorScaleFactor = scaleFactor;

  if (!aCursor.IsCustom()) {
    return NS_ERROR_FAILURE;
  }

  nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
  // prevent DoS attacks
  if (size.width > 128 || size.height > 128) {
    return NS_ERROR_FAILURE;
  }

  NSImage* cursorImage;
  nsresult rv = nsCocoaUtils::CreateNSImageFromImageContainer(
      aCursor.mContainer, imgIContainer::FRAME_FIRST, nullptr, nullptr, &cursorImage, scaleFactor);
  if (NS_FAILED(rv) || !cursorImage) {
    return NS_ERROR_FAILURE;
  }

  {
    NSSize cocoaSize = NSMakeSize(size.width, size.height);
    [cursorImage setSize:cocoaSize];
    [[[cursorImage representations] objectAtIndex:0] setSize:cocoaSize];
  }

  // if the hotspot is nonsensical, make it 0,0
  uint32_t hotspotX = aCursor.mHotspotX > (uint32_t(size.width) - 1) ? 0 : aCursor.mHotspotX;
  uint32_t hotspotY = aCursor.mHotspotY > (uint32_t(size.height) - 1) ? 0 : aCursor.mHotspotY;
  NSPoint hotSpot = ::NSMakePoint(hotspotX, hotspotY);
  [self setMacCursor:[nsMacCursor cursorWithCursor:[[NSCursor alloc] initWithImage:cursorImage
                                                                           hotSpot:hotSpot]
                                              type:kCustomCursor]];
  [cursorImage release];
  return NS_OK;

  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
}

- (nsMacCursor*)getCursor:(enum nsCursor)aCursor {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  nsMacCursor* result = [mCursors objectForKey:[NSNumber numberWithInt:aCursor]];
  if (!result) {
    result = [nsCursorManager createCursor:aCursor];
    [mCursors setObject:result forKey:[NSNumber numberWithInt:aCursor]];
  }
  return result;

  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
}

- (void)dealloc {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  [mCurrentMacCursor unset];
  [mCurrentMacCursor release];
  [mCursors release];
  sCurrentCursor = {};
  [super dealloc];

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

@end