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