1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19
20 #include <gtk/gtk.h>
21
22 #include "widgets-types.h"
23
24 #include "gimpcursor.h"
25 #include "gimpwidgets-utils.h"
26
27 #include "cursors/gimp-tool-cursors.c"
28
29
30 #define cursor_default_hot_x 10
31 #define cursor_default_hot_y 10
32
33 #define cursor_mouse_hot_x 3
34 #define cursor_mouse_hot_y 2
35 #define cursor_crosshair_hot_x 15
36 #define cursor_crosshair_hot_y 15
37 #define cursor_zoom_hot_x 8
38 #define cursor_zoom_hot_y 8
39 #define cursor_color_picker_hot_x 1
40 #define cursor_color_picker_hot_y 30
41
42
43 typedef struct _GimpCursor GimpCursor;
44
45 struct _GimpCursor
46 {
47 const gchar *resource_name;
48 const gint hot_x;
49 const gint hot_y;
50
51 GdkPixbuf *pixbuf;
52 GdkPixbuf *pixbuf_x2;
53 };
54
55
56 static GimpCursor gimp_cursors[] =
57 {
58 /* these have to match up with enum GimpCursorType in widgets-enums.h */
59
60 {
61 "cursor-none",
62 cursor_default_hot_x, cursor_default_hot_y
63 },
64 {
65 "cursor-mouse",
66 cursor_mouse_hot_x, cursor_mouse_hot_y
67 },
68 {
69 "cursor-crosshair",
70 cursor_crosshair_hot_x, cursor_crosshair_hot_y
71 },
72 {
73 "cursor-crosshair-small",
74 cursor_default_hot_x, cursor_default_hot_y
75 },
76 {
77 "cursor-bad",
78 cursor_default_hot_x, cursor_default_hot_y
79 },
80 {
81 "cursor-move",
82 cursor_default_hot_x, cursor_default_hot_y
83 },
84 {
85 "cursor-zoom",
86 cursor_zoom_hot_x, cursor_zoom_hot_y
87 },
88 {
89 "cursor-color-picker",
90 cursor_color_picker_hot_x, cursor_color_picker_hot_y
91 },
92 {
93 "cursor-corner-top",
94 cursor_default_hot_x, cursor_default_hot_y
95 },
96 {
97 "cursor-corner-top-right",
98 cursor_default_hot_x, cursor_default_hot_y
99 },
100 {
101 "cursor-corner-right",
102 cursor_default_hot_x, cursor_default_hot_y
103 },
104 {
105 "cursor-corner-bottom-right",
106 cursor_default_hot_x, cursor_default_hot_y
107 },
108 {
109 "cursor-corner-bottom",
110 cursor_default_hot_x, cursor_default_hot_y
111 },
112 {
113 "cursor-corner-bottom-left",
114 cursor_default_hot_x, cursor_default_hot_y
115 },
116 {
117 "cursor-corner-left",
118 cursor_default_hot_x, cursor_default_hot_y
119 },
120 {
121 "cursor-corner-top-left",
122 cursor_default_hot_x, cursor_default_hot_y
123 },
124 {
125 "cursor-side-top",
126 cursor_default_hot_x, cursor_default_hot_y
127 },
128 {
129 "cursor-side-top-right",
130 cursor_default_hot_x, cursor_default_hot_y
131 },
132 {
133 "cursor-side-right",
134 cursor_default_hot_x, cursor_default_hot_y
135 },
136 {
137 "cursor-side-bottom-right",
138 cursor_default_hot_x, cursor_default_hot_y
139 },
140 {
141 "cursor-side-bottom",
142 cursor_default_hot_x, cursor_default_hot_y
143 },
144 {
145 "cursor-side-bottom-left",
146 cursor_default_hot_x, cursor_default_hot_y
147 },
148 {
149 "cursor-side-left",
150 cursor_default_hot_x, cursor_default_hot_y
151 },
152 {
153 "cursor-side-top-left",
154 cursor_default_hot_x, cursor_default_hot_y
155 }
156 };
157
158 static GimpCursor gimp_tool_cursors[] =
159 {
160 /* these have to match up with enum GimpToolCursorType in widgets-enums.h */
161
162 { NULL },
163 { "tool-rect-select" },
164 { "tool-ellipse-select" },
165 { "tool-free-select" },
166 { "tool-polygon-select" },
167 { "tool-fuzzy-select" },
168 { "tool-paths" },
169 { "tool-paths-anchor" },
170 { "tool-paths-control" },
171 { "tool-paths-segment" },
172 { "tool-iscissors" },
173 { "tool-move" },
174 { "tool-zoom" },
175 { "tool-crop" },
176 { "tool-resize" },
177 { "tool-rotate" },
178 { "tool-shear" },
179 { "tool-perspective" },
180 { "tool-transform-3d-camera" },
181 { "tool-flip-horizontal" },
182 { "tool-flip-vertical" },
183 { "tool-text" },
184 { "tool-color-picker" },
185 { "tool-bucket-fill" },
186 { "tool-gradient" },
187 { "tool-pencil" },
188 { "tool-paintbrush" },
189 { "tool-airbrush" },
190 { "tool-ink" },
191 { "tool-clone" },
192 { "tool-heal" },
193 { "tool-eraser" },
194 { "tool-smudge" },
195 { "tool-blur" },
196 { "tool-dodge" },
197 { "tool-burn" },
198 { "tool-measure" },
199 { "tool-warp" },
200 { "tool-hand" }
201 };
202
203 static GimpCursor gimp_cursor_modifiers[] =
204 {
205 /* these have to match up with enum GimpCursorModifier in widgets-enums.h */
206
207 { NULL },
208 { "modifier-bad" },
209 { "modifier-plus" },
210 { "modifier-minus" },
211 { "modifier-intersect" },
212 { "modifier-move" },
213 { "modifier-resize" },
214 { "modifier-rotate" },
215 { "modifier-zoom" },
216 { "modifier-control" },
217 { "modifier-anchor" },
218 { "modifier-foreground" },
219 { "modifier-background" },
220 { "modifier-pattern" },
221 { "modifier-join" },
222 { "modifier-select" }
223 };
224
225
226 static const GdkPixbuf *
get_cursor_pixbuf(GimpCursor * cursor,gint scale_factor)227 get_cursor_pixbuf (GimpCursor *cursor,
228 gint scale_factor)
229 {
230 gchar *resource_path;
231 GError *error = NULL;
232
233 if (! cursor->pixbuf)
234 {
235 resource_path = g_strconcat ("/org/gimp/tool-cursors/",
236 cursor->resource_name,
237 ".png", NULL);
238
239 cursor->pixbuf = gdk_pixbuf_new_from_resource (resource_path, &error);
240
241 if (! cursor->pixbuf)
242 {
243 g_critical ("Failed to create cursor image '%s': %s",
244 resource_path, error->message);
245 g_clear_error (&error);
246 }
247
248 g_free (resource_path);
249 }
250
251 if (scale_factor == 2 && ! cursor->pixbuf_x2)
252 {
253 resource_path = g_strconcat ("/org/gimp/tool-cursors/",
254 cursor->resource_name,
255 "-x2.png", NULL);
256
257 cursor->pixbuf_x2 = gdk_pixbuf_new_from_resource (resource_path, &error);
258
259 if (! cursor->pixbuf_x2)
260 {
261 /* no critical here until we actually have the cursor files */
262 g_printerr ("Failed to create scaled cursor image '%s' "
263 "falling back to upscaling default cursor: %s\n",
264 resource_path, error->message);
265 g_clear_error (&error);
266
267 if (cursor->pixbuf)
268 {
269 gint width = gdk_pixbuf_get_width (cursor->pixbuf);
270 gint height = gdk_pixbuf_get_height (cursor->pixbuf);
271
272 cursor->pixbuf_x2 = gdk_pixbuf_scale_simple (cursor->pixbuf,
273 width * 2,
274 height * 2,
275 GDK_INTERP_NEAREST);
276 }
277 }
278
279 g_free (resource_path);
280 }
281
282 if (scale_factor == 2)
283 return cursor->pixbuf_x2;
284 else
285 return cursor->pixbuf;
286 }
287
288 GdkCursor *
gimp_cursor_new(GdkWindow * window,GimpHandedness cursor_handedness,GimpCursorType cursor_type,GimpToolCursorType tool_cursor,GimpCursorModifier modifier)289 gimp_cursor_new (GdkWindow *window,
290 GimpHandedness cursor_handedness,
291 GimpCursorType cursor_type,
292 GimpToolCursorType tool_cursor,
293 GimpCursorModifier modifier)
294 {
295 GdkDisplay *display;
296 GimpCursor *bmcursor = NULL;
297 GimpCursor *bmmodifier = NULL;
298 GimpCursor *bmtool = NULL;
299 GdkCursor *cursor;
300 GdkPixbuf *pixbuf;
301 gint scale_factor;
302 gint hot_x;
303 gint hot_y;
304
305 g_return_val_if_fail (GDK_IS_WINDOW (window), NULL);
306 g_return_val_if_fail (cursor_type < GIMP_CURSOR_LAST, NULL);
307
308 display = gdk_window_get_display (window);
309
310 if (cursor_type <= (GimpCursorType) GDK_LAST_CURSOR)
311 return gdk_cursor_new_for_display (display, (GdkCursorType) cursor_type);
312
313 g_return_val_if_fail (cursor_type >= GIMP_CURSOR_NONE, NULL);
314
315 /* disallow the small tool cursor with some cursors
316 */
317 if (cursor_type <= GIMP_CURSOR_NONE ||
318 cursor_type == GIMP_CURSOR_CROSSHAIR ||
319 cursor_type == GIMP_CURSOR_ZOOM ||
320 cursor_type == GIMP_CURSOR_COLOR_PICKER ||
321 cursor_type >= GIMP_CURSOR_LAST)
322 {
323 tool_cursor = GIMP_TOOL_CURSOR_NONE;
324 }
325
326 /* don't allow anything with the empty cursor
327 */
328 if (cursor_type == GIMP_CURSOR_NONE)
329 {
330 tool_cursor = GIMP_TOOL_CURSOR_NONE;
331 modifier = GIMP_CURSOR_MODIFIER_NONE;
332 }
333
334 /* some more sanity checks
335 */
336 if (cursor_type == GIMP_CURSOR_MOVE &&
337 modifier == GIMP_CURSOR_MODIFIER_MOVE)
338 {
339 modifier = GIMP_CURSOR_MODIFIER_NONE;
340 }
341
342 /* when cursor is "corner" or "side" sides must be exchanged for
343 * left-hand-mice-flipping of pixbuf below
344 */
345
346 if (cursor_handedness == GIMP_HANDEDNESS_LEFT)
347 {
348 switch (cursor_type)
349 {
350 case GIMP_CURSOR_CORNER_TOP_LEFT:
351 cursor_type = GIMP_CURSOR_CORNER_TOP_RIGHT; break;
352
353 case GIMP_CURSOR_CORNER_TOP_RIGHT:
354 cursor_type = GIMP_CURSOR_CORNER_TOP_LEFT; break;
355
356 case GIMP_CURSOR_CORNER_LEFT:
357 cursor_type = GIMP_CURSOR_CORNER_RIGHT; break;
358
359 case GIMP_CURSOR_CORNER_RIGHT:
360 cursor_type = GIMP_CURSOR_CORNER_LEFT; break;
361
362 case GIMP_CURSOR_CORNER_BOTTOM_LEFT:
363 cursor_type = GIMP_CURSOR_CORNER_BOTTOM_RIGHT; break;
364
365 case GIMP_CURSOR_CORNER_BOTTOM_RIGHT:
366 cursor_type = GIMP_CURSOR_CORNER_BOTTOM_LEFT; break;
367
368 case GIMP_CURSOR_SIDE_TOP_LEFT:
369 cursor_type = GIMP_CURSOR_SIDE_TOP_RIGHT; break;
370
371 case GIMP_CURSOR_SIDE_TOP_RIGHT:
372 cursor_type = GIMP_CURSOR_SIDE_TOP_LEFT; break;
373
374 case GIMP_CURSOR_SIDE_LEFT:
375 cursor_type = GIMP_CURSOR_SIDE_RIGHT; break;
376
377 case GIMP_CURSOR_SIDE_RIGHT:
378 cursor_type = GIMP_CURSOR_SIDE_LEFT; break;
379
380 case GIMP_CURSOR_SIDE_BOTTOM_LEFT:
381 cursor_type = GIMP_CURSOR_SIDE_BOTTOM_RIGHT; break;
382
383 case GIMP_CURSOR_SIDE_BOTTOM_RIGHT:
384 cursor_type = GIMP_CURSOR_SIDE_BOTTOM_LEFT; break;
385
386 default:
387 break;
388 }
389 }
390
391 /* prepare the main cursor */
392
393 cursor_type -= GIMP_CURSOR_NONE;
394 bmcursor = &gimp_cursors[cursor_type];
395
396 /* prepare the tool cursor */
397
398 if (tool_cursor > GIMP_TOOL_CURSOR_NONE &&
399 tool_cursor < GIMP_TOOL_CURSOR_LAST)
400 {
401 bmtool = &gimp_tool_cursors[tool_cursor];
402 }
403
404 /* prepare the cursor modifier */
405
406 if (modifier > GIMP_CURSOR_MODIFIER_NONE &&
407 modifier < GIMP_CURSOR_MODIFIER_LAST)
408 {
409 bmmodifier = &gimp_cursor_modifiers[modifier];
410 }
411
412 scale_factor = 1;
413
414 /* guess HiDPI */
415 {
416 GdkScreen *screen = gdk_window_get_screen (window);
417 gdouble xres, yres;
418
419 gimp_get_monitor_resolution (screen,
420 gdk_screen_get_monitor_at_window (screen, window),
421 &xres, &yres);
422
423 if ((xres + yres) / 2.0 > 250.0)
424 scale_factor = 2;
425 }
426
427 pixbuf = gdk_pixbuf_copy (get_cursor_pixbuf (bmcursor, scale_factor));
428
429 if (bmmodifier || bmtool)
430 {
431 gint width = gdk_pixbuf_get_width (pixbuf);
432 gint height = gdk_pixbuf_get_height (pixbuf);
433
434 if (bmmodifier)
435 gdk_pixbuf_composite (get_cursor_pixbuf (bmmodifier, scale_factor),
436 pixbuf,
437 0, 0, width, height,
438 0.0, 0.0, 1.0, 1.0,
439 GDK_INTERP_NEAREST, 200);
440
441 if (bmtool)
442 gdk_pixbuf_composite (get_cursor_pixbuf (bmtool, scale_factor),
443 pixbuf,
444 0, 0, width, height,
445 0.0, 0.0, 1.0, 1.0,
446 GDK_INTERP_NEAREST, 200);
447 }
448
449 hot_x = bmcursor->hot_x;
450 hot_y = bmcursor->hot_y;
451
452 /* flip the cursor if mouse setting is left-handed */
453
454 if (cursor_handedness == GIMP_HANDEDNESS_LEFT)
455 {
456 GdkPixbuf *flipped = gdk_pixbuf_flip (pixbuf, TRUE);
457 gint width = gdk_pixbuf_get_width (flipped);
458
459 g_object_unref (pixbuf);
460 pixbuf = flipped;
461
462 hot_x = (width - 1) - hot_x;
463 }
464
465 cursor = gdk_cursor_new_from_pixbuf (display, pixbuf,
466 hot_x * scale_factor,
467 hot_y * scale_factor);
468
469 g_object_unref (pixbuf);
470
471 return cursor;
472 }
473
474 void
gimp_cursor_set(GtkWidget * widget,GimpHandedness cursor_handedness,GimpCursorType cursor_type,GimpToolCursorType tool_cursor,GimpCursorModifier modifier)475 gimp_cursor_set (GtkWidget *widget,
476 GimpHandedness cursor_handedness,
477 GimpCursorType cursor_type,
478 GimpToolCursorType tool_cursor,
479 GimpCursorModifier modifier)
480 {
481 GdkWindow *window;
482 GdkCursor *cursor;
483
484 g_return_if_fail (GTK_IS_WIDGET (widget));
485 g_return_if_fail (gtk_widget_get_realized (widget));
486
487 window = gtk_widget_get_window (widget);
488
489 cursor = gimp_cursor_new (window,
490 cursor_handedness,
491 cursor_type,
492 tool_cursor,
493 modifier);
494 gdk_window_set_cursor (window, cursor);
495 gdk_cursor_unref (cursor);
496
497 gdk_display_flush (gdk_window_get_display (window));
498 }
499
500 GimpCursorType
gimp_cursor_rotate(GimpCursorType cursor,gdouble angle)501 gimp_cursor_rotate (GimpCursorType cursor,
502 gdouble angle)
503 {
504 if (cursor >= GIMP_CURSOR_CORNER_TOP &&
505 cursor <= GIMP_CURSOR_SIDE_TOP_LEFT)
506 {
507 gint offset = (gint) (angle / 45 + 0.5);
508
509 if (cursor < GIMP_CURSOR_SIDE_TOP)
510 {
511 cursor += offset;
512
513 if (cursor > GIMP_CURSOR_CORNER_TOP_LEFT)
514 cursor -= 8;
515 }
516 else
517 {
518 cursor += offset;
519
520 if (cursor > GIMP_CURSOR_SIDE_TOP_LEFT)
521 cursor -= 8;
522 }
523 }
524
525 return cursor;
526 }
527