1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald |
3 | * |
4 | * GtkSpinButton widget for GTK |
5 | * Copyright (C) 1998 Lars Hamann and Stefan Jeske |
6 | * |
7 | * This library is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2 of the License, or (at your option) any later version. |
11 | * |
12 | * This library is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
19 | */ |
20 | |
21 | /* |
22 | * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS |
23 | * file for a list of people on the GTK+ Team. See the ChangeLog |
24 | * files for a list of changes. These files are distributed with |
25 | * GTK+ at ftp://ftp.gtk.org/pub/gtk/. |
26 | */ |
27 | |
28 | #include "config.h" |
29 | |
30 | #include "gtkspinbutton.h" |
31 | |
32 | #include "gtkspinbuttonprivate.h" |
33 | |
34 | #include "gtkaccessibleprivate.h" |
35 | #include "gtkadjustment.h" |
36 | #include "gtkbox.h" |
37 | #include "gtkbutton.h" |
38 | #include "gtkbuttonprivate.h" |
39 | #include "gtkeditable.h" |
40 | #include "gtkcelleditable.h" |
41 | #include "gtkimage.h" |
42 | #include "gtktext.h" |
43 | #include "gtkeventcontrollerkey.h" |
44 | #include "gtkeventcontrollerfocus.h" |
45 | #include "gtkeventcontrollermotion.h" |
46 | #include "gtkeventcontrollerscroll.h" |
47 | #include "gtkgestureclick.h" |
48 | #include "gtkgestureswipe.h" |
49 | #include "gtkicontheme.h" |
50 | #include "gtkintl.h" |
51 | #include "gtkmarshalers.h" |
52 | #include "gtkorientable.h" |
53 | #include "gtkprivate.h" |
54 | #include "gtksettings.h" |
55 | #include "gtktypebuiltins.h" |
56 | #include "gtkwidgetprivate.h" |
57 | #include "gtkboxlayout.h" |
58 | #include "gtktextprivate.h" |
59 | |
60 | #include <stdio.h> |
61 | #include <stdlib.h> |
62 | #include <math.h> |
63 | #include <string.h> |
64 | #include <locale.h> |
65 | |
66 | #define MAX_TIMER_CALLS 5 |
67 | #define EPSILON 1e-10 |
68 | #define MAX_DIGITS 20 |
69 | #define TIMEOUT_INITIAL 500 |
70 | #define TIMEOUT_REPEAT 50 |
71 | |
72 | /** |
73 | * GtkSpinButton: |
74 | * |
75 | * A `GtkSpinButton` is an ideal way to allow the user to set the |
76 | * value of some attribute. |
77 | * |
78 | * ![An example GtkSpinButton](spinbutton.png) |
79 | * |
80 | * Rather than having to directly type a number into a `GtkEntry`, |
81 | * `GtkSpinButton` allows the user to click on one of two arrows |
82 | * to increment or decrement the displayed value. A value can still be |
83 | * typed in, with the bonus that it can be checked to ensure it is in a |
84 | * given range. |
85 | * |
86 | * The main properties of a `GtkSpinButton` are through an adjustment. |
87 | * See the [class@Gtk.Adjustment] documentation for more details about |
88 | * an adjustment's properties. |
89 | * |
90 | * Note that `GtkSpinButton` will by default make its entry large enough |
91 | * to accommodate the lower and upper bounds of the adjustment. If this |
92 | * is not desired, the automatic sizing can be turned off by explicitly |
93 | * setting [property@Gtk.Editable:width-chars] to a value != -1. |
94 | * |
95 | * ## Using a GtkSpinButton to get an integer |
96 | * |
97 | * ```c |
98 | * // Provides a function to retrieve an integer value from a GtkSpinButton |
99 | * // and creates a spin button to model percentage values. |
100 | * |
101 | * int |
102 | * grab_int_value (GtkSpinButton *button, |
103 | * gpointer user_data) |
104 | * { |
105 | * return gtk_spin_button_get_value_as_int (button); |
106 | * } |
107 | * |
108 | * void |
109 | * create_integer_spin_button (void) |
110 | * { |
111 | * |
112 | * GtkWidget *window, *button; |
113 | * GtkAdjustment *adjustment; |
114 | * |
115 | * adjustment = gtk_adjustment_new (50.0, 0.0, 100.0, 1.0, 5.0, 0.0); |
116 | * |
117 | * window = gtk_window_new (); |
118 | * |
119 | * // creates the spinbutton, with no decimal places |
120 | * button = gtk_spin_button_new (adjustment, 1.0, 0); |
121 | * gtk_window_set_child (GTK_WINDOW (window), button); |
122 | * |
123 | * gtk_widget_show (window); |
124 | * } |
125 | * ``` |
126 | * |
127 | * ## Using a GtkSpinButton to get a floating point value |
128 | * |
129 | * ```c |
130 | * // Provides a function to retrieve a floating point value from a |
131 | * // GtkSpinButton, and creates a high precision spin button. |
132 | * |
133 | * float |
134 | * grab_float_value (GtkSpinButton *button, |
135 | * gpointer user_data) |
136 | * { |
137 | * return gtk_spin_button_get_value (button); |
138 | * } |
139 | * |
140 | * void |
141 | * create_floating_spin_button (void) |
142 | * { |
143 | * GtkWidget *window, *button; |
144 | * GtkAdjustment *adjustment; |
145 | * |
146 | * adjustment = gtk_adjustment_new (2.500, 0.0, 5.0, 0.001, 0.1, 0.0); |
147 | * |
148 | * window = gtk_window_new (); |
149 | * |
150 | * // creates the spinbutton, with three decimal places |
151 | * button = gtk_spin_button_new (adjustment, 0.001, 3); |
152 | * gtk_window_set_child (GTK_WINDOW (window), button); |
153 | * |
154 | * gtk_widget_show (window); |
155 | * } |
156 | * ``` |
157 | * |
158 | * # CSS nodes |
159 | * |
160 | * ``` |
161 | * spinbutton.horizontal |
162 | * ├── text |
163 | * │ ├── undershoot.left |
164 | * │ ╰── undershoot.right |
165 | * ├── button.down |
166 | * ╰── button.up |
167 | * ``` |
168 | * |
169 | * ``` |
170 | * spinbutton.vertical |
171 | * ├── button.up |
172 | * ├── text |
173 | * │ ├── undershoot.left |
174 | * │ ╰── undershoot.right |
175 | * ╰── button.down |
176 | * ``` |
177 | * |
178 | * `GtkSpinButton`s main CSS node has the name spinbutton. It creates subnodes |
179 | * for the entry and the two buttons, with these names. The button nodes have |
180 | * the style classes .up and .down. The `GtkText` subnodes (if present) are put |
181 | * below the text node. The orientation of the spin button is reflected in |
182 | * the .vertical or .horizontal style class on the main node. |
183 | * |
184 | * # Accessiblity |
185 | * |
186 | * `GtkSpinButton` uses the %GTK_ACCESSIBLE_ROLE_SPIN_BUTTON role. |
187 | */ |
188 | |
189 | typedef struct _GtkSpinButton GtkSpinButton; |
190 | typedef struct _GtkSpinButtonClass GtkSpinButtonClass; |
191 | |
192 | struct _GtkSpinButton |
193 | { |
194 | GtkWidget parent_instance; |
195 | |
196 | GtkAdjustment *adjustment; |
197 | |
198 | GtkWidget *entry; |
199 | |
200 | GtkWidget *up_button; |
201 | GtkWidget *down_button; |
202 | |
203 | GtkWidget *click_child; |
204 | |
205 | guint32 timer; |
206 | |
207 | GtkSpinButtonUpdatePolicy update_policy; |
208 | |
209 | double climb_rate; |
210 | double timer_step; |
211 | double swipe_remainder; |
212 | |
213 | int width_chars; |
214 | |
215 | GtkOrientation orientation; |
216 | |
217 | guint digits : 10; |
218 | guint need_timer : 1; |
219 | guint numeric : 1; |
220 | guint snap_to_ticks : 1; |
221 | guint timer_calls : 3; |
222 | guint wrap : 1; |
223 | guint editing_canceled : 1; |
224 | }; |
225 | |
226 | struct _GtkSpinButtonClass |
227 | { |
228 | GtkWidgetClass parent_class; |
229 | |
230 | int (*input) (GtkSpinButton *spin_button, |
231 | double *new_value); |
232 | int (*output) (GtkSpinButton *spin_button); |
233 | void (*value_changed) (GtkSpinButton *spin_button); |
234 | |
235 | /* Action signals for keybindings, do not connect to these */ |
236 | void (*change_value) (GtkSpinButton *spin_button, |
237 | GtkScrollType scroll); |
238 | |
239 | void (*wrapped) (GtkSpinButton *spin_button); |
240 | }; |
241 | |
242 | enum { |
243 | PROP_0, |
244 | PROP_ADJUSTMENT, |
245 | PROP_CLIMB_RATE, |
246 | PROP_DIGITS, |
247 | PROP_SNAP_TO_TICKS, |
248 | PROP_NUMERIC, |
249 | PROP_WRAP, |
250 | PROP_UPDATE_POLICY, |
251 | PROP_VALUE, |
252 | NUM_SPINBUTTON_PROPS, |
253 | PROP_ORIENTATION = NUM_SPINBUTTON_PROPS, |
254 | PROP_EDITING_CANCELED |
255 | }; |
256 | |
257 | /* Signals */ |
258 | enum |
259 | { |
260 | INPUT, |
261 | OUTPUT, |
262 | VALUE_CHANGED, |
263 | CHANGE_VALUE, |
264 | WRAPPED, |
265 | LAST_SIGNAL |
266 | }; |
267 | |
268 | static void gtk_spin_button_editable_init (GtkEditableInterface *iface); |
269 | static void gtk_spin_button_cell_editable_init (GtkCellEditableIface *iface); |
270 | static void gtk_spin_button_finalize (GObject *object); |
271 | static void gtk_spin_button_dispose (GObject *object); |
272 | static void gtk_spin_button_set_property (GObject *object, |
273 | guint prop_id, |
274 | const GValue *value, |
275 | GParamSpec *pspec); |
276 | static void gtk_spin_button_get_property (GObject *object, |
277 | guint prop_id, |
278 | GValue *value, |
279 | GParamSpec *pspec); |
280 | static void gtk_spin_button_realize (GtkWidget *widget); |
281 | static void gtk_spin_button_state_flags_changed (GtkWidget *widget, |
282 | GtkStateFlags previous_state); |
283 | static gboolean gtk_spin_button_timer (GtkSpinButton *spin_button); |
284 | static gboolean gtk_spin_button_stop_spinning (GtkSpinButton *spin); |
285 | static void gtk_spin_button_value_changed (GtkAdjustment *adjustment, |
286 | GtkSpinButton *spin_button); |
287 | |
288 | static void gtk_spin_button_activate (GtkText *entry, |
289 | gpointer user_data); |
290 | static void gtk_spin_button_unset_adjustment (GtkSpinButton *spin_button); |
291 | static void gtk_spin_button_set_orientation (GtkSpinButton *spin_button, |
292 | GtkOrientation orientation); |
293 | static void gtk_spin_button_snap (GtkSpinButton *spin_button, |
294 | double val); |
295 | static void gtk_spin_button_insert_text (GtkEditable *editable, |
296 | const char *new_text, |
297 | int new_text_length, |
298 | int *position); |
299 | static void gtk_spin_button_real_spin (GtkSpinButton *spin_button, |
300 | double step); |
301 | static void gtk_spin_button_real_change_value (GtkSpinButton *spin, |
302 | GtkScrollType scroll); |
303 | |
304 | static int gtk_spin_button_default_input (GtkSpinButton *spin_button, |
305 | double *new_val); |
306 | static void gtk_spin_button_default_output (GtkSpinButton *spin_button); |
307 | |
308 | static void gtk_spin_button_update_width_chars (GtkSpinButton *spin_button); |
309 | |
310 | static void gtk_spin_button_accessible_init (GtkAccessibleInterface *iface); |
311 | |
312 | static guint spinbutton_signals[LAST_SIGNAL] = {0}; |
313 | static GParamSpec *spinbutton_props[NUM_SPINBUTTON_PROPS] = {NULL, }; |
314 | |
315 | G_DEFINE_TYPE_WITH_CODE (GtkSpinButton, gtk_spin_button, GTK_TYPE_WIDGET, |
316 | G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL) |
317 | G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE, |
318 | gtk_spin_button_accessible_init) |
319 | G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, |
320 | gtk_spin_button_editable_init) |
321 | G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_EDITABLE, |
322 | gtk_spin_button_cell_editable_init)) |
323 | |
324 | #define add_spin_binding(widget_class, keyval, mask, scroll) \ |
325 | gtk_widget_class_add_binding_signal (widget_class, keyval, mask, \ |
326 | "change-value", \ |
327 | "(i)", scroll) |
328 | |
329 | |
330 | static gboolean |
331 | gtk_spin_button_grab_focus (GtkWidget *widget) |
332 | { |
333 | GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget); |
334 | |
335 | return gtk_widget_grab_focus (widget: spin_button->entry); |
336 | } |
337 | |
338 | static gboolean |
339 | gtk_spin_button_mnemonic_activate (GtkWidget *widget, |
340 | gboolean group_cycling) |
341 | { |
342 | GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget); |
343 | |
344 | return gtk_widget_grab_focus (widget: spin_button->entry); |
345 | } |
346 | |
347 | static void |
348 | gtk_spin_button_class_init (GtkSpinButtonClass *class) |
349 | { |
350 | GObjectClass *gobject_class = G_OBJECT_CLASS (class); |
351 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); |
352 | |
353 | gobject_class->finalize = gtk_spin_button_finalize; |
354 | gobject_class->dispose = gtk_spin_button_dispose; |
355 | gobject_class->set_property = gtk_spin_button_set_property; |
356 | gobject_class->get_property = gtk_spin_button_get_property; |
357 | |
358 | widget_class->realize = gtk_spin_button_realize; |
359 | widget_class->state_flags_changed = gtk_spin_button_state_flags_changed; |
360 | widget_class->mnemonic_activate = gtk_spin_button_mnemonic_activate; |
361 | widget_class->grab_focus = gtk_spin_button_grab_focus; |
362 | widget_class->focus = gtk_widget_focus_child; |
363 | |
364 | class->input = NULL; |
365 | class->output = NULL; |
366 | class->change_value = gtk_spin_button_real_change_value; |
367 | |
368 | /** |
369 | * GtkSpinButton:adjustment: (attributes org.gtk.Property.get=gtk_spin_button_get_adjustment org.gtk.Property.set=gtk_spin_button_set_adjustment) |
370 | * |
371 | * The adjustment that holds the value of the spin button. |
372 | */ |
373 | spinbutton_props[PROP_ADJUSTMENT] = |
374 | g_param_spec_object (name: "adjustment" , |
375 | P_("Adjustment" ), |
376 | P_("The adjustment that holds the value of the spin button" ), |
377 | GTK_TYPE_ADJUSTMENT, |
378 | GTK_PARAM_READWRITE); |
379 | |
380 | /** |
381 | * GtkSpinButton:climb-rate: (attributes org.gtk.Property.get=gtk_spin_button_get_climb_rate org.gtk.Property.set=gtk_spin_button_set_climb_rate) |
382 | * |
383 | * The acceleration rate when you hold down a button or key. |
384 | */ |
385 | spinbutton_props[PROP_CLIMB_RATE] = |
386 | g_param_spec_double (name: "climb-rate" , |
387 | P_("Climb Rate" ), |
388 | P_("The acceleration rate when you hold down a button or key" ), |
389 | minimum: 0.0, G_MAXDOUBLE, default_value: 0.0, |
390 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
391 | |
392 | /** |
393 | * GtkSpinButton:digits: (attributes org.gtk.Property.get=gtk_spin_button_get_digits org.gtk.Property.set=gtk_spin_button_set_digits) |
394 | * |
395 | * The number of decimal places to display. |
396 | */ |
397 | spinbutton_props[PROP_DIGITS] = |
398 | g_param_spec_uint (name: "digits" , |
399 | P_("Digits" ), |
400 | P_("The number of decimal places to display" ), |
401 | minimum: 0, MAX_DIGITS, default_value: 0, |
402 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
403 | |
404 | /** |
405 | * GtkSpinButton:snap-to-ticks: (attributes org.gtk.Property.get=gtk_spin_button_get_snap_to_ticks org.gtk.Property.set=gtk_spin_button_set_snap_to_ticks) |
406 | * |
407 | * Whether erroneous values are automatically changed to the spin buttons |
408 | * nearest step increment. |
409 | */ |
410 | spinbutton_props[PROP_SNAP_TO_TICKS] = |
411 | g_param_spec_boolean (name: "snap-to-ticks" , |
412 | P_("Snap to Ticks" ), |
413 | P_("Whether erroneous values are automatically changed to a spin button’s nearest step increment" ), |
414 | FALSE, |
415 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
416 | |
417 | /** |
418 | * GtkSpinButton:numeric: (attributes org.gtk.Property.get=gtk_spin_button_get_numeric org.gtk.Property.set=gtk_spin_button_set_numeric) |
419 | * |
420 | * Whether non-numeric characters should be ignored. |
421 | */ |
422 | spinbutton_props[PROP_NUMERIC] = |
423 | g_param_spec_boolean (name: "numeric" , |
424 | P_("Numeric" ), |
425 | P_("Whether non-numeric characters should be ignored" ), |
426 | FALSE, |
427 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
428 | |
429 | /** |
430 | * GtkSpinButton:wrap: (attributes org.gtk.Property.get=gtk_spin_button_get_wrap org.gtk.Property.set=gtk_spin_button_set_wrap) |
431 | * |
432 | * Whether a spin button should wrap upon reaching its limits. |
433 | */ |
434 | spinbutton_props[PROP_WRAP] = |
435 | g_param_spec_boolean (name: "wrap" , |
436 | P_("Wrap" ), |
437 | P_("Whether a spin button should wrap upon reaching its limits" ), |
438 | FALSE, |
439 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
440 | |
441 | /** |
442 | * GtkSpinButton:update-policy: (attributes org.gtk.Property.get=gtk_spin_button_get_update_policy org.gtk.Property.set=gtk_spin_button_set_update_policy) |
443 | * |
444 | * Whether the spin button should update always, or only when the value |
445 | * is acceptable. |
446 | */ |
447 | spinbutton_props[PROP_UPDATE_POLICY] = |
448 | g_param_spec_enum (name: "update-policy" , |
449 | P_("Update Policy" ), |
450 | P_("Whether the spin button should update always, or only when the value is legal" ), |
451 | enum_type: GTK_TYPE_SPIN_BUTTON_UPDATE_POLICY, |
452 | default_value: GTK_UPDATE_ALWAYS, |
453 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
454 | |
455 | /** |
456 | * GtkSpinButton:value: (attributes org.gtk.Property.get=gtk_spin_button_get_value org.gtk.Property.set=gtk_spin_button_set_value) |
457 | * |
458 | * The current value. |
459 | */ |
460 | spinbutton_props[PROP_VALUE] = |
461 | g_param_spec_double (name: "value" , |
462 | P_("Value" ), |
463 | P_("Reads the current value, or sets a new value" ), |
464 | minimum: -G_MAXDOUBLE, G_MAXDOUBLE, default_value: 0.0, |
465 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
466 | |
467 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_SPINBUTTON_PROPS, pspecs: spinbutton_props); |
468 | g_object_class_override_property (oclass: gobject_class, property_id: PROP_ORIENTATION, name: "orientation" ); |
469 | g_object_class_override_property (oclass: gobject_class, property_id: PROP_EDITING_CANCELED, name: "editing-canceled" ); |
470 | gtk_editable_install_properties (object_class: gobject_class, first_prop: PROP_EDITING_CANCELED + 1); |
471 | |
472 | /** |
473 | * GtkSpinButton::input: |
474 | * @spin_button: the object on which the signal was emitted |
475 | * @new_value: (out) (type double): return location for the new value |
476 | * |
477 | * Emitted to convert the users input into a double value. |
478 | * |
479 | * The signal handler is expected to use [method@Gtk.Editable.get_text] |
480 | * to retrieve the text of the spinbutton and set @new_value to the |
481 | * new value. |
482 | * |
483 | * The default conversion uses g_strtod(). |
484 | * |
485 | * Returns: %TRUE for a successful conversion, %FALSE if the input |
486 | * was not handled, and %GTK_INPUT_ERROR if the conversion failed. |
487 | */ |
488 | spinbutton_signals[INPUT] = |
489 | g_signal_new (I_("input" ), |
490 | G_TYPE_FROM_CLASS (gobject_class), |
491 | signal_flags: G_SIGNAL_RUN_LAST, |
492 | G_STRUCT_OFFSET (GtkSpinButtonClass, input), |
493 | NULL, NULL, |
494 | c_marshaller: _gtk_marshal_INT__POINTER, |
495 | G_TYPE_INT, n_params: 1, |
496 | G_TYPE_POINTER); |
497 | |
498 | /** |
499 | * GtkSpinButton::output: |
500 | * @spin_button: the object on which the signal was emitted |
501 | * |
502 | * Emitted to tweak the formatting of the value for display. |
503 | * |
504 | * ```c |
505 | * // show leading zeros |
506 | * static gboolean |
507 | * on_output (GtkSpinButton *spin, |
508 | * gpointer data) |
509 | * { |
510 | * GtkAdjustment *adjustment; |
511 | * char *text; |
512 | * int value; |
513 | * |
514 | * adjustment = gtk_spin_button_get_adjustment (spin); |
515 | * value = (int)gtk_adjustment_get_value (adjustment); |
516 | * text = g_strdup_printf ("%02d", value); |
517 | * gtk_spin_button_set_text (spin, text): |
518 | * g_free (text); |
519 | * |
520 | * return TRUE; |
521 | * } |
522 | * ``` |
523 | * |
524 | * Returns: %TRUE if the value has been displayed |
525 | */ |
526 | spinbutton_signals[OUTPUT] = |
527 | g_signal_new (I_("output" ), |
528 | G_TYPE_FROM_CLASS (gobject_class), |
529 | signal_flags: G_SIGNAL_RUN_LAST, |
530 | G_STRUCT_OFFSET (GtkSpinButtonClass, output), |
531 | accumulator: _gtk_boolean_handled_accumulator, NULL, |
532 | c_marshaller: _gtk_marshal_BOOLEAN__VOID, |
533 | G_TYPE_BOOLEAN, n_params: 0); |
534 | |
535 | /** |
536 | * GtkSpinButton::value-changed: |
537 | * @spin_button: the object on which the signal was emitted |
538 | * |
539 | * Emitted when the value is changed. |
540 | * |
541 | * Also see the [signal@Gtk.SpinButton::output] signal. |
542 | */ |
543 | spinbutton_signals[VALUE_CHANGED] = |
544 | g_signal_new (I_("value-changed" ), |
545 | G_TYPE_FROM_CLASS (gobject_class), |
546 | signal_flags: G_SIGNAL_RUN_LAST, |
547 | G_STRUCT_OFFSET (GtkSpinButtonClass, value_changed), |
548 | NULL, NULL, |
549 | NULL, |
550 | G_TYPE_NONE, n_params: 0); |
551 | |
552 | /** |
553 | * GtkSpinButton::wrapped: |
554 | * @spin_button: the object on which the signal was emitted |
555 | * |
556 | * Emitted right after the spinbutton wraps from its maximum |
557 | * to its minimum value or vice-versa. |
558 | */ |
559 | spinbutton_signals[WRAPPED] = |
560 | g_signal_new (I_("wrapped" ), |
561 | G_TYPE_FROM_CLASS (gobject_class), |
562 | signal_flags: G_SIGNAL_RUN_LAST, |
563 | G_STRUCT_OFFSET (GtkSpinButtonClass, wrapped), |
564 | NULL, NULL, |
565 | NULL, |
566 | G_TYPE_NONE, n_params: 0); |
567 | |
568 | /* Action signals */ |
569 | /** |
570 | * GtkSpinButton::change-value: |
571 | * @spin_button: the object on which the signal was emitted |
572 | * @scroll: a `GtkScrollType` to specify the speed and amount of change |
573 | * |
574 | * Emitted when the user initiates a value change. |
575 | * |
576 | * This is a [keybinding signal](class.SignalAction.html). |
577 | * |
578 | * Applications should not connect to it, but may emit it with |
579 | * g_signal_emit_by_name() if they need to control the cursor |
580 | * programmatically. |
581 | * |
582 | * The default bindings for this signal are Up/Down and PageUp/PageDown. |
583 | */ |
584 | spinbutton_signals[CHANGE_VALUE] = |
585 | g_signal_new (I_("change-value" ), |
586 | G_TYPE_FROM_CLASS (gobject_class), |
587 | signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, |
588 | G_STRUCT_OFFSET (GtkSpinButtonClass, change_value), |
589 | NULL, NULL, |
590 | NULL, |
591 | G_TYPE_NONE, n_params: 1, |
592 | GTK_TYPE_SCROLL_TYPE); |
593 | |
594 | add_spin_binding (widget_class, GDK_KEY_Up, 0, GTK_SCROLL_STEP_UP); |
595 | add_spin_binding (widget_class, GDK_KEY_KP_Up, 0, GTK_SCROLL_STEP_UP); |
596 | add_spin_binding (widget_class, GDK_KEY_Down, 0, GTK_SCROLL_STEP_DOWN); |
597 | add_spin_binding (widget_class, GDK_KEY_KP_Down, 0, GTK_SCROLL_STEP_DOWN); |
598 | add_spin_binding (widget_class, GDK_KEY_Page_Up, 0, GTK_SCROLL_PAGE_UP); |
599 | add_spin_binding (widget_class, GDK_KEY_Page_Down, 0, GTK_SCROLL_PAGE_DOWN); |
600 | add_spin_binding (widget_class, GDK_KEY_End, GDK_CONTROL_MASK, GTK_SCROLL_END); |
601 | add_spin_binding (widget_class, GDK_KEY_Home, GDK_CONTROL_MASK, GTK_SCROLL_START); |
602 | add_spin_binding (widget_class, GDK_KEY_Page_Up, GDK_CONTROL_MASK, GTK_SCROLL_END); |
603 | add_spin_binding (widget_class, GDK_KEY_Page_Down, GDK_CONTROL_MASK, GTK_SCROLL_START); |
604 | |
605 | gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT); |
606 | gtk_widget_class_set_css_name (widget_class, I_("spinbutton" )); |
607 | gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_SPIN_BUTTON); |
608 | } |
609 | |
610 | static GtkEditable * |
611 | gtk_spin_button_get_delegate (GtkEditable *editable) |
612 | { |
613 | GtkSpinButton *spin_button = GTK_SPIN_BUTTON (editable); |
614 | |
615 | return GTK_EDITABLE (spin_button->entry); |
616 | } |
617 | |
618 | static void |
619 | gtk_spin_button_editable_init (GtkEditableInterface *iface) |
620 | { |
621 | iface->get_delegate = gtk_spin_button_get_delegate; |
622 | iface->insert_text = gtk_spin_button_insert_text; |
623 | } |
624 | |
625 | static gboolean |
626 | gtk_spin_button_accessible_get_platform_state (GtkAccessible *self, |
627 | GtkAccessiblePlatformState state) |
628 | { |
629 | GtkSpinButton *spin_button = GTK_SPIN_BUTTON (self); |
630 | |
631 | switch (state) |
632 | { |
633 | case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE: |
634 | return gtk_widget_get_focusable (widget: spin_button->entry); |
635 | case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED: |
636 | return gtk_widget_has_focus (widget: spin_button->entry); |
637 | case GTK_ACCESSIBLE_PLATFORM_STATE_ACTIVE: |
638 | return FALSE; |
639 | default: |
640 | g_assert_not_reached (); |
641 | } |
642 | } |
643 | |
644 | static void |
645 | gtk_spin_button_accessible_init (GtkAccessibleInterface *iface) |
646 | { |
647 | GtkAccessibleInterface *parent_iface = g_type_interface_peek_parent (g_iface: iface); |
648 | iface->get_at_context = parent_iface->get_at_context; |
649 | iface->get_platform_state = gtk_spin_button_accessible_get_platform_state; |
650 | } |
651 | |
652 | static void |
653 | gtk_cell_editable_spin_button_activated (GtkText *text, GtkSpinButton *spin) |
654 | { |
655 | g_object_ref (spin); |
656 | gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (spin)); |
657 | gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (spin)); |
658 | g_object_unref (object: spin); |
659 | } |
660 | |
661 | static gboolean |
662 | gtk_cell_editable_spin_button_key_pressed (GtkEventControllerKey *key, |
663 | guint keyval, |
664 | guint keycode, |
665 | GdkModifierType modifiers, |
666 | GtkSpinButton *spin) |
667 | { |
668 | if (keyval == GDK_KEY_Escape) |
669 | { |
670 | spin->editing_canceled = TRUE; |
671 | |
672 | g_object_ref (spin); |
673 | gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (spin)); |
674 | gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (spin)); |
675 | g_object_unref (object: spin); |
676 | |
677 | return GDK_EVENT_STOP; |
678 | } |
679 | |
680 | /* override focus */ |
681 | if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Down) |
682 | { |
683 | g_object_ref (spin); |
684 | gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (spin)); |
685 | gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (spin)); |
686 | g_object_unref (object: spin); |
687 | |
688 | return GDK_EVENT_STOP; |
689 | } |
690 | |
691 | return GDK_EVENT_PROPAGATE; |
692 | } |
693 | |
694 | static void |
695 | gtk_spin_button_start_editing (GtkCellEditable *cell_editable, |
696 | GdkEvent *event) |
697 | { |
698 | GtkSpinButton *spin = GTK_SPIN_BUTTON (cell_editable); |
699 | |
700 | g_signal_connect (spin->entry, "activate" , |
701 | G_CALLBACK (gtk_cell_editable_spin_button_activated), cell_editable); |
702 | g_signal_connect (gtk_text_get_key_controller (GTK_TEXT (spin->entry)), "key-pressed" , |
703 | G_CALLBACK (gtk_cell_editable_spin_button_key_pressed), cell_editable); |
704 | } |
705 | |
706 | static void |
707 | gtk_spin_button_cell_editable_init (GtkCellEditableIface *iface) |
708 | { |
709 | iface->start_editing = gtk_spin_button_start_editing; |
710 | } |
711 | |
712 | static void |
713 | gtk_spin_button_set_property (GObject *object, |
714 | guint prop_id, |
715 | const GValue *value, |
716 | GParamSpec *pspec) |
717 | { |
718 | GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object); |
719 | |
720 | if (prop_id == PROP_EDITING_CANCELED + 1 + GTK_EDITABLE_PROP_WIDTH_CHARS) |
721 | { |
722 | spin_button->width_chars = g_value_get_int (value); |
723 | gtk_spin_button_update_width_chars (spin_button); |
724 | return; |
725 | } |
726 | |
727 | if (gtk_editable_delegate_set_property (object, prop_id, value, pspec)) |
728 | return; |
729 | |
730 | switch (prop_id) |
731 | { |
732 | GtkAdjustment *adjustment; |
733 | |
734 | case PROP_ADJUSTMENT: |
735 | adjustment = GTK_ADJUSTMENT (g_value_get_object (value)); |
736 | gtk_spin_button_set_adjustment (spin_button, adjustment); |
737 | break; |
738 | case PROP_CLIMB_RATE: |
739 | gtk_spin_button_configure (spin_button, |
740 | adjustment: spin_button->adjustment, |
741 | climb_rate: g_value_get_double (value), |
742 | digits: spin_button->digits); |
743 | break; |
744 | case PROP_DIGITS: |
745 | gtk_spin_button_configure (spin_button, |
746 | adjustment: spin_button->adjustment, |
747 | climb_rate: spin_button->climb_rate, |
748 | digits: g_value_get_uint (value)); |
749 | break; |
750 | case PROP_SNAP_TO_TICKS: |
751 | gtk_spin_button_set_snap_to_ticks (spin_button, snap_to_ticks: g_value_get_boolean (value)); |
752 | break; |
753 | case PROP_NUMERIC: |
754 | gtk_spin_button_set_numeric (spin_button, numeric: g_value_get_boolean (value)); |
755 | break; |
756 | case PROP_WRAP: |
757 | gtk_spin_button_set_wrap (spin_button, wrap: g_value_get_boolean (value)); |
758 | break; |
759 | case PROP_UPDATE_POLICY: |
760 | gtk_spin_button_set_update_policy (spin_button, policy: g_value_get_enum (value)); |
761 | break; |
762 | case PROP_VALUE: |
763 | gtk_spin_button_set_value (spin_button, value: g_value_get_double (value)); |
764 | break; |
765 | case PROP_ORIENTATION: |
766 | gtk_spin_button_set_orientation (spin_button, orientation: g_value_get_enum (value)); |
767 | break; |
768 | case PROP_EDITING_CANCELED: |
769 | if (spin_button->editing_canceled != g_value_get_boolean (value)) |
770 | { |
771 | spin_button->editing_canceled = g_value_get_boolean (value); |
772 | g_object_notify (object, property_name: "editing-canceled" ); |
773 | } |
774 | break; |
775 | default: |
776 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
777 | break; |
778 | } |
779 | } |
780 | |
781 | static void |
782 | gtk_spin_button_get_property (GObject *object, |
783 | guint prop_id, |
784 | GValue *value, |
785 | GParamSpec *pspec) |
786 | { |
787 | GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object); |
788 | |
789 | if (prop_id == PROP_EDITING_CANCELED + 1 + GTK_EDITABLE_PROP_WIDTH_CHARS) |
790 | { |
791 | g_value_set_int (value, v_int: spin_button->width_chars); |
792 | return; |
793 | } |
794 | if (gtk_editable_delegate_get_property (object, prop_id, value, pspec)) |
795 | return; |
796 | |
797 | switch (prop_id) |
798 | { |
799 | case PROP_ADJUSTMENT: |
800 | g_value_set_object (value, v_object: spin_button->adjustment); |
801 | break; |
802 | case PROP_CLIMB_RATE: |
803 | g_value_set_double (value, v_double: spin_button->climb_rate); |
804 | break; |
805 | case PROP_DIGITS: |
806 | g_value_set_uint (value, v_uint: spin_button->digits); |
807 | break; |
808 | case PROP_SNAP_TO_TICKS: |
809 | g_value_set_boolean (value, v_boolean: spin_button->snap_to_ticks); |
810 | break; |
811 | case PROP_NUMERIC: |
812 | g_value_set_boolean (value, v_boolean: spin_button->numeric); |
813 | break; |
814 | case PROP_WRAP: |
815 | g_value_set_boolean (value, v_boolean: spin_button->wrap); |
816 | break; |
817 | case PROP_UPDATE_POLICY: |
818 | g_value_set_enum (value, v_enum: spin_button->update_policy); |
819 | break; |
820 | case PROP_VALUE: |
821 | g_value_set_double (value, v_double: gtk_adjustment_get_value (adjustment: spin_button->adjustment)); |
822 | break; |
823 | case PROP_ORIENTATION: |
824 | g_value_set_enum (value, v_enum: gtk_orientable_get_orientation (GTK_ORIENTABLE (gtk_widget_get_layout_manager (GTK_WIDGET (spin_button))))); |
825 | break; |
826 | case PROP_EDITING_CANCELED: |
827 | g_value_set_boolean (value, v_boolean: spin_button->editing_canceled); |
828 | break; |
829 | default: |
830 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
831 | break; |
832 | } |
833 | } |
834 | |
835 | static void |
836 | swipe_gesture_begin (GtkGesture *gesture, |
837 | GdkEventSequence *sequence, |
838 | GtkSpinButton *spin_button) |
839 | { |
840 | gtk_gesture_set_state (gesture, state: GTK_EVENT_SEQUENCE_CLAIMED); |
841 | gtk_widget_grab_focus (GTK_WIDGET (spin_button)); |
842 | spin_button->swipe_remainder = 0; |
843 | } |
844 | |
845 | static void |
846 | swipe_gesture_update (GtkGesture *gesture, |
847 | GdkEventSequence *sequence, |
848 | GtkSpinButton *spin_button) |
849 | { |
850 | double vel_y, step; |
851 | |
852 | gtk_gesture_swipe_get_velocity (GTK_GESTURE_SWIPE (gesture), NULL, velocity_y: &vel_y); |
853 | step = (-vel_y / 20) + spin_button->swipe_remainder; |
854 | spin_button->swipe_remainder = fmod (x: step, y: gtk_adjustment_get_step_increment (adjustment: spin_button->adjustment)); |
855 | gtk_spin_button_real_spin (spin_button, step: step - spin_button->swipe_remainder); |
856 | } |
857 | |
858 | static gboolean |
859 | scroll_controller_scroll (GtkEventControllerScroll *Scroll, |
860 | double dx, |
861 | double dy, |
862 | GtkWidget *widget) |
863 | { |
864 | GtkSpinButton *spin = GTK_SPIN_BUTTON (widget); |
865 | |
866 | if (!gtk_widget_has_focus (widget)) |
867 | gtk_widget_grab_focus (widget); |
868 | gtk_spin_button_real_spin (spin_button: spin, step: -dy * gtk_adjustment_get_step_increment (adjustment: spin->adjustment)); |
869 | |
870 | return GDK_EVENT_STOP; |
871 | } |
872 | |
873 | static gboolean |
874 | gtk_spin_button_stop_spinning (GtkSpinButton *spin) |
875 | { |
876 | gboolean did_spin = FALSE; |
877 | |
878 | if (spin->timer) |
879 | { |
880 | g_source_remove (tag: spin->timer); |
881 | spin->timer = 0; |
882 | spin->need_timer = FALSE; |
883 | |
884 | did_spin = TRUE; |
885 | } |
886 | |
887 | spin->timer_step = gtk_adjustment_get_step_increment (adjustment: spin->adjustment); |
888 | spin->timer_calls = 0; |
889 | |
890 | spin->click_child = NULL; |
891 | |
892 | return did_spin; |
893 | } |
894 | |
895 | static void |
896 | start_spinning (GtkSpinButton *spin, |
897 | GtkWidget *click_child, |
898 | double step) |
899 | { |
900 | spin->click_child = click_child; |
901 | |
902 | if (!spin->timer) |
903 | { |
904 | spin->timer_step = step; |
905 | spin->need_timer = TRUE; |
906 | spin->timer = g_timeout_add (TIMEOUT_INITIAL, |
907 | function: (GSourceFunc) gtk_spin_button_timer, |
908 | data: (gpointer) spin); |
909 | gdk_source_set_static_name_by_id (tag: spin->timer, name: "[gtk] gtk_spin_button_timer" ); |
910 | } |
911 | gtk_spin_button_real_spin (spin_button: spin, step: click_child == spin->up_button ? step : -step); |
912 | } |
913 | |
914 | static void |
915 | button_pressed_cb (GtkGestureClick *gesture, |
916 | int n_pressses, |
917 | double x, |
918 | double y, |
919 | gpointer user_data) |
920 | { |
921 | GtkSpinButton *spin_button = user_data; |
922 | GtkWidget *pressed_button = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); |
923 | |
924 | gtk_widget_grab_focus (GTK_WIDGET (spin_button)); |
925 | |
926 | if (gtk_editable_get_editable (GTK_EDITABLE (spin_button->entry))) |
927 | { |
928 | int button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); |
929 | gtk_spin_button_update (spin_button); |
930 | |
931 | if (button == GDK_BUTTON_PRIMARY) |
932 | start_spinning (spin: spin_button, click_child: pressed_button, step: gtk_adjustment_get_step_increment (adjustment: spin_button->adjustment)); |
933 | else if (button == GDK_BUTTON_MIDDLE) |
934 | start_spinning (spin: spin_button, click_child: pressed_button, step: gtk_adjustment_get_page_increment (adjustment: spin_button->adjustment)); |
935 | |
936 | gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED); |
937 | } |
938 | else |
939 | { |
940 | gtk_widget_error_bell (GTK_WIDGET (spin_button)); |
941 | } |
942 | } |
943 | |
944 | static void |
945 | button_released_cb (GtkGestureClick *gesture, |
946 | int n_pressses, |
947 | double x, |
948 | double y, |
949 | gpointer user_data) |
950 | { |
951 | GtkSpinButton *spin_button = user_data; |
952 | int button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); |
953 | |
954 | gtk_spin_button_stop_spinning (spin: spin_button); |
955 | |
956 | if (button == GDK_BUTTON_SECONDARY) |
957 | { |
958 | GtkWidget *button_widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); |
959 | double diff; |
960 | if (button_widget == spin_button->down_button) |
961 | { |
962 | diff = gtk_adjustment_get_value (adjustment: spin_button->adjustment) - gtk_adjustment_get_lower (adjustment: spin_button->adjustment); |
963 | if (diff > EPSILON) |
964 | gtk_spin_button_real_spin (spin_button, step: -diff); |
965 | } |
966 | else if (button_widget == spin_button->up_button) |
967 | { |
968 | diff = gtk_adjustment_get_upper (adjustment: spin_button->adjustment) - gtk_adjustment_get_value (adjustment: spin_button->adjustment); |
969 | if (diff > EPSILON) |
970 | gtk_spin_button_real_spin (spin_button, step: diff); |
971 | } |
972 | } |
973 | } |
974 | |
975 | static void |
976 | button_cancel_cb (GtkGesture *gesture, |
977 | GdkEventSequence *sequence, |
978 | GtkSpinButton *spin_button) |
979 | { |
980 | gtk_spin_button_stop_spinning (spin: spin_button); |
981 | } |
982 | |
983 | static void |
984 | key_controller_key_released (GtkEventControllerKey *key, |
985 | guint keyval, |
986 | guint keycode, |
987 | GdkModifierType modifiers, |
988 | GtkSpinButton *spin_button) |
989 | { |
990 | spin_button->timer_step = gtk_adjustment_get_step_increment (adjustment: spin_button->adjustment); |
991 | spin_button->timer_calls = 0; |
992 | } |
993 | |
994 | static void |
995 | key_controller_focus_out (GtkEventController *controller, |
996 | GtkSpinButton *spin_button) |
997 | { |
998 | if (gtk_editable_get_editable (GTK_EDITABLE (spin_button->entry))) |
999 | gtk_spin_button_update (spin_button); |
1000 | } |
1001 | |
1002 | static void |
1003 | gtk_spin_button_init (GtkSpinButton *spin_button) |
1004 | { |
1005 | GtkEventController *controller; |
1006 | GtkGesture *gesture; |
1007 | |
1008 | spin_button->adjustment = NULL; |
1009 | spin_button->timer = 0; |
1010 | spin_button->climb_rate = 0.0; |
1011 | spin_button->timer_step = 0.0; |
1012 | spin_button->update_policy = GTK_UPDATE_ALWAYS; |
1013 | spin_button->click_child = NULL; |
1014 | spin_button->need_timer = FALSE; |
1015 | spin_button->timer_calls = 0; |
1016 | spin_button->digits = 0; |
1017 | spin_button->numeric = FALSE; |
1018 | spin_button->wrap = FALSE; |
1019 | spin_button->snap_to_ticks = FALSE; |
1020 | spin_button->width_chars = -1; |
1021 | |
1022 | gtk_widget_update_orientation (GTK_WIDGET (spin_button), orientation: GTK_ORIENTATION_HORIZONTAL); |
1023 | |
1024 | spin_button->entry = gtk_text_new (); |
1025 | gtk_editable_init_delegate (GTK_EDITABLE (spin_button)); |
1026 | gtk_editable_set_width_chars (GTK_EDITABLE (spin_button->entry), n_chars: 0); |
1027 | gtk_editable_set_max_width_chars (GTK_EDITABLE (spin_button->entry), n_chars: 0); |
1028 | gtk_widget_set_hexpand (widget: spin_button->entry, TRUE); |
1029 | gtk_widget_set_vexpand (widget: spin_button->entry, TRUE); |
1030 | g_signal_connect (spin_button->entry, "activate" , G_CALLBACK (gtk_spin_button_activate), spin_button); |
1031 | gtk_widget_set_parent (widget: spin_button->entry, GTK_WIDGET (spin_button)); |
1032 | |
1033 | spin_button->down_button = g_object_new (GTK_TYPE_BUTTON, |
1034 | first_property_name: "accessible-role" , GTK_ACCESSIBLE_ROLE_NONE, |
1035 | "icon-name" , "value-decrease-symbolic" , |
1036 | NULL); |
1037 | gtk_widget_add_css_class (widget: spin_button->down_button, css_class: "down" ); |
1038 | gtk_widget_set_can_focus (widget: spin_button->down_button, FALSE); |
1039 | gtk_widget_set_parent (widget: spin_button->down_button, GTK_WIDGET (spin_button)); |
1040 | |
1041 | gesture = gtk_gesture_click_new (); |
1042 | gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), button: 0); |
1043 | gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE); |
1044 | gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), |
1045 | phase: GTK_PHASE_CAPTURE); |
1046 | g_signal_connect (gesture, "pressed" , G_CALLBACK (button_pressed_cb), spin_button); |
1047 | g_signal_connect (gesture, "released" , G_CALLBACK (button_released_cb), spin_button); |
1048 | g_signal_connect (gesture, "cancel" , G_CALLBACK (button_cancel_cb), spin_button); |
1049 | gtk_widget_add_controller (GTK_WIDGET (spin_button->down_button), GTK_EVENT_CONTROLLER (gesture)); |
1050 | gtk_gesture_group (group_gesture: gtk_button_get_gesture (GTK_BUTTON (spin_button->down_button)), gesture); |
1051 | |
1052 | spin_button->up_button = g_object_new (GTK_TYPE_BUTTON, |
1053 | first_property_name: "accessible-role" , GTK_ACCESSIBLE_ROLE_NONE, |
1054 | "icon-name" , "value-increase-symbolic" , |
1055 | NULL); |
1056 | gtk_widget_add_css_class (widget: spin_button->up_button, css_class: "up" ); |
1057 | gtk_widget_set_can_focus (widget: spin_button->up_button, FALSE); |
1058 | gtk_widget_set_parent (widget: spin_button->up_button, GTK_WIDGET (spin_button)); |
1059 | |
1060 | gesture = gtk_gesture_click_new (); |
1061 | gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), button: 0); |
1062 | gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE); |
1063 | gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), |
1064 | phase: GTK_PHASE_CAPTURE); |
1065 | g_signal_connect (gesture, "pressed" , G_CALLBACK (button_pressed_cb), spin_button); |
1066 | g_signal_connect (gesture, "released" , G_CALLBACK (button_released_cb), spin_button); |
1067 | g_signal_connect (gesture, "cancel" , G_CALLBACK (button_cancel_cb), spin_button); |
1068 | gtk_widget_add_controller (GTK_WIDGET (spin_button->up_button), GTK_EVENT_CONTROLLER (gesture)); |
1069 | gtk_gesture_group (group_gesture: gtk_button_get_gesture (GTK_BUTTON (spin_button->up_button)), |
1070 | gesture); |
1071 | |
1072 | gtk_spin_button_set_adjustment (spin_button, NULL); |
1073 | |
1074 | gesture = gtk_gesture_swipe_new (); |
1075 | gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), TRUE); |
1076 | gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), |
1077 | phase: GTK_PHASE_CAPTURE); |
1078 | g_signal_connect (gesture, "begin" , |
1079 | G_CALLBACK (swipe_gesture_begin), spin_button); |
1080 | g_signal_connect (gesture, "update" , |
1081 | G_CALLBACK (swipe_gesture_update), spin_button); |
1082 | gtk_widget_add_controller (GTK_WIDGET (spin_button->entry), |
1083 | GTK_EVENT_CONTROLLER (gesture)); |
1084 | |
1085 | controller = gtk_event_controller_scroll_new (flags: GTK_EVENT_CONTROLLER_SCROLL_VERTICAL | |
1086 | GTK_EVENT_CONTROLLER_SCROLL_DISCRETE); |
1087 | g_signal_connect (controller, "scroll" , |
1088 | G_CALLBACK (scroll_controller_scroll), spin_button); |
1089 | gtk_widget_add_controller (GTK_WIDGET (spin_button), controller); |
1090 | |
1091 | controller = gtk_event_controller_key_new (); |
1092 | g_signal_connect (controller, "key-released" , |
1093 | G_CALLBACK (key_controller_key_released), spin_button); |
1094 | gtk_widget_add_controller (GTK_WIDGET (spin_button), controller); |
1095 | controller = gtk_event_controller_focus_new (); |
1096 | g_signal_connect (controller, "leave" , |
1097 | G_CALLBACK (key_controller_focus_out), spin_button); |
1098 | gtk_widget_add_controller (GTK_WIDGET (spin_button), controller); |
1099 | } |
1100 | |
1101 | static void |
1102 | gtk_spin_button_finalize (GObject *object) |
1103 | { |
1104 | GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object); |
1105 | |
1106 | gtk_spin_button_unset_adjustment (spin_button); |
1107 | |
1108 | gtk_editable_finish_delegate (GTK_EDITABLE (spin_button)); |
1109 | |
1110 | gtk_widget_unparent (widget: spin_button->entry); |
1111 | gtk_widget_unparent (widget: spin_button->up_button); |
1112 | gtk_widget_unparent (widget: spin_button->down_button); |
1113 | |
1114 | G_OBJECT_CLASS (gtk_spin_button_parent_class)->finalize (object); |
1115 | } |
1116 | |
1117 | static void |
1118 | gtk_spin_button_dispose (GObject *object) |
1119 | { |
1120 | gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (object)); |
1121 | |
1122 | G_OBJECT_CLASS (gtk_spin_button_parent_class)->dispose (object); |
1123 | } |
1124 | |
1125 | static void |
1126 | gtk_spin_button_realize (GtkWidget *widget) |
1127 | { |
1128 | GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget); |
1129 | gboolean return_val; |
1130 | const char *text; |
1131 | |
1132 | GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->realize (widget); |
1133 | |
1134 | return_val = FALSE; |
1135 | g_signal_emit (instance: spin_button, signal_id: spinbutton_signals[OUTPUT], detail: 0, &return_val); |
1136 | |
1137 | /* If output wasn't processed explicitly by the method connected to the |
1138 | * 'output' signal; and if we don't have any explicit 'text' set initially, |
1139 | * fallback to the default output. |
1140 | */ |
1141 | text = gtk_editable_get_text (GTK_EDITABLE (spin_button->entry)); |
1142 | if (!return_val && (spin_button->numeric || text == NULL || *text == '\0')) |
1143 | gtk_spin_button_default_output (spin_button); |
1144 | } |
1145 | |
1146 | /* This is called when :value, :wrap, or the bounds of the adjustment change, |
1147 | * as the combination of those determines if our up|down_button are sensitive |
1148 | */ |
1149 | static void |
1150 | update_buttons_sensitivity (GtkSpinButton *spin_button) |
1151 | { |
1152 | const double lower = gtk_adjustment_get_lower (adjustment: spin_button->adjustment); |
1153 | const double upper = gtk_adjustment_get_upper (adjustment: spin_button->adjustment); |
1154 | const double value = gtk_adjustment_get_value (adjustment: spin_button->adjustment); |
1155 | |
1156 | gtk_widget_set_sensitive (widget: spin_button->up_button, |
1157 | sensitive: spin_button->wrap || upper - value > EPSILON); |
1158 | gtk_widget_set_sensitive (widget: spin_button->down_button, |
1159 | sensitive: spin_button->wrap || value - lower > EPSILON); |
1160 | } |
1161 | |
1162 | /* Callback used when the spin button's adjustment changes. |
1163 | * We need to reevaluate our size request & up|down_button sensitivity. |
1164 | */ |
1165 | static void |
1166 | adjustment_changed_cb (GtkAdjustment *adjustment, gpointer data) |
1167 | { |
1168 | GtkSpinButton *spin_button = GTK_SPIN_BUTTON (data); |
1169 | |
1170 | spin_button->timer_step = gtk_adjustment_get_step_increment (adjustment); |
1171 | |
1172 | update_buttons_sensitivity (spin_button); |
1173 | |
1174 | gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: spin_button), |
1175 | first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, gtk_adjustment_get_upper (adjustment), |
1176 | GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, gtk_adjustment_get_lower (adjustment), |
1177 | GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (adjustment), |
1178 | -1); |
1179 | |
1180 | gtk_widget_queue_resize (GTK_WIDGET (spin_button)); |
1181 | } |
1182 | |
1183 | static void |
1184 | gtk_spin_button_unset_adjustment (GtkSpinButton *spin_button) |
1185 | { |
1186 | if (spin_button->adjustment) |
1187 | { |
1188 | g_signal_handlers_disconnect_by_func (spin_button->adjustment, |
1189 | gtk_spin_button_value_changed, |
1190 | spin_button); |
1191 | g_signal_handlers_disconnect_by_func (spin_button->adjustment, |
1192 | adjustment_changed_cb, |
1193 | spin_button); |
1194 | g_clear_object (&spin_button->adjustment); |
1195 | } |
1196 | } |
1197 | |
1198 | static void |
1199 | gtk_spin_button_set_orientation (GtkSpinButton *spin, |
1200 | GtkOrientation orientation) |
1201 | { |
1202 | GtkBoxLayout *layout_manager; |
1203 | GtkEditable *editable = GTK_EDITABLE (spin->entry); |
1204 | |
1205 | if (gtk_orientable_get_orientation (GTK_ORIENTABLE (spin)) == orientation) |
1206 | return; |
1207 | |
1208 | layout_manager = GTK_BOX_LAYOUT (ptr: gtk_widget_get_layout_manager (GTK_WIDGET (spin))); |
1209 | gtk_orientable_set_orientation (GTK_ORIENTABLE (layout_manager), orientation); |
1210 | |
1211 | gtk_widget_update_orientation (GTK_WIDGET (spin), orientation); |
1212 | |
1213 | /* change alignment if it's the default */ |
1214 | if (orientation == GTK_ORIENTATION_VERTICAL && |
1215 | gtk_editable_get_alignment (editable) == 0.0) |
1216 | gtk_editable_set_alignment (editable, xalign: 0.5); |
1217 | else if (orientation == GTK_ORIENTATION_HORIZONTAL && |
1218 | gtk_editable_get_alignment (editable) == 0.5) |
1219 | gtk_editable_set_alignment (editable, xalign: 0.0); |
1220 | |
1221 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
1222 | { |
1223 | /* Current orientation of the box is vertical! */ |
1224 | gtk_widget_insert_after (widget: spin->up_button, GTK_WIDGET (spin), previous_sibling: spin->down_button); |
1225 | } |
1226 | else |
1227 | { |
1228 | /* Current orientation of the box is horizontal! */ |
1229 | gtk_widget_insert_before (widget: spin->up_button, GTK_WIDGET (spin), next_sibling: spin->entry); |
1230 | } |
1231 | |
1232 | g_object_notify (G_OBJECT (spin), property_name: "orientation" ); |
1233 | } |
1234 | |
1235 | static char * |
1236 | weed_out_neg_zero (char *str, |
1237 | int digits) |
1238 | { |
1239 | if (str[0] == '-') |
1240 | { |
1241 | char neg_zero[8]; |
1242 | g_snprintf (string: neg_zero, n: 8, format: "%0.*f" , digits, -0.0); |
1243 | if (strcmp (s1: neg_zero, s2: str) == 0) |
1244 | memmove (dest: str, src: str + 1, n: strlen (s: str)); |
1245 | } |
1246 | return str; |
1247 | } |
1248 | |
1249 | static char * |
1250 | gtk_spin_button_format_for_value (GtkSpinButton *spin_button, |
1251 | double value) |
1252 | { |
1253 | char *buf = g_strdup_printf (format: "%0.*f" , spin_button->digits, value); |
1254 | |
1255 | return weed_out_neg_zero (str: buf, digits: spin_button->digits); |
1256 | } |
1257 | |
1258 | static void |
1259 | gtk_spin_button_update_width_chars (GtkSpinButton *spin_button) |
1260 | { |
1261 | char *str; |
1262 | double value; |
1263 | int width_chars, c; |
1264 | |
1265 | if (spin_button->width_chars == -1) |
1266 | { |
1267 | width_chars = 0; |
1268 | |
1269 | value = gtk_adjustment_get_lower (adjustment: spin_button->adjustment); |
1270 | str = gtk_spin_button_format_for_value (spin_button, value); |
1271 | c = g_utf8_strlen (p: str, max: -1); |
1272 | g_free (mem: str); |
1273 | |
1274 | width_chars = MAX (width_chars, c); |
1275 | |
1276 | value = gtk_adjustment_get_upper (adjustment: spin_button->adjustment); |
1277 | str = gtk_spin_button_format_for_value (spin_button, value); |
1278 | c = g_utf8_strlen (p: str, max: -1); |
1279 | g_free (mem: str); |
1280 | |
1281 | width_chars = MAX (width_chars, c); |
1282 | |
1283 | width_chars = MIN (width_chars, 10); |
1284 | } |
1285 | else |
1286 | width_chars = spin_button->width_chars; |
1287 | |
1288 | gtk_editable_set_width_chars (GTK_EDITABLE (spin_button->entry), n_chars: width_chars); |
1289 | } |
1290 | |
1291 | static void |
1292 | gtk_spin_button_state_flags_changed (GtkWidget *widget, |
1293 | GtkStateFlags previous_state) |
1294 | { |
1295 | GtkSpinButton *spin = GTK_SPIN_BUTTON (widget); |
1296 | |
1297 | if (!gtk_widget_is_sensitive (widget)) |
1298 | gtk_spin_button_stop_spinning (spin); |
1299 | |
1300 | GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->state_flags_changed (widget, previous_state); |
1301 | } |
1302 | |
1303 | static int |
1304 | gtk_spin_button_timer (GtkSpinButton *spin_button) |
1305 | { |
1306 | gboolean retval = FALSE; |
1307 | |
1308 | if (spin_button->timer) |
1309 | { |
1310 | if (spin_button->click_child == spin_button->up_button) |
1311 | gtk_spin_button_real_spin (spin_button, step: spin_button->timer_step); |
1312 | else |
1313 | gtk_spin_button_real_spin (spin_button, step: -spin_button->timer_step); |
1314 | |
1315 | if (spin_button->need_timer) |
1316 | { |
1317 | spin_button->need_timer = FALSE; |
1318 | spin_button->timer = g_timeout_add (TIMEOUT_REPEAT, |
1319 | function: (GSourceFunc) gtk_spin_button_timer, |
1320 | data: spin_button); |
1321 | gdk_source_set_static_name_by_id (tag: spin_button->timer, name: "[gtk] gtk_spin_button_timer" ); |
1322 | } |
1323 | else |
1324 | { |
1325 | if (spin_button->climb_rate > 0.0 && spin_button->timer_step |
1326 | < gtk_adjustment_get_page_increment (adjustment: spin_button->adjustment)) |
1327 | { |
1328 | if (spin_button->timer_calls < MAX_TIMER_CALLS) |
1329 | spin_button->timer_calls++; |
1330 | else |
1331 | { |
1332 | spin_button->timer_calls = 0; |
1333 | spin_button->timer_step += spin_button->climb_rate; |
1334 | } |
1335 | } |
1336 | retval = TRUE; |
1337 | } |
1338 | } |
1339 | |
1340 | return retval; |
1341 | } |
1342 | |
1343 | static void |
1344 | gtk_spin_button_value_changed (GtkAdjustment *adjustment, |
1345 | GtkSpinButton *spin_button) |
1346 | { |
1347 | gboolean return_val; |
1348 | |
1349 | g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment)); |
1350 | |
1351 | return_val = FALSE; |
1352 | g_signal_emit (instance: spin_button, signal_id: spinbutton_signals[OUTPUT], detail: 0, &return_val); |
1353 | if (return_val == FALSE) |
1354 | gtk_spin_button_default_output (spin_button); |
1355 | |
1356 | g_signal_emit (instance: spin_button, signal_id: spinbutton_signals[VALUE_CHANGED], detail: 0); |
1357 | |
1358 | gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: spin_button), |
1359 | first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (adjustment), |
1360 | -1); |
1361 | |
1362 | update_buttons_sensitivity (spin_button); |
1363 | |
1364 | g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_VALUE]); |
1365 | } |
1366 | |
1367 | static void |
1368 | gtk_spin_button_real_change_value (GtkSpinButton *spin, |
1369 | GtkScrollType scroll) |
1370 | { |
1371 | double old_value; |
1372 | |
1373 | if (!gtk_editable_get_editable (GTK_EDITABLE (spin->entry))) |
1374 | { |
1375 | gtk_widget_error_bell (GTK_WIDGET (spin)); |
1376 | return; |
1377 | } |
1378 | |
1379 | /* When the key binding is activated, there may be an outstanding |
1380 | * value, so we first have to commit what is currently written in |
1381 | * the spin buttons text entry. See #106574 |
1382 | */ |
1383 | gtk_spin_button_update (spin_button: spin); |
1384 | |
1385 | old_value = gtk_adjustment_get_value (adjustment: spin->adjustment); |
1386 | |
1387 | switch (scroll) |
1388 | { |
1389 | case GTK_SCROLL_STEP_BACKWARD: |
1390 | case GTK_SCROLL_STEP_DOWN: |
1391 | case GTK_SCROLL_STEP_LEFT: |
1392 | gtk_spin_button_real_spin (spin_button: spin, step: -spin->timer_step); |
1393 | |
1394 | if (spin->climb_rate > 0.0 && |
1395 | spin->timer_step < gtk_adjustment_get_page_increment (adjustment: spin->adjustment)) |
1396 | { |
1397 | if (spin->timer_calls < MAX_TIMER_CALLS) |
1398 | spin->timer_calls++; |
1399 | else |
1400 | { |
1401 | spin->timer_calls = 0; |
1402 | spin->timer_step += spin->climb_rate; |
1403 | } |
1404 | } |
1405 | break; |
1406 | |
1407 | case GTK_SCROLL_STEP_FORWARD: |
1408 | case GTK_SCROLL_STEP_UP: |
1409 | case GTK_SCROLL_STEP_RIGHT: |
1410 | gtk_spin_button_real_spin (spin_button: spin, step: spin->timer_step); |
1411 | |
1412 | if (spin->climb_rate > 0.0 && |
1413 | spin->timer_step < gtk_adjustment_get_page_increment (adjustment: spin->adjustment)) |
1414 | { |
1415 | if (spin->timer_calls < MAX_TIMER_CALLS) |
1416 | spin->timer_calls++; |
1417 | else |
1418 | { |
1419 | spin->timer_calls = 0; |
1420 | spin->timer_step += spin->climb_rate; |
1421 | } |
1422 | } |
1423 | break; |
1424 | |
1425 | case GTK_SCROLL_PAGE_BACKWARD: |
1426 | case GTK_SCROLL_PAGE_DOWN: |
1427 | case GTK_SCROLL_PAGE_LEFT: |
1428 | gtk_spin_button_real_spin (spin_button: spin, step: -gtk_adjustment_get_page_increment (adjustment: spin->adjustment)); |
1429 | break; |
1430 | |
1431 | case GTK_SCROLL_PAGE_FORWARD: |
1432 | case GTK_SCROLL_PAGE_UP: |
1433 | case GTK_SCROLL_PAGE_RIGHT: |
1434 | gtk_spin_button_real_spin (spin_button: spin, step: gtk_adjustment_get_page_increment (adjustment: spin->adjustment)); |
1435 | break; |
1436 | |
1437 | case GTK_SCROLL_START: |
1438 | { |
1439 | double diff = gtk_adjustment_get_value (adjustment: spin->adjustment) - gtk_adjustment_get_lower (adjustment: spin->adjustment); |
1440 | if (diff > EPSILON) |
1441 | gtk_spin_button_real_spin (spin_button: spin, step: -diff); |
1442 | break; |
1443 | } |
1444 | |
1445 | case GTK_SCROLL_END: |
1446 | { |
1447 | double diff = gtk_adjustment_get_upper (adjustment: spin->adjustment) - gtk_adjustment_get_value (adjustment: spin->adjustment); |
1448 | if (diff > EPSILON) |
1449 | gtk_spin_button_real_spin (spin_button: spin, step: diff); |
1450 | break; |
1451 | } |
1452 | |
1453 | case GTK_SCROLL_NONE: |
1454 | case GTK_SCROLL_JUMP: |
1455 | default: |
1456 | g_warning ("Invalid scroll type %d for GtkSpinButton::change-value" , scroll); |
1457 | break; |
1458 | } |
1459 | |
1460 | gtk_spin_button_update (spin_button: spin); |
1461 | |
1462 | if (gtk_adjustment_get_value (adjustment: spin->adjustment) == old_value) |
1463 | gtk_widget_error_bell (GTK_WIDGET (spin)); |
1464 | } |
1465 | |
1466 | static void |
1467 | gtk_spin_button_snap (GtkSpinButton *spin_button, |
1468 | double val) |
1469 | { |
1470 | double inc; |
1471 | double tmp; |
1472 | |
1473 | inc = gtk_adjustment_get_step_increment (adjustment: spin_button->adjustment); |
1474 | if (inc == 0) |
1475 | return; |
1476 | |
1477 | tmp = (val - gtk_adjustment_get_lower (adjustment: spin_button->adjustment)) / inc; |
1478 | if (tmp - floor (x: tmp) < ceil (x: tmp) - tmp) |
1479 | val = gtk_adjustment_get_lower (adjustment: spin_button->adjustment) + floor (x: tmp) * inc; |
1480 | else |
1481 | val = gtk_adjustment_get_lower (adjustment: spin_button->adjustment) + ceil (x: tmp) * inc; |
1482 | |
1483 | gtk_spin_button_set_value (spin_button, value: val); |
1484 | } |
1485 | |
1486 | static void |
1487 | gtk_spin_button_activate (GtkText *entry, |
1488 | gpointer user_data) |
1489 | { |
1490 | GtkSpinButton *spin_button = user_data; |
1491 | |
1492 | if (gtk_editable_get_editable (GTK_EDITABLE (spin_button->entry))) |
1493 | gtk_spin_button_update (spin_button); |
1494 | } |
1495 | |
1496 | static void |
1497 | gtk_spin_button_insert_text (GtkEditable *editable, |
1498 | const char *new_text, |
1499 | int new_text_length, |
1500 | int *position) |
1501 | { |
1502 | GtkSpinButton *spin = GTK_SPIN_BUTTON (editable); |
1503 | |
1504 | if (spin->numeric) |
1505 | { |
1506 | struct lconv *lc; |
1507 | gboolean sign; |
1508 | int dotpos = -1; |
1509 | int i; |
1510 | guint32 pos_sign; |
1511 | guint32 neg_sign; |
1512 | int entry_length; |
1513 | const char *entry_text; |
1514 | |
1515 | entry_text = gtk_editable_get_text (GTK_EDITABLE (spin->entry)); |
1516 | entry_length = g_utf8_strlen (p: entry_text, max: -1); |
1517 | |
1518 | lc = localeconv (); |
1519 | |
1520 | if (*(lc->negative_sign)) |
1521 | neg_sign = *(lc->negative_sign); |
1522 | else |
1523 | neg_sign = '-'; |
1524 | |
1525 | if (*(lc->positive_sign)) |
1526 | pos_sign = *(lc->positive_sign); |
1527 | else |
1528 | pos_sign = '+'; |
1529 | |
1530 | #ifdef G_OS_WIN32 |
1531 | /* Workaround for bug caused by some Windows application messing |
1532 | * up the positive sign of the current locale, more specifically |
1533 | * HKEY_CURRENT_USER\Control Panel\International\sPositiveSign. |
1534 | * See bug #330743 and for instance |
1535 | * http://www.msnewsgroups.net/group/microsoft.public.dotnet.languages.csharp/topic36024.aspx |
1536 | * |
1537 | * I don't know if the positive sign always gets bogusly set to |
1538 | * a digit when the above Registry value is corrupted as |
1539 | * described. (In my test case, it got set to "8", and in the |
1540 | * bug report above it presumably was set to "0".) Probably it |
1541 | * might get set to almost anything? So how to distinguish a |
1542 | * bogus value from some correct one for some locale? That is |
1543 | * probably hard, but at least we should filter out the |
1544 | * digits... |
1545 | */ |
1546 | if (pos_sign >= '0' && pos_sign <= '9') |
1547 | pos_sign = '+'; |
1548 | #endif |
1549 | |
1550 | for (sign = 0, i = 0; i<entry_length; i++) |
1551 | if ((entry_text[i] == neg_sign) || |
1552 | (entry_text[i] == pos_sign)) |
1553 | { |
1554 | sign = 1; |
1555 | break; |
1556 | } |
1557 | |
1558 | if (sign && !(*position)) |
1559 | return; |
1560 | |
1561 | for (dotpos = -1, i = 0; i<entry_length; i++) |
1562 | if (entry_text[i] == *(lc->decimal_point)) |
1563 | { |
1564 | dotpos = i; |
1565 | break; |
1566 | } |
1567 | |
1568 | if (dotpos > -1 && *position > dotpos && |
1569 | (int)spin->digits - entry_length |
1570 | + dotpos - new_text_length + 1 < 0) |
1571 | return; |
1572 | |
1573 | for (i = 0; i < new_text_length; i++) |
1574 | { |
1575 | if (new_text[i] == neg_sign || new_text[i] == pos_sign) |
1576 | { |
1577 | if (sign || (*position) || i) |
1578 | return; |
1579 | sign = TRUE; |
1580 | } |
1581 | else if (new_text[i] == *(lc->decimal_point)) |
1582 | { |
1583 | if (!spin->digits || dotpos > -1 || |
1584 | (new_text_length - 1 - i + entry_length - *position > (int)spin->digits)) |
1585 | return; |
1586 | dotpos = *position + i; |
1587 | } |
1588 | else if (new_text[i] < 0x30 || new_text[i] > 0x39) |
1589 | return; |
1590 | } |
1591 | } |
1592 | |
1593 | gtk_editable_insert_text (GTK_EDITABLE (spin->entry), |
1594 | text: new_text, length: new_text_length, position); |
1595 | } |
1596 | |
1597 | static void |
1598 | gtk_spin_button_real_spin (GtkSpinButton *spin_button, |
1599 | double increment) |
1600 | { |
1601 | GtkAdjustment *adjustment; |
1602 | double new_value = 0.0; |
1603 | gboolean wrapped = FALSE; |
1604 | |
1605 | adjustment = spin_button->adjustment; |
1606 | |
1607 | new_value = gtk_adjustment_get_value (adjustment) + increment; |
1608 | |
1609 | if (increment > 0) |
1610 | { |
1611 | if (spin_button->wrap) |
1612 | { |
1613 | if (fabs (x: gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_upper (adjustment)) < EPSILON) |
1614 | { |
1615 | new_value = gtk_adjustment_get_lower (adjustment); |
1616 | wrapped = TRUE; |
1617 | } |
1618 | else if (new_value > gtk_adjustment_get_upper (adjustment)) |
1619 | new_value = gtk_adjustment_get_upper (adjustment); |
1620 | } |
1621 | else |
1622 | new_value = MIN (new_value, gtk_adjustment_get_upper (adjustment)); |
1623 | } |
1624 | else if (increment < 0) |
1625 | { |
1626 | if (spin_button->wrap) |
1627 | { |
1628 | if (fabs (x: gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_lower (adjustment)) < EPSILON) |
1629 | { |
1630 | new_value = gtk_adjustment_get_upper (adjustment); |
1631 | wrapped = TRUE; |
1632 | } |
1633 | else if (new_value < gtk_adjustment_get_lower (adjustment)) |
1634 | new_value = gtk_adjustment_get_lower (adjustment); |
1635 | } |
1636 | else |
1637 | new_value = MAX (new_value, gtk_adjustment_get_lower (adjustment)); |
1638 | } |
1639 | |
1640 | if (fabs (x: new_value - gtk_adjustment_get_value (adjustment)) > EPSILON) |
1641 | gtk_adjustment_set_value (adjustment, value: new_value); |
1642 | |
1643 | if (wrapped) |
1644 | g_signal_emit (instance: spin_button, signal_id: spinbutton_signals[WRAPPED], detail: 0); |
1645 | } |
1646 | |
1647 | static int |
1648 | gtk_spin_button_default_input (GtkSpinButton *spin_button, |
1649 | double *new_val) |
1650 | { |
1651 | char *err = NULL; |
1652 | const char *text = gtk_editable_get_text (GTK_EDITABLE (spin_button->entry)); |
1653 | |
1654 | *new_val = g_strtod (nptr: text, endptr: &err); |
1655 | if (*err) |
1656 | { |
1657 | /* try to convert with local digits */ |
1658 | gint64 val = 0; |
1659 | int sign = 1; |
1660 | const char *p; |
1661 | |
1662 | for (p = text; *p; p = g_utf8_next_char (p)) |
1663 | { |
1664 | gunichar ch = g_utf8_get_char (p); |
1665 | |
1666 | if (p == text && ch == '-') |
1667 | { |
1668 | sign = -1; |
1669 | continue; |
1670 | } |
1671 | |
1672 | if (!g_unichar_isdigit (c: ch)) |
1673 | break; |
1674 | |
1675 | val = val * 10 + g_unichar_digit_value (c: ch); |
1676 | } |
1677 | |
1678 | if (*p) |
1679 | return GTK_INPUT_ERROR; |
1680 | |
1681 | *new_val = sign * val; |
1682 | } |
1683 | |
1684 | return FALSE; |
1685 | } |
1686 | |
1687 | static void |
1688 | gtk_spin_button_default_output (GtkSpinButton *spin_button) |
1689 | { |
1690 | char *buf = gtk_spin_button_format_for_value (spin_button, |
1691 | value: gtk_adjustment_get_value (adjustment: spin_button->adjustment)); |
1692 | |
1693 | if (strcmp (s1: buf, s2: gtk_editable_get_text (GTK_EDITABLE (spin_button->entry)))) |
1694 | gtk_editable_set_text (GTK_EDITABLE (spin_button->entry), text: buf); |
1695 | |
1696 | g_free (mem: buf); |
1697 | } |
1698 | |
1699 | |
1700 | /*********************************************************** |
1701 | *********************************************************** |
1702 | *** Public interface *** |
1703 | *********************************************************** |
1704 | ***********************************************************/ |
1705 | |
1706 | /** |
1707 | * gtk_spin_button_configure: |
1708 | * @spin_button: a `GtkSpinButton` |
1709 | * @adjustment: (nullable): a `GtkAdjustment` to replace the spin button’s |
1710 | * existing adjustment, or %NULL to leave its current adjustment unchanged |
1711 | * @climb_rate: the new climb rate |
1712 | * @digits: the number of decimal places to display in the spin button |
1713 | * |
1714 | * Changes the properties of an existing spin button. |
1715 | * |
1716 | * The adjustment, climb rate, and number of decimal places |
1717 | * are updated accordingly. |
1718 | */ |
1719 | void |
1720 | gtk_spin_button_configure (GtkSpinButton *spin_button, |
1721 | GtkAdjustment *adjustment, |
1722 | double climb_rate, |
1723 | guint digits) |
1724 | { |
1725 | g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); |
1726 | |
1727 | if (!adjustment) |
1728 | adjustment = spin_button->adjustment; |
1729 | |
1730 | g_object_freeze_notify (G_OBJECT (spin_button)); |
1731 | |
1732 | if (spin_button->adjustment != adjustment) |
1733 | { |
1734 | gtk_spin_button_unset_adjustment (spin_button); |
1735 | |
1736 | spin_button->adjustment = adjustment; |
1737 | g_object_ref_sink (adjustment); |
1738 | g_signal_connect (adjustment, "value-changed" , |
1739 | G_CALLBACK (gtk_spin_button_value_changed), |
1740 | spin_button); |
1741 | g_signal_connect (adjustment, "changed" , |
1742 | G_CALLBACK (adjustment_changed_cb), |
1743 | spin_button); |
1744 | spin_button->timer_step = gtk_adjustment_get_step_increment (adjustment: spin_button->adjustment); |
1745 | |
1746 | g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_ADJUSTMENT]); |
1747 | gtk_widget_queue_resize (GTK_WIDGET (spin_button)); |
1748 | } |
1749 | |
1750 | if (spin_button->digits != digits) |
1751 | { |
1752 | spin_button->digits = digits; |
1753 | g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_DIGITS]); |
1754 | } |
1755 | |
1756 | if (spin_button->climb_rate != climb_rate) |
1757 | { |
1758 | spin_button->climb_rate = climb_rate; |
1759 | g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_CLIMB_RATE]); |
1760 | } |
1761 | |
1762 | gtk_spin_button_update_width_chars (spin_button); |
1763 | |
1764 | g_object_thaw_notify (G_OBJECT (spin_button)); |
1765 | |
1766 | gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: spin_button), |
1767 | first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, gtk_adjustment_get_upper (adjustment), |
1768 | GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, gtk_adjustment_get_lower (adjustment), |
1769 | GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (adjustment), |
1770 | -1); |
1771 | |
1772 | gtk_spin_button_value_changed (adjustment, spin_button); |
1773 | } |
1774 | |
1775 | /** |
1776 | * gtk_spin_button_new: |
1777 | * @adjustment: (nullable): the `GtkAdjustment` that this spin button should use |
1778 | * @climb_rate: specifies by how much the rate of change in the value will |
1779 | * accelerate if you continue to hold down an up/down button or arrow key |
1780 | * @digits: the number of decimal places to display |
1781 | * |
1782 | * Creates a new `GtkSpinButton`. |
1783 | * |
1784 | * Returns: The new `GtkSpinButton` |
1785 | */ |
1786 | GtkWidget * |
1787 | gtk_spin_button_new (GtkAdjustment *adjustment, |
1788 | double climb_rate, |
1789 | guint digits) |
1790 | { |
1791 | GtkSpinButton *spin; |
1792 | |
1793 | if (adjustment) |
1794 | g_return_val_if_fail (GTK_IS_ADJUSTMENT (adjustment), NULL); |
1795 | |
1796 | spin = g_object_new (GTK_TYPE_SPIN_BUTTON, NULL); |
1797 | |
1798 | gtk_spin_button_configure (spin_button: spin, adjustment, climb_rate, digits); |
1799 | |
1800 | return GTK_WIDGET (spin); |
1801 | } |
1802 | |
1803 | /** |
1804 | * gtk_spin_button_new_with_range: |
1805 | * @min: Minimum allowable value |
1806 | * @max: Maximum allowable value |
1807 | * @step: Increment added or subtracted by spinning the widget |
1808 | * |
1809 | * Creates a new `GtkSpinButton` with the given properties. |
1810 | * |
1811 | * This is a convenience constructor that allows creation |
1812 | * of a numeric `GtkSpinButton` without manually creating |
1813 | * an adjustment. The value is initially set to the minimum |
1814 | * value and a page increment of 10 * @step is the default. |
1815 | * The precision of the spin button is equivalent to the |
1816 | * precision of @step. |
1817 | * |
1818 | * Note that the way in which the precision is derived works |
1819 | * best if @step is a power of ten. If the resulting precision |
1820 | * is not suitable for your needs, use |
1821 | * [method@Gtk.SpinButton.set_digits] to correct it. |
1822 | * |
1823 | * Returns: The new `GtkSpinButton` |
1824 | */ |
1825 | GtkWidget * |
1826 | gtk_spin_button_new_with_range (double min, |
1827 | double max, |
1828 | double step) |
1829 | { |
1830 | GtkAdjustment *adjustment; |
1831 | GtkSpinButton *spin; |
1832 | int digits; |
1833 | |
1834 | g_return_val_if_fail (min <= max, NULL); |
1835 | g_return_val_if_fail (step != 0.0, NULL); |
1836 | |
1837 | spin = g_object_new (GTK_TYPE_SPIN_BUTTON, NULL); |
1838 | |
1839 | adjustment = gtk_adjustment_new (value: min, lower: min, upper: max, step_increment: step, page_increment: 10 * step, page_size: 0); |
1840 | |
1841 | if (fabs (x: step) >= 1.0 || step == 0.0) |
1842 | digits = 0; |
1843 | else { |
1844 | digits = abs (x: (int) floor (x: log10 (x: fabs (x: step)))); |
1845 | if (digits > MAX_DIGITS) |
1846 | digits = MAX_DIGITS; |
1847 | } |
1848 | |
1849 | gtk_spin_button_configure (spin_button: spin, adjustment, climb_rate: step, digits); |
1850 | |
1851 | gtk_spin_button_set_numeric (spin_button: spin, TRUE); |
1852 | |
1853 | return GTK_WIDGET (spin); |
1854 | } |
1855 | |
1856 | /** |
1857 | * gtk_spin_button_set_adjustment: (attributes org.gtk.Method.set_property=adjustment) |
1858 | * @spin_button: a `GtkSpinButton` |
1859 | * @adjustment: a `GtkAdjustment` to replace the existing adjustment |
1860 | * |
1861 | * Replaces the `GtkAdjustment` associated with @spin_button. |
1862 | */ |
1863 | void |
1864 | gtk_spin_button_set_adjustment (GtkSpinButton *spin_button, |
1865 | GtkAdjustment *adjustment) |
1866 | { |
1867 | g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); |
1868 | |
1869 | if (!adjustment) |
1870 | adjustment = gtk_adjustment_new (value: 0.0, lower: 0.0, upper: 0.0, step_increment: 0.0, page_increment: 0.0, page_size: 0.0); |
1871 | |
1872 | gtk_spin_button_configure (spin_button, |
1873 | adjustment, |
1874 | climb_rate: spin_button->climb_rate, |
1875 | digits: spin_button->digits); |
1876 | } |
1877 | |
1878 | /** |
1879 | * gtk_spin_button_get_adjustment: (attributes org.gtk.Method.get_property=adjustment) |
1880 | * @spin_button: a `GtkSpinButton` |
1881 | * |
1882 | * Get the adjustment associated with a `GtkSpinButton`. |
1883 | * |
1884 | * Returns: (transfer none): the `GtkAdjustment` of @spin_button |
1885 | */ |
1886 | GtkAdjustment * |
1887 | gtk_spin_button_get_adjustment (GtkSpinButton *spin_button) |
1888 | { |
1889 | g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), NULL); |
1890 | |
1891 | return spin_button->adjustment; |
1892 | } |
1893 | |
1894 | /** |
1895 | * gtk_spin_button_set_digits: (attributes org.gtk.Method.set_property=digits) |
1896 | * @spin_button: a `GtkSpinButton` |
1897 | * @digits: the number of digits after the decimal point to be |
1898 | * displayed for the spin button’s value |
1899 | * |
1900 | * Set the precision to be displayed by @spin_button. |
1901 | * |
1902 | * Up to 20 digit precision is allowed. |
1903 | */ |
1904 | void |
1905 | gtk_spin_button_set_digits (GtkSpinButton *spin_button, |
1906 | guint digits) |
1907 | { |
1908 | g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); |
1909 | |
1910 | if (spin_button->digits != digits) |
1911 | { |
1912 | spin_button->digits = digits; |
1913 | gtk_spin_button_value_changed (adjustment: spin_button->adjustment, spin_button); |
1914 | g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_DIGITS]); |
1915 | |
1916 | /* since lower/upper may have changed */ |
1917 | gtk_widget_queue_resize (GTK_WIDGET (spin_button)); |
1918 | } |
1919 | } |
1920 | |
1921 | /** |
1922 | * gtk_spin_button_get_digits: (attributes org.gtk.Method.get_property=digits) |
1923 | * @spin_button: a `GtkSpinButton` |
1924 | * |
1925 | * Fetches the precision of @spin_button. |
1926 | * |
1927 | * Returns: the current precision |
1928 | **/ |
1929 | guint |
1930 | gtk_spin_button_get_digits (GtkSpinButton *spin_button) |
1931 | { |
1932 | g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0); |
1933 | |
1934 | return spin_button->digits; |
1935 | } |
1936 | |
1937 | /** |
1938 | * gtk_spin_button_set_increments: |
1939 | * @spin_button: a `GtkSpinButton` |
1940 | * @step: increment applied for a button 1 press. |
1941 | * @page: increment applied for a button 2 press. |
1942 | * |
1943 | * Sets the step and page increments for spin_button. |
1944 | * |
1945 | * This affects how quickly the value changes when |
1946 | * the spin button’s arrows are activated. |
1947 | */ |
1948 | void |
1949 | gtk_spin_button_set_increments (GtkSpinButton *spin_button, |
1950 | double step, |
1951 | double page) |
1952 | { |
1953 | g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); |
1954 | |
1955 | gtk_adjustment_configure (adjustment: spin_button->adjustment, |
1956 | value: gtk_adjustment_get_value (adjustment: spin_button->adjustment), |
1957 | lower: gtk_adjustment_get_lower (adjustment: spin_button->adjustment), |
1958 | upper: gtk_adjustment_get_upper (adjustment: spin_button->adjustment), |
1959 | step_increment: step, |
1960 | page_increment: page, |
1961 | page_size: gtk_adjustment_get_page_size (adjustment: spin_button->adjustment)); |
1962 | } |
1963 | |
1964 | /** |
1965 | * gtk_spin_button_get_increments: |
1966 | * @spin_button: a `GtkSpinButton` |
1967 | * @step: (out) (optional): location to store step increment |
1968 | * @page: (out) (optional): location to store page increment |
1969 | * |
1970 | * Gets the current step and page the increments |
1971 | * used by @spin_button. |
1972 | * |
1973 | * See [method@Gtk.SpinButton.set_increments]. |
1974 | */ |
1975 | void |
1976 | gtk_spin_button_get_increments (GtkSpinButton *spin_button, |
1977 | double *step, |
1978 | double *page) |
1979 | { |
1980 | g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); |
1981 | |
1982 | if (step) |
1983 | *step = gtk_adjustment_get_step_increment (adjustment: spin_button->adjustment); |
1984 | if (page) |
1985 | *page = gtk_adjustment_get_page_increment (adjustment: spin_button->adjustment); |
1986 | } |
1987 | |
1988 | /** |
1989 | * gtk_spin_button_set_range: |
1990 | * @spin_button: a `GtkSpinButton` |
1991 | * @min: minimum allowable value |
1992 | * @max: maximum allowable value |
1993 | * |
1994 | * Sets the minimum and maximum allowable values for @spin_button. |
1995 | * |
1996 | * If the current value is outside this range, it will be adjusted |
1997 | * to fit within the range, otherwise it will remain unchanged. |
1998 | */ |
1999 | void |
2000 | gtk_spin_button_set_range (GtkSpinButton *spin_button, |
2001 | double min, |
2002 | double max) |
2003 | { |
2004 | GtkAdjustment *adjustment; |
2005 | |
2006 | g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); |
2007 | |
2008 | adjustment = spin_button->adjustment; |
2009 | |
2010 | gtk_adjustment_configure (adjustment, |
2011 | CLAMP (gtk_adjustment_get_value (adjustment), min, max), |
2012 | lower: min, |
2013 | upper: max, |
2014 | step_increment: gtk_adjustment_get_step_increment (adjustment), |
2015 | page_increment: gtk_adjustment_get_page_increment (adjustment), |
2016 | page_size: gtk_adjustment_get_page_size (adjustment)); |
2017 | } |
2018 | |
2019 | /** |
2020 | * gtk_spin_button_get_range: |
2021 | * @spin_button: a `GtkSpinButton` |
2022 | * @min: (out) (optional): location to store minimum allowed value |
2023 | * @max: (out) (optional): location to store maximum allowed value |
2024 | * |
2025 | * Gets the range allowed for @spin_button. |
2026 | * |
2027 | * See [method@Gtk.SpinButton.set_range]. |
2028 | */ |
2029 | void |
2030 | gtk_spin_button_get_range (GtkSpinButton *spin_button, |
2031 | double *min, |
2032 | double *max) |
2033 | { |
2034 | g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); |
2035 | |
2036 | if (min) |
2037 | *min = gtk_adjustment_get_lower (adjustment: spin_button->adjustment); |
2038 | if (max) |
2039 | *max = gtk_adjustment_get_upper (adjustment: spin_button->adjustment); |
2040 | } |
2041 | |
2042 | /** |
2043 | * gtk_spin_button_get_value: (attributes org.gtk.Method.get_property=value) |
2044 | * @spin_button: a `GtkSpinButton` |
2045 | * |
2046 | * Get the value in the @spin_button. |
2047 | * |
2048 | * Returns: the value of @spin_button |
2049 | */ |
2050 | double |
2051 | gtk_spin_button_get_value (GtkSpinButton *spin_button) |
2052 | { |
2053 | g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0.0); |
2054 | |
2055 | return gtk_adjustment_get_value (adjustment: spin_button->adjustment); |
2056 | } |
2057 | |
2058 | /** |
2059 | * gtk_spin_button_get_value_as_int: |
2060 | * @spin_button: a `GtkSpinButton` |
2061 | * |
2062 | * Get the value @spin_button represented as an integer. |
2063 | * |
2064 | * Returns: the value of @spin_button |
2065 | */ |
2066 | int |
2067 | gtk_spin_button_get_value_as_int (GtkSpinButton *spin_button) |
2068 | { |
2069 | double val; |
2070 | |
2071 | g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0); |
2072 | |
2073 | val = gtk_adjustment_get_value (adjustment: spin_button->adjustment); |
2074 | if (val - floor (x: val) < ceil (x: val) - val) |
2075 | return floor (x: val); |
2076 | else |
2077 | return ceil (x: val); |
2078 | } |
2079 | |
2080 | /** |
2081 | * gtk_spin_button_set_value: (attributes org.gtk.Method.set_property=value) |
2082 | * @spin_button: a `GtkSpinButton` |
2083 | * @value: the new value |
2084 | * |
2085 | * Sets the value of @spin_button. |
2086 | */ |
2087 | void |
2088 | gtk_spin_button_set_value (GtkSpinButton *spin_button, |
2089 | double value) |
2090 | { |
2091 | g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); |
2092 | |
2093 | if (fabs (x: value - gtk_adjustment_get_value (adjustment: spin_button->adjustment)) > EPSILON) |
2094 | gtk_adjustment_set_value (adjustment: spin_button->adjustment, value); |
2095 | else |
2096 | { |
2097 | int return_val = FALSE; |
2098 | g_signal_emit (instance: spin_button, signal_id: spinbutton_signals[OUTPUT], detail: 0, &return_val); |
2099 | if (!return_val) |
2100 | gtk_spin_button_default_output (spin_button); |
2101 | } |
2102 | } |
2103 | |
2104 | /** |
2105 | * gtk_spin_button_set_update_policy: (attributes org.gtk.Method.set_property=update-policy) |
2106 | * @spin_button: a `GtkSpinButton` |
2107 | * @policy: a `GtkSpinButtonUpdatePolicy` value |
2108 | * |
2109 | * Sets the update behavior of a spin button. |
2110 | * |
2111 | * This determines whether the spin button is always |
2112 | * updated or only when a valid value is set. |
2113 | */ |
2114 | void |
2115 | gtk_spin_button_set_update_policy (GtkSpinButton *spin_button, |
2116 | GtkSpinButtonUpdatePolicy policy) |
2117 | { |
2118 | g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); |
2119 | |
2120 | if (spin_button->update_policy != policy) |
2121 | { |
2122 | spin_button->update_policy = policy; |
2123 | g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_UPDATE_POLICY]); |
2124 | } |
2125 | } |
2126 | |
2127 | /** |
2128 | * gtk_spin_button_get_update_policy: (attributes org.gtk.Method.get_property=update-policy) |
2129 | * @spin_button: a `GtkSpinButton` |
2130 | * |
2131 | * Gets the update behavior of a spin button. |
2132 | * |
2133 | * See [method@Gtk.SpinButton.set_update_policy]. |
2134 | * |
2135 | * Returns: the current update policy |
2136 | */ |
2137 | GtkSpinButtonUpdatePolicy |
2138 | gtk_spin_button_get_update_policy (GtkSpinButton *spin_button) |
2139 | { |
2140 | g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), GTK_UPDATE_ALWAYS); |
2141 | |
2142 | return spin_button->update_policy; |
2143 | } |
2144 | |
2145 | /** |
2146 | * gtk_spin_button_set_numeric: (attributes org.gtk.Method.set_property=numeric) |
2147 | * @spin_button: a `GtkSpinButton` |
2148 | * @numeric: flag indicating if only numeric entry is allowed |
2149 | * |
2150 | * Sets the flag that determines if non-numeric text can be typed |
2151 | * into the spin button. |
2152 | */ |
2153 | void |
2154 | gtk_spin_button_set_numeric (GtkSpinButton *spin_button, |
2155 | gboolean numeric) |
2156 | { |
2157 | g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); |
2158 | |
2159 | numeric = numeric != FALSE; |
2160 | |
2161 | if (spin_button->numeric != numeric) |
2162 | { |
2163 | spin_button->numeric = numeric; |
2164 | g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_NUMERIC]); |
2165 | } |
2166 | } |
2167 | |
2168 | /** |
2169 | * gtk_spin_button_get_numeric: (attributes org.gtk.Method.get_property=numeric) |
2170 | * @spin_button: a `GtkSpinButton` |
2171 | * |
2172 | * Returns whether non-numeric text can be typed into the spin button. |
2173 | * |
2174 | * Returns: %TRUE if only numeric text can be entered |
2175 | */ |
2176 | gboolean |
2177 | gtk_spin_button_get_numeric (GtkSpinButton *spin_button) |
2178 | { |
2179 | g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE); |
2180 | |
2181 | return spin_button->numeric; |
2182 | } |
2183 | |
2184 | /** |
2185 | * gtk_spin_button_set_wrap: (attributes org.gtk.Method.set_property=wrap) |
2186 | * @spin_button: a `GtkSpinButton` |
2187 | * @wrap: a flag indicating if wrapping behavior is performed |
2188 | * |
2189 | * Sets the flag that determines if a spin button value wraps |
2190 | * around to the opposite limit when the upper or lower limit |
2191 | * of the range is exceeded. |
2192 | */ |
2193 | void |
2194 | gtk_spin_button_set_wrap (GtkSpinButton *spin_button, |
2195 | gboolean wrap) |
2196 | { |
2197 | g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); |
2198 | |
2199 | wrap = wrap != FALSE; |
2200 | |
2201 | if (spin_button->wrap != wrap) |
2202 | { |
2203 | spin_button->wrap = wrap; |
2204 | g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_WRAP]); |
2205 | |
2206 | update_buttons_sensitivity (spin_button); |
2207 | } |
2208 | } |
2209 | |
2210 | /** |
2211 | * gtk_spin_button_get_wrap: (attributes org.gtk.Method.get_property=wrap) |
2212 | * @spin_button: a `GtkSpinButton` |
2213 | * |
2214 | * Returns whether the spin button’s value wraps around to the |
2215 | * opposite limit when the upper or lower limit of the range is |
2216 | * exceeded. |
2217 | * |
2218 | * Returns: %TRUE if the spin button wraps around |
2219 | */ |
2220 | gboolean |
2221 | gtk_spin_button_get_wrap (GtkSpinButton *spin_button) |
2222 | { |
2223 | g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE); |
2224 | |
2225 | return spin_button->wrap; |
2226 | } |
2227 | |
2228 | /** |
2229 | * gtk_spin_button_set_snap_to_ticks: (attributes org.gtk.Method.set_property=snap-to-ticks) |
2230 | * @spin_button: a `GtkSpinButton` |
2231 | * @snap_to_ticks: a flag indicating if invalid values should be corrected |
2232 | * |
2233 | * Sets the policy as to whether values are corrected to the |
2234 | * nearest step increment when a spin button is activated after |
2235 | * providing an invalid value. |
2236 | */ |
2237 | void |
2238 | gtk_spin_button_set_snap_to_ticks (GtkSpinButton *spin_button, |
2239 | gboolean snap_to_ticks) |
2240 | { |
2241 | guint new_val; |
2242 | |
2243 | g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); |
2244 | |
2245 | new_val = (snap_to_ticks != 0); |
2246 | |
2247 | if (new_val != spin_button->snap_to_ticks) |
2248 | { |
2249 | spin_button->snap_to_ticks = new_val; |
2250 | if (new_val && gtk_editable_get_editable (GTK_EDITABLE (spin_button->entry))) |
2251 | gtk_spin_button_update (spin_button); |
2252 | |
2253 | g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_SNAP_TO_TICKS]); |
2254 | } |
2255 | } |
2256 | |
2257 | /** |
2258 | * gtk_spin_button_get_snap_to_ticks: (attributes org.gtk.Method.get_property=snap-to-ticks) |
2259 | * @spin_button: a `GtkSpinButton` |
2260 | * |
2261 | * Returns whether the values are corrected to the nearest step. |
2262 | * |
2263 | * Returns: %TRUE if values are snapped to the nearest step |
2264 | */ |
2265 | gboolean |
2266 | gtk_spin_button_get_snap_to_ticks (GtkSpinButton *spin_button) |
2267 | { |
2268 | g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE); |
2269 | |
2270 | return spin_button->snap_to_ticks; |
2271 | } |
2272 | |
2273 | /** |
2274 | * gtk_spin_button_set_climb_rate: (attributes org.gtk.Method.set_property=climb-rate) |
2275 | * @spin_button: a `GtkSpinButton` |
2276 | * @climb_rate: the rate of acceleration, must be >= 0 |
2277 | * |
2278 | * Sets the acceleration rate for repeated changes when you |
2279 | * hold down a button or key. |
2280 | */ |
2281 | void |
2282 | gtk_spin_button_set_climb_rate (GtkSpinButton *spin_button, |
2283 | double climb_rate) |
2284 | { |
2285 | g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); |
2286 | g_return_if_fail (0.0 <= climb_rate); |
2287 | |
2288 | if (spin_button->climb_rate == climb_rate) |
2289 | return; |
2290 | |
2291 | spin_button->climb_rate = climb_rate; |
2292 | |
2293 | g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_CLIMB_RATE]); |
2294 | } |
2295 | |
2296 | /** |
2297 | * gtk_spin_button_get_climb_rate: (attributes org.gtk.Method.get_property=climb-rate) |
2298 | * @spin_button: a `GtkSpinButton` |
2299 | * |
2300 | * Returns the acceleration rate for repeated changes. |
2301 | * |
2302 | * Returns: the acceleration rate |
2303 | */ |
2304 | double |
2305 | gtk_spin_button_get_climb_rate (GtkSpinButton *spin_button) |
2306 | { |
2307 | g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0.0); |
2308 | |
2309 | return spin_button->climb_rate; |
2310 | } |
2311 | |
2312 | /** |
2313 | * gtk_spin_button_spin: |
2314 | * @spin_button: a `GtkSpinButton` |
2315 | * @direction: a `GtkSpinType` indicating the direction to spin |
2316 | * @increment: step increment to apply in the specified direction |
2317 | * |
2318 | * Increment or decrement a spin button’s value in a specified |
2319 | * direction by a specified amount. |
2320 | */ |
2321 | void |
2322 | gtk_spin_button_spin (GtkSpinButton *spin_button, |
2323 | GtkSpinType direction, |
2324 | double increment) |
2325 | { |
2326 | GtkAdjustment *adjustment; |
2327 | double diff; |
2328 | |
2329 | g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); |
2330 | |
2331 | adjustment = spin_button->adjustment; |
2332 | |
2333 | /* for compatibility with the 1.0.x version of this function */ |
2334 | if (increment != 0 && increment != gtk_adjustment_get_step_increment (adjustment) && |
2335 | (direction == GTK_SPIN_STEP_FORWARD || |
2336 | direction == GTK_SPIN_STEP_BACKWARD)) |
2337 | { |
2338 | if (direction == GTK_SPIN_STEP_BACKWARD && increment > 0) |
2339 | increment = -increment; |
2340 | direction = GTK_SPIN_USER_DEFINED; |
2341 | } |
2342 | |
2343 | switch (direction) |
2344 | { |
2345 | case GTK_SPIN_STEP_FORWARD: |
2346 | |
2347 | gtk_spin_button_real_spin (spin_button, increment: gtk_adjustment_get_step_increment (adjustment)); |
2348 | break; |
2349 | |
2350 | case GTK_SPIN_STEP_BACKWARD: |
2351 | |
2352 | gtk_spin_button_real_spin (spin_button, increment: -gtk_adjustment_get_step_increment (adjustment)); |
2353 | break; |
2354 | |
2355 | case GTK_SPIN_PAGE_FORWARD: |
2356 | |
2357 | gtk_spin_button_real_spin (spin_button, increment: gtk_adjustment_get_page_increment (adjustment)); |
2358 | break; |
2359 | |
2360 | case GTK_SPIN_PAGE_BACKWARD: |
2361 | |
2362 | gtk_spin_button_real_spin (spin_button, increment: -gtk_adjustment_get_page_increment (adjustment)); |
2363 | break; |
2364 | |
2365 | case GTK_SPIN_HOME: |
2366 | |
2367 | diff = gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_lower (adjustment); |
2368 | if (diff > EPSILON) |
2369 | gtk_spin_button_real_spin (spin_button, increment: -diff); |
2370 | break; |
2371 | |
2372 | case GTK_SPIN_END: |
2373 | |
2374 | diff = gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_value (adjustment); |
2375 | if (diff > EPSILON) |
2376 | gtk_spin_button_real_spin (spin_button, increment: diff); |
2377 | break; |
2378 | |
2379 | case GTK_SPIN_USER_DEFINED: |
2380 | |
2381 | if (increment != 0) |
2382 | gtk_spin_button_real_spin (spin_button, increment); |
2383 | break; |
2384 | |
2385 | default: |
2386 | break; |
2387 | } |
2388 | } |
2389 | |
2390 | /** |
2391 | * gtk_spin_button_update: |
2392 | * @spin_button: a `GtkSpinButton` |
2393 | * |
2394 | * Manually force an update of the spin button. |
2395 | */ |
2396 | void |
2397 | gtk_spin_button_update (GtkSpinButton *spin_button) |
2398 | { |
2399 | double val; |
2400 | int error = 0; |
2401 | int return_val; |
2402 | |
2403 | g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); |
2404 | |
2405 | return_val = FALSE; |
2406 | g_signal_emit (instance: spin_button, signal_id: spinbutton_signals[INPUT], detail: 0, &val, &return_val); |
2407 | if (return_val == FALSE) |
2408 | { |
2409 | return_val = gtk_spin_button_default_input (spin_button, new_val: &val); |
2410 | error = (return_val == GTK_INPUT_ERROR); |
2411 | } |
2412 | else if (return_val == GTK_INPUT_ERROR) |
2413 | error = 1; |
2414 | |
2415 | if (spin_button->update_policy == GTK_UPDATE_ALWAYS) |
2416 | { |
2417 | if (val < gtk_adjustment_get_lower (adjustment: spin_button->adjustment)) |
2418 | val = gtk_adjustment_get_lower (adjustment: spin_button->adjustment); |
2419 | else if (val > gtk_adjustment_get_upper (adjustment: spin_button->adjustment)) |
2420 | val = gtk_adjustment_get_upper (adjustment: spin_button->adjustment); |
2421 | } |
2422 | else if ((spin_button->update_policy == GTK_UPDATE_IF_VALID) && |
2423 | (error || |
2424 | val < gtk_adjustment_get_lower (adjustment: spin_button->adjustment) || |
2425 | val > gtk_adjustment_get_upper (adjustment: spin_button->adjustment))) |
2426 | { |
2427 | gtk_spin_button_value_changed (adjustment: spin_button->adjustment, spin_button); |
2428 | return; |
2429 | } |
2430 | |
2431 | if (spin_button->snap_to_ticks) |
2432 | gtk_spin_button_snap (spin_button, val); |
2433 | else |
2434 | gtk_spin_button_set_value (spin_button, value: val); |
2435 | } |
2436 | |
2437 | GtkText * |
2438 | gtk_spin_button_get_text_widget (GtkSpinButton *spin_button) |
2439 | { |
2440 | return GTK_TEXT (spin_button->entry); |
2441 | } |
2442 | |
2443 | |