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
76enum {
77 PROP_0,
78 PROP_FALLBACK,
79 PROP_HOTSPOT_X,
80 PROP_HOTSPOT_Y,
81 PROP_NAME,
82 PROP_TEXTURE,
83};
84
85G_DEFINE_TYPE (GdkCursor, gdk_cursor, G_TYPE_OBJECT)
86
87static void
88gdk_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
118static void
119gdk_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
149static void
150gdk_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
161static void
162gdk_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
245static void
246gdk_cursor_init (GdkCursor *cursor)
247{
248}
249
250guint
251gdk_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
271gboolean
272gdk_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 */
323GdkCursor *
324gdk_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 */
348GdkCursor *
349gdk_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 */
382GdkCursor *
383gdk_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 */
401const char *
402gdk_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 */
420GdkTexture *
421gdk_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 */
442int
443gdk_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 */
464int
465gdk_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

source code of gtk/gdk/gdkcursor.c