1 | /* GDK - The GIMP Drawing Kit |
2 | * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library 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 GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | */ |
17 | |
18 | /* |
19 | * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS |
20 | * file for a list of people on the GTK+ Team. See the ChangeLog |
21 | * files for a list of changes. These files are distributed with |
22 | * GTK+ at ftp://ftp.gtk.org/pub/gtk/. |
23 | */ |
24 | |
25 | #include "config.h" |
26 | |
27 | #define GDK_PIXBUF_ENABLE_BACKEND |
28 | #include <gdk-pixbuf/gdk-pixbuf.h> |
29 | |
30 | #include "gdkcursor.h" |
31 | #include "gdkcursorprivate.h" |
32 | #include "gdktexture.h" |
33 | #include "gdkintl.h" |
34 | |
35 | #include <math.h> |
36 | #include <errno.h> |
37 | |
38 | /** |
39 | * GdkCursor: |
40 | * |
41 | * `GdkCursor` is used to create and destroy cursors. |
42 | * |
43 | * Cursors are immutable objects, so once you created them, there is no way |
44 | * to modify them later. You should create a new cursor when you want to change |
45 | * something about it. |
46 | * |
47 | * Cursors by themselves are not very interesting: they must be bound to a |
48 | * window for users to see them. This is done with [method@Gdk.Surface.set_cursor] |
49 | * or [method@Gdk.Surface.set_device_cursor]. Applications will typically |
50 | * use higher-level GTK functions such as [method@Gtk.Widget.set_cursor] instead. |
51 | * |
52 | * Cursors are not bound to a given [class@Gdk.Display], so they can be shared. |
53 | * However, the appearance of cursors may vary when used on different |
54 | * platforms. |
55 | * |
56 | * ## Named and texture cursors |
57 | * |
58 | * There are multiple ways to create cursors. The platform's own cursors |
59 | * can be created with [ctor@Gdk.Cursor.new_from_name]. That function lists |
60 | * the commonly available names that are shared with the CSS specification. |
61 | * Other names may be available, depending on the platform in use. On some |
62 | * platforms, what images are used for named cursors may be influenced by |
63 | * the cursor theme. |
64 | * |
65 | * Another option to create a cursor is to use [ctor@Gdk.Cursor.new_from_texture] |
66 | * and provide an image to use for the cursor. |
67 | * |
68 | * To ease work with unsupported cursors, a fallback cursor can be provided. |
69 | * If a [class@Gdk.Surface] cannot use a cursor because of the reasons mentioned |
70 | * above, it will try the fallback cursor. Fallback cursors can themselves have |
71 | * fallback cursors again, so it is possible to provide a chain of progressively |
72 | * easier to support cursors. If none of the provided cursors can be supported, |
73 | * the default cursor will be the ultimate fallback. |
74 | */ |
75 | |
76 | enum { |
77 | PROP_0, |
78 | PROP_FALLBACK, |
79 | PROP_HOTSPOT_X, |
80 | PROP_HOTSPOT_Y, |
81 | PROP_NAME, |
82 | PROP_TEXTURE, |
83 | }; |
84 | |
85 | G_DEFINE_TYPE (GdkCursor, gdk_cursor, G_TYPE_OBJECT) |
86 | |
87 | static void |
88 | gdk_cursor_get_property (GObject *object, |
89 | guint prop_id, |
90 | GValue *value, |
91 | GParamSpec *pspec) |
92 | { |
93 | GdkCursor *cursor = GDK_CURSOR (object); |
94 | |
95 | switch (prop_id) |
96 | { |
97 | case PROP_FALLBACK: |
98 | g_value_set_object (value, v_object: cursor->fallback); |
99 | break; |
100 | case PROP_HOTSPOT_X: |
101 | g_value_set_int (value, v_int: cursor->hotspot_x); |
102 | break; |
103 | case PROP_HOTSPOT_Y: |
104 | g_value_set_int (value, v_int: cursor->hotspot_y); |
105 | break; |
106 | case PROP_NAME: |
107 | g_value_set_string (value, v_string: cursor->name); |
108 | break; |
109 | case PROP_TEXTURE: |
110 | g_value_set_object (value, v_object: cursor->texture); |
111 | break; |
112 | default: |
113 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
114 | break; |
115 | } |
116 | } |
117 | |
118 | static void |
119 | gdk_cursor_set_property (GObject *object, |
120 | guint prop_id, |
121 | const GValue *value, |
122 | GParamSpec *pspec) |
123 | { |
124 | GdkCursor *cursor = GDK_CURSOR (object); |
125 | |
126 | switch (prop_id) |
127 | { |
128 | case PROP_FALLBACK: |
129 | cursor->fallback = g_value_dup_object (value); |
130 | break; |
131 | case PROP_HOTSPOT_X: |
132 | cursor->hotspot_x = g_value_get_int (value); |
133 | break; |
134 | case PROP_HOTSPOT_Y: |
135 | cursor->hotspot_y = g_value_get_int (value); |
136 | break; |
137 | case PROP_NAME: |
138 | cursor->name = g_value_dup_string (value); |
139 | break; |
140 | case PROP_TEXTURE: |
141 | cursor->texture = g_value_dup_object (value); |
142 | break; |
143 | default: |
144 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
145 | break; |
146 | } |
147 | } |
148 | |
149 | static void |
150 | gdk_cursor_finalize (GObject *object) |
151 | { |
152 | GdkCursor *cursor = GDK_CURSOR (object); |
153 | |
154 | g_free (mem: cursor->name); |
155 | g_clear_object (&cursor->texture); |
156 | g_clear_object (&cursor->fallback); |
157 | |
158 | G_OBJECT_CLASS (gdk_cursor_parent_class)->finalize (object); |
159 | } |
160 | |
161 | static void |
162 | gdk_cursor_class_init (GdkCursorClass *cursor_class) |
163 | { |
164 | GObjectClass *object_class = G_OBJECT_CLASS (cursor_class); |
165 | |
166 | object_class->get_property = gdk_cursor_get_property; |
167 | object_class->set_property = gdk_cursor_set_property; |
168 | object_class->finalize = gdk_cursor_finalize; |
169 | |
170 | /** |
171 | * GdkCursor:fallback: (attributes org.gtk.Property.get=gdk_cursor_get_fallback) |
172 | * |
173 | * Cursor to fall back to if this cursor cannot be displayed. |
174 | */ |
175 | g_object_class_install_property (oclass: object_class, |
176 | property_id: PROP_FALLBACK, |
177 | pspec: g_param_spec_object (name: "fallback" , |
178 | P_("Fallback" ), |
179 | P_("Cursor image to fall back to if this cursor cannot be displayed" ), |
180 | GDK_TYPE_CURSOR, |
181 | flags: G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | |
182 | G_PARAM_STATIC_STRINGS)); |
183 | |
184 | /** |
185 | * GdkCursor:hotspot-x: (attributes org.gtk.Property.get=gdk_cursor_get_hotspot_x) |
186 | * |
187 | * X position of the cursor hotspot in the cursor image. |
188 | */ |
189 | g_object_class_install_property (oclass: object_class, |
190 | property_id: PROP_HOTSPOT_X, |
191 | pspec: g_param_spec_int (name: "hotspot-x" , |
192 | P_("Hotspot X" ), |
193 | P_("Horizontal offset of the cursor hotspot" ), |
194 | minimum: 0, G_MAXINT, default_value: 0, |
195 | flags: G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | |
196 | G_PARAM_STATIC_STRINGS)); |
197 | |
198 | /** |
199 | * GdkCursor:hotspot-y: (attributes org.gtk.Property.get=gdk_cursor_get_hotspot_y) |
200 | * |
201 | * Y position of the cursor hotspot in the cursor image. |
202 | */ |
203 | g_object_class_install_property (oclass: object_class, |
204 | property_id: PROP_HOTSPOT_Y, |
205 | pspec: g_param_spec_int (name: "hotspot-y" , |
206 | P_("Hotspot Y" ), |
207 | P_("Vertical offset of the cursor hotspot" ), |
208 | minimum: 0, G_MAXINT, default_value: 0, |
209 | flags: G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | |
210 | G_PARAM_STATIC_STRINGS)); |
211 | |
212 | /** |
213 | * GdkCursor:name: (attributes org.gtk.Property.get=gdk_cursor_get_name) |
214 | * |
215 | * Name of this this cursor. |
216 | * |
217 | * The name will be %NULL if the cursor was created from a texture. |
218 | */ |
219 | g_object_class_install_property (oclass: object_class, |
220 | property_id: PROP_NAME, |
221 | pspec: g_param_spec_string (name: "name" , |
222 | P_("Name" ), |
223 | P_("Name of this cursor" ), |
224 | NULL, |
225 | flags: G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | |
226 | G_PARAM_STATIC_STRINGS)); |
227 | |
228 | /** |
229 | * GdkCursor:texture: |
230 | * |
231 | * The texture displayed by this cursor. |
232 | * |
233 | * The texture will be %NULL if the cursor was created from a name. |
234 | */ |
235 | g_object_class_install_property (oclass: object_class, |
236 | property_id: PROP_TEXTURE, |
237 | pspec: g_param_spec_object (name: "texture" , |
238 | P_("Texture" ), |
239 | P_("The texture displayed by this cursor" ), |
240 | GDK_TYPE_TEXTURE, |
241 | flags: G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | |
242 | G_PARAM_STATIC_STRINGS)); |
243 | } |
244 | |
245 | static void |
246 | gdk_cursor_init (GdkCursor *cursor) |
247 | { |
248 | } |
249 | |
250 | guint |
251 | gdk_cursor_hash (gconstpointer pointer) |
252 | { |
253 | const GdkCursor *cursor = pointer; |
254 | guint hash; |
255 | |
256 | if (cursor->fallback) |
257 | hash = gdk_cursor_hash (pointer: cursor->fallback) << 16; |
258 | else |
259 | hash = 0; |
260 | |
261 | if (cursor->name) |
262 | hash ^= g_str_hash (v: cursor->name); |
263 | else if (cursor->texture) |
264 | hash ^= g_direct_hash (v: cursor->texture); |
265 | |
266 | hash ^= (cursor->hotspot_x << 8) | cursor->hotspot_y; |
267 | |
268 | return hash; |
269 | } |
270 | |
271 | gboolean |
272 | gdk_cursor_equal (gconstpointer a, |
273 | gconstpointer b) |
274 | { |
275 | const GdkCursor *ca = a; |
276 | const GdkCursor *cb = b; |
277 | |
278 | if ((ca->fallback != NULL) != (cb->fallback != NULL)) |
279 | return FALSE; |
280 | if (ca->fallback != NULL && !gdk_cursor_equal (a: ca->fallback, b: cb->fallback)) |
281 | return FALSE; |
282 | |
283 | if (g_strcmp0 (str1: ca->name, str2: cb->name) != 0) |
284 | return FALSE; |
285 | |
286 | if (ca->texture != cb->texture) |
287 | return FALSE; |
288 | |
289 | if (ca->hotspot_x != cb->hotspot_x || |
290 | ca->hotspot_y != cb->hotspot_y) |
291 | return FALSE; |
292 | |
293 | return TRUE; |
294 | } |
295 | |
296 | /** |
297 | * gdk_cursor_new_from_name: |
298 | * @name: the name of the cursor |
299 | * @fallback: (nullable): %NULL or the `GdkCursor` to fall back to when |
300 | * this one cannot be supported |
301 | * |
302 | * Creates a new cursor by looking up @name in the current cursor |
303 | * theme. |
304 | * |
305 | * A recommended set of cursor names that will work across different |
306 | * platforms can be found in the CSS specification: |
307 | * |
308 | * | | | | | |
309 | * | --- | --- | ---- | --- | |
310 | * | "none" | ![](default_cursor.png) "default" | ![](help_cursor.png) "help" | ![](pointer_cursor.png) "pointer" | |
311 | * | ![](context_menu_cursor.png) "context-menu" | ![](progress_cursor.png) "progress" | ![](wait_cursor.png) "wait" | ![](cell_cursor.png) "cell" | |
312 | * | ![](crosshair_cursor.png) "crosshair" | ![](text_cursor.png) "text" | ![](vertical_text_cursor.png) "vertical-text" | ![](alias_cursor.png) "alias" | |
313 | * | ![](copy_cursor.png) "copy" | ![](no_drop_cursor.png) "no-drop" | ![](move_cursor.png) "move" | ![](not_allowed_cursor.png) "not-allowed" | |
314 | * | ![](grab_cursor.png) "grab" | ![](grabbing_cursor.png) "grabbing" | ![](all_scroll_cursor.png) "all-scroll" | ![](col_resize_cursor.png) "col-resize" | |
315 | * | ![](row_resize_cursor.png) "row-resize" | ![](n_resize_cursor.png) "n-resize" | ![](e_resize_cursor.png) "e-resize" | ![](s_resize_cursor.png) "s-resize" | |
316 | * | ![](w_resize_cursor.png) "w-resize" | ![](ne_resize_cursor.png) "ne-resize" | ![](nw_resize_cursor.png) "nw-resize" | ![](sw_resize_cursor.png) "sw-resize" | |
317 | * | ![](se_resize_cursor.png) "se-resize" | ![](ew_resize_cursor.png) "ew-resize" | ![](ns_resize_cursor.png) "ns-resize" | ![](nesw_resize_cursor.png) "nesw-resize" | |
318 | * | ![](nwse_resize_cursor.png) "nwse-resize" | ![](zoom_in_cursor.png) "zoom-in" | ![](zoom_out_cursor.png) "zoom-out" | | |
319 | * |
320 | * Returns: (nullable): a new `GdkCursor`, or %NULL if there is no |
321 | * cursor with the given name |
322 | */ |
323 | GdkCursor * |
324 | gdk_cursor_new_from_name (const char *name, |
325 | GdkCursor *fallback) |
326 | { |
327 | g_return_val_if_fail (name != NULL, NULL); |
328 | g_return_val_if_fail (fallback == NULL || GDK_IS_CURSOR (fallback), NULL); |
329 | |
330 | return g_object_new (GDK_TYPE_CURSOR, |
331 | first_property_name: "name" , name, |
332 | "fallback" , fallback, |
333 | NULL); |
334 | } |
335 | |
336 | /** |
337 | * gdk_cursor_new_from_texture: |
338 | * @texture: the texture providing the pixel data |
339 | * @hotspot_x: the horizontal offset of the “hotspot” of the cursor |
340 | * @hotspot_y: the vertical offset of the “hotspot” of the cursor |
341 | * @fallback: (nullable): the `GdkCursor` to fall back to when |
342 | * this one cannot be supported |
343 | * |
344 | * Creates a new cursor from a `GdkTexture`. |
345 | * |
346 | * Returns: a new `GdkCursor` |
347 | */ |
348 | GdkCursor * |
349 | gdk_cursor_new_from_texture (GdkTexture *texture, |
350 | int hotspot_x, |
351 | int hotspot_y, |
352 | GdkCursor *fallback) |
353 | { |
354 | g_return_val_if_fail (GDK_IS_TEXTURE (texture), NULL); |
355 | g_return_val_if_fail (0 <= hotspot_x && hotspot_x < gdk_texture_get_width (texture), NULL); |
356 | g_return_val_if_fail (0 <= hotspot_y && hotspot_y < gdk_texture_get_height (texture), NULL); |
357 | g_return_val_if_fail (fallback == NULL || GDK_IS_CURSOR (fallback), NULL); |
358 | |
359 | return g_object_new (GDK_TYPE_CURSOR, |
360 | first_property_name: "texture" , texture, |
361 | "hotspot-x" , hotspot_x, |
362 | "hotspot-y" , hotspot_y, |
363 | "fallback" , fallback, |
364 | NULL); |
365 | } |
366 | |
367 | /** |
368 | * gdk_cursor_get_fallback: (attributes org.gtk.Method.get_property=fallback) |
369 | * @cursor: a `GdkCursor` |
370 | * |
371 | * Returns the fallback for this @cursor. |
372 | * |
373 | * The fallback will be used if this cursor is not available on a given |
374 | * `GdkDisplay`. For named cursors, this can happen when using nonstandard |
375 | * names or when using an incomplete cursor theme. For textured cursors, |
376 | * this can happen when the texture is too large or when the `GdkDisplay` |
377 | * it is used on does not support textured cursors. |
378 | * |
379 | * Returns: (transfer none) (nullable): the fallback of the cursor or %NULL |
380 | * to use the default cursor as fallback |
381 | */ |
382 | GdkCursor * |
383 | gdk_cursor_get_fallback (GdkCursor *cursor) |
384 | { |
385 | g_return_val_if_fail (GDK_IS_CURSOR (cursor), NULL); |
386 | |
387 | return cursor->fallback; |
388 | } |
389 | |
390 | /** |
391 | * gdk_cursor_get_name: (attributes org.gtk.Method.get_property=name) |
392 | * @cursor: a `GdkCursor` |
393 | * |
394 | * Returns the name of the cursor. |
395 | * |
396 | * If the cursor is not a named cursor, %NULL will be returned. |
397 | * |
398 | * Returns: (transfer none) (nullable): the name of the cursor or %NULL |
399 | * if it is not a named cursor |
400 | */ |
401 | const char * |
402 | gdk_cursor_get_name (GdkCursor *cursor) |
403 | { |
404 | g_return_val_if_fail (GDK_IS_CURSOR (cursor), NULL); |
405 | |
406 | return cursor->name; |
407 | } |
408 | |
409 | /** |
410 | * gdk_cursor_get_texture: |
411 | * @cursor: a `GdkCursor` |
412 | * |
413 | * Returns the texture for the cursor. |
414 | * |
415 | * If the cursor is a named cursor, %NULL will be returned. |
416 | * |
417 | * Returns: (transfer none) (nullable): the texture for cursor or %NULL |
418 | * if it is a named cursor |
419 | */ |
420 | GdkTexture * |
421 | gdk_cursor_get_texture (GdkCursor *cursor) |
422 | { |
423 | g_return_val_if_fail (GDK_IS_CURSOR (cursor), NULL); |
424 | |
425 | return cursor->texture; |
426 | } |
427 | |
428 | /** |
429 | * gdk_cursor_get_hotspot_x: (attributes org.gtk.Method.get_property=hotspot-x) |
430 | * @cursor: a `GdkCursor` |
431 | * |
432 | * Returns the horizontal offset of the hotspot. |
433 | * |
434 | * The hotspot indicates the pixel that will be directly above the cursor. |
435 | * |
436 | * Note that named cursors may have a nonzero hotspot, but this function |
437 | * will only return the hotspot position for cursors created with |
438 | * [ctor@Gdk.Cursor.new_from_texture]. |
439 | * |
440 | * Returns: the horizontal offset of the hotspot or 0 for named cursors |
441 | */ |
442 | int |
443 | gdk_cursor_get_hotspot_x (GdkCursor *cursor) |
444 | { |
445 | g_return_val_if_fail (GDK_IS_CURSOR (cursor), 0); |
446 | |
447 | return cursor->hotspot_x; |
448 | } |
449 | |
450 | /** |
451 | * gdk_cursor_get_hotspot_y: (attributes org.gtk.Method.get_property=hotspot-y) |
452 | * @cursor: a `GdkCursor` |
453 | * |
454 | * Returns the vertical offset of the hotspot. |
455 | * |
456 | * The hotspot indicates the pixel that will be directly above the cursor. |
457 | * |
458 | * Note that named cursors may have a nonzero hotspot, but this function |
459 | * will only return the hotspot position for cursors created with |
460 | * [ctor@Gdk.Cursor.new_from_texture]. |
461 | * |
462 | * Returns: the vertical offset of the hotspot or 0 for named cursors |
463 | */ |
464 | int |
465 | gdk_cursor_get_hotspot_y (GdkCursor *cursor) |
466 | { |
467 | g_return_val_if_fail (GDK_IS_CURSOR (cursor), 0); |
468 | |
469 | return cursor->hotspot_y; |
470 | } |
471 | |