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
G_DEFINE_TYPE_WITH_CODE(GtkSpinButton,gtk_spin_button,GTK_TYPE_WIDGET,G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,NULL)G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE,gtk_spin_button_accessible_init)G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,gtk_spin_button_editable_init)G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_EDITABLE,gtk_spin_button_cell_editable_init))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 (spin_button->entry);
336 }
337
338 static gboolean
gtk_spin_button_mnemonic_activate(GtkWidget * widget,gboolean group_cycling)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 (spin_button->entry);
345 }
346
347 static void
gtk_spin_button_class_init(GtkSpinButtonClass * class)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 ("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 ("climb-rate",
387 P_("Climb Rate"),
388 P_("The acceleration rate when you hold down a button or key"),
389 0.0, G_MAXDOUBLE, 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 ("digits",
399 P_("Digits"),
400 P_("The number of decimal places to display"),
401 0, MAX_DIGITS, 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 ("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 ("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 ("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 ("update-policy",
449 P_("Update Policy"),
450 P_("Whether the spin button should update always, or only when the value is legal"),
451 GTK_TYPE_SPIN_BUTTON_UPDATE_POLICY,
452 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 ("value",
462 P_("Value"),
463 P_("Reads the current value, or sets a new value"),
464 -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
465 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
466
467 g_object_class_install_properties (gobject_class, NUM_SPINBUTTON_PROPS, spinbutton_props);
468 g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation");
469 g_object_class_override_property (gobject_class, PROP_EDITING_CANCELED, "editing-canceled");
470 gtk_editable_install_properties (gobject_class, 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 G_SIGNAL_RUN_LAST,
492 G_STRUCT_OFFSET (GtkSpinButtonClass, input),
493 NULL, NULL,
494 _gtk_marshal_INT__POINTER,
495 G_TYPE_INT, 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 G_SIGNAL_RUN_LAST,
530 G_STRUCT_OFFSET (GtkSpinButtonClass, output),
531 _gtk_boolean_handled_accumulator, NULL,
532 _gtk_marshal_BOOLEAN__VOID,
533 G_TYPE_BOOLEAN, 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 G_SIGNAL_RUN_LAST,
547 G_STRUCT_OFFSET (GtkSpinButtonClass, value_changed),
548 NULL, NULL,
549 NULL,
550 G_TYPE_NONE, 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 G_SIGNAL_RUN_LAST,
563 G_STRUCT_OFFSET (GtkSpinButtonClass, wrapped),
564 NULL, NULL,
565 NULL,
566 G_TYPE_NONE, 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 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
588 G_STRUCT_OFFSET (GtkSpinButtonClass, change_value),
589 NULL, NULL,
590 NULL,
591 G_TYPE_NONE, 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, GTK_ACCESSIBLE_ROLE_SPIN_BUTTON);
608 }
609
610 static GtkEditable *
gtk_spin_button_get_delegate(GtkEditable * editable)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
gtk_spin_button_editable_init(GtkEditableInterface * iface)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
gtk_spin_button_accessible_get_platform_state(GtkAccessible * self,GtkAccessiblePlatformState state)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 (spin_button->entry);
635 case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED:
636 return gtk_widget_has_focus (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
gtk_spin_button_accessible_init(GtkAccessibleInterface * iface)645 gtk_spin_button_accessible_init (GtkAccessibleInterface *iface)
646 {
647 GtkAccessibleInterface *parent_iface = g_type_interface_peek_parent (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
gtk_cell_editable_spin_button_activated(GtkText * text,GtkSpinButton * spin)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 (spin);
659 }
660
661 static gboolean
gtk_cell_editable_spin_button_key_pressed(GtkEventControllerKey * key,guint keyval,guint keycode,GdkModifierType modifiers,GtkSpinButton * spin)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 (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 (spin);
687
688 return GDK_EVENT_STOP;
689 }
690
691 return GDK_EVENT_PROPAGATE;
692 }
693
694 static void
gtk_spin_button_start_editing(GtkCellEditable * cell_editable,GdkEvent * event)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
gtk_spin_button_cell_editable_init(GtkCellEditableIface * iface)707 gtk_spin_button_cell_editable_init (GtkCellEditableIface *iface)
708 {
709 iface->start_editing = gtk_spin_button_start_editing;
710 }
711
712 static void
gtk_spin_button_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)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 spin_button->adjustment,
741 g_value_get_double (value),
742 spin_button->digits);
743 break;
744 case PROP_DIGITS:
745 gtk_spin_button_configure (spin_button,
746 spin_button->adjustment,
747 spin_button->climb_rate,
748 g_value_get_uint (value));
749 break;
750 case PROP_SNAP_TO_TICKS:
751 gtk_spin_button_set_snap_to_ticks (spin_button, g_value_get_boolean (value));
752 break;
753 case PROP_NUMERIC:
754 gtk_spin_button_set_numeric (spin_button, g_value_get_boolean (value));
755 break;
756 case PROP_WRAP:
757 gtk_spin_button_set_wrap (spin_button, g_value_get_boolean (value));
758 break;
759 case PROP_UPDATE_POLICY:
760 gtk_spin_button_set_update_policy (spin_button, g_value_get_enum (value));
761 break;
762 case PROP_VALUE:
763 gtk_spin_button_set_value (spin_button, g_value_get_double (value));
764 break;
765 case PROP_ORIENTATION:
766 gtk_spin_button_set_orientation (spin_button, 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, "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
gtk_spin_button_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)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, 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, spin_button->adjustment);
801 break;
802 case PROP_CLIMB_RATE:
803 g_value_set_double (value, spin_button->climb_rate);
804 break;
805 case PROP_DIGITS:
806 g_value_set_uint (value, spin_button->digits);
807 break;
808 case PROP_SNAP_TO_TICKS:
809 g_value_set_boolean (value, spin_button->snap_to_ticks);
810 break;
811 case PROP_NUMERIC:
812 g_value_set_boolean (value, spin_button->numeric);
813 break;
814 case PROP_WRAP:
815 g_value_set_boolean (value, spin_button->wrap);
816 break;
817 case PROP_UPDATE_POLICY:
818 g_value_set_enum (value, spin_button->update_policy);
819 break;
820 case PROP_VALUE:
821 g_value_set_double (value, gtk_adjustment_get_value (spin_button->adjustment));
822 break;
823 case PROP_ORIENTATION:
824 g_value_set_enum (value, 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, 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
swipe_gesture_begin(GtkGesture * gesture,GdkEventSequence * sequence,GtkSpinButton * spin_button)836 swipe_gesture_begin (GtkGesture *gesture,
837 GdkEventSequence *sequence,
838 GtkSpinButton *spin_button)
839 {
840 gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
841 gtk_widget_grab_focus (GTK_WIDGET (spin_button));
842 spin_button->swipe_remainder = 0;
843 }
844
845 static void
swipe_gesture_update(GtkGesture * gesture,GdkEventSequence * sequence,GtkSpinButton * spin_button)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, &vel_y);
853 step = (-vel_y / 20) + spin_button->swipe_remainder;
854 spin_button->swipe_remainder = fmod (step, gtk_adjustment_get_step_increment (spin_button->adjustment));
855 gtk_spin_button_real_spin (spin_button, step - spin_button->swipe_remainder);
856 }
857
858 static gboolean
scroll_controller_scroll(GtkEventControllerScroll * Scroll,double dx,double dy,GtkWidget * widget)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, -dy * gtk_adjustment_get_step_increment (spin->adjustment));
869
870 return GDK_EVENT_STOP;
871 }
872
873 static gboolean
gtk_spin_button_stop_spinning(GtkSpinButton * spin)874 gtk_spin_button_stop_spinning (GtkSpinButton *spin)
875 {
876 gboolean did_spin = FALSE;
877
878 if (spin->timer)
879 {
880 g_source_remove (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 (spin->adjustment);
888 spin->timer_calls = 0;
889
890 spin->click_child = NULL;
891
892 return did_spin;
893 }
894
895 static void
start_spinning(GtkSpinButton * spin,GtkWidget * click_child,double step)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 (GSourceFunc) gtk_spin_button_timer,
908 (gpointer) spin);
909 gdk_source_set_static_name_by_id (spin->timer, "[gtk] gtk_spin_button_timer");
910 }
911 gtk_spin_button_real_spin (spin, click_child == spin->up_button ? step : -step);
912 }
913
914 static void
button_pressed_cb(GtkGestureClick * gesture,int n_pressses,double x,double y,gpointer user_data)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_button, pressed_button, gtk_adjustment_get_step_increment (spin_button->adjustment));
933 else if (button == GDK_BUTTON_MIDDLE)
934 start_spinning (spin_button, pressed_button, gtk_adjustment_get_page_increment (spin_button->adjustment));
935
936 gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
937 }
938 else
939 {
940 gtk_widget_error_bell (GTK_WIDGET (spin_button));
941 }
942 }
943
944 static void
button_released_cb(GtkGestureClick * gesture,int n_pressses,double x,double y,gpointer user_data)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_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 (spin_button->adjustment) - gtk_adjustment_get_lower (spin_button->adjustment);
963 if (diff > EPSILON)
964 gtk_spin_button_real_spin (spin_button, -diff);
965 }
966 else if (button_widget == spin_button->up_button)
967 {
968 diff = gtk_adjustment_get_upper (spin_button->adjustment) - gtk_adjustment_get_value (spin_button->adjustment);
969 if (diff > EPSILON)
970 gtk_spin_button_real_spin (spin_button, diff);
971 }
972 }
973 }
974
975 static void
button_cancel_cb(GtkGesture * gesture,GdkEventSequence * sequence,GtkSpinButton * spin_button)976 button_cancel_cb (GtkGesture *gesture,
977 GdkEventSequence *sequence,
978 GtkSpinButton *spin_button)
979 {
980 gtk_spin_button_stop_spinning (spin_button);
981 }
982
983 static void
key_controller_key_released(GtkEventControllerKey * key,guint keyval,guint keycode,GdkModifierType modifiers,GtkSpinButton * spin_button)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 (spin_button->adjustment);
991 spin_button->timer_calls = 0;
992 }
993
994 static void
key_controller_focus_out(GtkEventController * controller,GtkSpinButton * spin_button)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
gtk_spin_button_init(GtkSpinButton * spin_button)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), 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), 0);
1027 gtk_editable_set_max_width_chars (GTK_EDITABLE (spin_button->entry), 0);
1028 gtk_widget_set_hexpand (spin_button->entry, TRUE);
1029 gtk_widget_set_vexpand (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 (spin_button->entry, GTK_WIDGET (spin_button));
1032
1033 spin_button->down_button = g_object_new (GTK_TYPE_BUTTON,
1034 "accessible-role", GTK_ACCESSIBLE_ROLE_NONE,
1035 "icon-name", "value-decrease-symbolic",
1036 NULL);
1037 gtk_widget_add_css_class (spin_button->down_button, "down");
1038 gtk_widget_set_can_focus (spin_button->down_button, FALSE);
1039 gtk_widget_set_parent (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), 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 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 (gtk_button_get_gesture (GTK_BUTTON (spin_button->down_button)), gesture);
1051
1052 spin_button->up_button = g_object_new (GTK_TYPE_BUTTON,
1053 "accessible-role", GTK_ACCESSIBLE_ROLE_NONE,
1054 "icon-name", "value-increase-symbolic",
1055 NULL);
1056 gtk_widget_add_css_class (spin_button->up_button, "up");
1057 gtk_widget_set_can_focus (spin_button->up_button, FALSE);
1058 gtk_widget_set_parent (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), 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 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 (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 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 (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
gtk_spin_button_finalize(GObject * object)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 (spin_button->entry);
1111 gtk_widget_unparent (spin_button->up_button);
1112 gtk_widget_unparent (spin_button->down_button);
1113
1114 G_OBJECT_CLASS (gtk_spin_button_parent_class)->finalize (object);
1115 }
1116
1117 static void
gtk_spin_button_dispose(GObject * object)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
gtk_spin_button_realize(GtkWidget * widget)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 (spin_button, spinbutton_signals[OUTPUT], 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
update_buttons_sensitivity(GtkSpinButton * spin_button)1150 update_buttons_sensitivity (GtkSpinButton *spin_button)
1151 {
1152 const double lower = gtk_adjustment_get_lower (spin_button->adjustment);
1153 const double upper = gtk_adjustment_get_upper (spin_button->adjustment);
1154 const double value = gtk_adjustment_get_value (spin_button->adjustment);
1155
1156 gtk_widget_set_sensitive (spin_button->up_button,
1157 spin_button->wrap || upper - value > EPSILON);
1158 gtk_widget_set_sensitive (spin_button->down_button,
1159 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
adjustment_changed_cb(GtkAdjustment * adjustment,gpointer data)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 (GTK_ACCESSIBLE (spin_button),
1175 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
gtk_spin_button_unset_adjustment(GtkSpinButton * spin_button)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
gtk_spin_button_set_orientation(GtkSpinButton * spin,GtkOrientation orientation)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 (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, 0.5);
1217 else if (orientation == GTK_ORIENTATION_HORIZONTAL &&
1218 gtk_editable_get_alignment (editable) == 0.5)
1219 gtk_editable_set_alignment (editable, 0.0);
1220
1221 if (orientation == GTK_ORIENTATION_HORIZONTAL)
1222 {
1223 /* Current orientation of the box is vertical! */
1224 gtk_widget_insert_after (spin->up_button, GTK_WIDGET (spin), spin->down_button);
1225 }
1226 else
1227 {
1228 /* Current orientation of the box is horizontal! */
1229 gtk_widget_insert_before (spin->up_button, GTK_WIDGET (spin), spin->entry);
1230 }
1231
1232 g_object_notify (G_OBJECT (spin), "orientation");
1233 }
1234
1235 static char *
weed_out_neg_zero(char * str,int digits)1236 weed_out_neg_zero (char *str,
1237 int digits)
1238 {
1239 if (str[0] == '-')
1240 {
1241 char neg_zero[8];
1242 g_snprintf (neg_zero, 8, "%0.*f", digits, -0.0);
1243 if (strcmp (neg_zero, str) == 0)
1244 memmove (str, str + 1, strlen (str));
1245 }
1246 return str;
1247 }
1248
1249 static char *
gtk_spin_button_format_for_value(GtkSpinButton * spin_button,double value)1250 gtk_spin_button_format_for_value (GtkSpinButton *spin_button,
1251 double value)
1252 {
1253 char *buf = g_strdup_printf ("%0.*f", spin_button->digits, value);
1254
1255 return weed_out_neg_zero (buf, spin_button->digits);
1256 }
1257
1258 static void
gtk_spin_button_update_width_chars(GtkSpinButton * spin_button)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 (spin_button->adjustment);
1270 str = gtk_spin_button_format_for_value (spin_button, value);
1271 c = g_utf8_strlen (str, -1);
1272 g_free (str);
1273
1274 width_chars = MAX (width_chars, c);
1275
1276 value = gtk_adjustment_get_upper (spin_button->adjustment);
1277 str = gtk_spin_button_format_for_value (spin_button, value);
1278 c = g_utf8_strlen (str, -1);
1279 g_free (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), width_chars);
1289 }
1290
1291 static void
gtk_spin_button_state_flags_changed(GtkWidget * widget,GtkStateFlags previous_state)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
gtk_spin_button_timer(GtkSpinButton * spin_button)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, spin_button->timer_step);
1312 else
1313 gtk_spin_button_real_spin (spin_button, -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 (GSourceFunc) gtk_spin_button_timer,
1320 spin_button);
1321 gdk_source_set_static_name_by_id (spin_button->timer, "[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 (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
gtk_spin_button_value_changed(GtkAdjustment * adjustment,GtkSpinButton * spin_button)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 (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
1353 if (return_val == FALSE)
1354 gtk_spin_button_default_output (spin_button);
1355
1356 g_signal_emit (spin_button, spinbutton_signals[VALUE_CHANGED], 0);
1357
1358 gtk_accessible_update_property (GTK_ACCESSIBLE (spin_button),
1359 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), spinbutton_props[PROP_VALUE]);
1365 }
1366
1367 static void
gtk_spin_button_real_change_value(GtkSpinButton * spin,GtkScrollType scroll)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);
1384
1385 old_value = gtk_adjustment_get_value (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, -spin->timer_step);
1393
1394 if (spin->climb_rate > 0.0 &&
1395 spin->timer_step < gtk_adjustment_get_page_increment (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, spin->timer_step);
1411
1412 if (spin->climb_rate > 0.0 &&
1413 spin->timer_step < gtk_adjustment_get_page_increment (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, -gtk_adjustment_get_page_increment (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, gtk_adjustment_get_page_increment (spin->adjustment));
1435 break;
1436
1437 case GTK_SCROLL_START:
1438 {
1439 double diff = gtk_adjustment_get_value (spin->adjustment) - gtk_adjustment_get_lower (spin->adjustment);
1440 if (diff > EPSILON)
1441 gtk_spin_button_real_spin (spin, -diff);
1442 break;
1443 }
1444
1445 case GTK_SCROLL_END:
1446 {
1447 double diff = gtk_adjustment_get_upper (spin->adjustment) - gtk_adjustment_get_value (spin->adjustment);
1448 if (diff > EPSILON)
1449 gtk_spin_button_real_spin (spin, 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);
1461
1462 if (gtk_adjustment_get_value (spin->adjustment) == old_value)
1463 gtk_widget_error_bell (GTK_WIDGET (spin));
1464 }
1465
1466 static void
gtk_spin_button_snap(GtkSpinButton * spin_button,double val)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 (spin_button->adjustment);
1474 if (inc == 0)
1475 return;
1476
1477 tmp = (val - gtk_adjustment_get_lower (spin_button->adjustment)) / inc;
1478 if (tmp - floor (tmp) < ceil (tmp) - tmp)
1479 val = gtk_adjustment_get_lower (spin_button->adjustment) + floor (tmp) * inc;
1480 else
1481 val = gtk_adjustment_get_lower (spin_button->adjustment) + ceil (tmp) * inc;
1482
1483 gtk_spin_button_set_value (spin_button, val);
1484 }
1485
1486 static void
gtk_spin_button_activate(GtkText * entry,gpointer user_data)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
gtk_spin_button_insert_text(GtkEditable * editable,const char * new_text,int new_text_length,int * position)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 (entry_text, -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 new_text, new_text_length, position);
1595 }
1596
1597 static void
gtk_spin_button_real_spin(GtkSpinButton * spin_button,double increment)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 (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 (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 (new_value - gtk_adjustment_get_value (adjustment)) > EPSILON)
1641 gtk_adjustment_set_value (adjustment, new_value);
1642
1643 if (wrapped)
1644 g_signal_emit (spin_button, spinbutton_signals[WRAPPED], 0);
1645 }
1646
1647 static int
gtk_spin_button_default_input(GtkSpinButton * spin_button,double * new_val)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 (text, &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 (ch))
1673 break;
1674
1675 val = val * 10 + g_unichar_digit_value (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
gtk_spin_button_default_output(GtkSpinButton * spin_button)1688 gtk_spin_button_default_output (GtkSpinButton *spin_button)
1689 {
1690 char *buf = gtk_spin_button_format_for_value (spin_button,
1691 gtk_adjustment_get_value (spin_button->adjustment));
1692
1693 if (strcmp (buf, gtk_editable_get_text (GTK_EDITABLE (spin_button->entry))))
1694 gtk_editable_set_text (GTK_EDITABLE (spin_button->entry), buf);
1695
1696 g_free (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
gtk_spin_button_configure(GtkSpinButton * spin_button,GtkAdjustment * adjustment,double climb_rate,guint digits)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 (spin_button->adjustment);
1745
1746 g_object_notify_by_pspec (G_OBJECT (spin_button), 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), 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), 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 (GTK_ACCESSIBLE (spin_button),
1767 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 *
gtk_spin_button_new(GtkAdjustment * adjustment,double climb_rate,guint digits)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, 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 *
gtk_spin_button_new_with_range(double min,double max,double step)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 (min, min, max, step, 10 * step, 0);
1840
1841 if (fabs (step) >= 1.0 || step == 0.0)
1842 digits = 0;
1843 else {
1844 digits = abs ((int) floor (log10 (fabs (step))));
1845 if (digits > MAX_DIGITS)
1846 digits = MAX_DIGITS;
1847 }
1848
1849 gtk_spin_button_configure (spin, adjustment, step, digits);
1850
1851 gtk_spin_button_set_numeric (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
gtk_spin_button_set_adjustment(GtkSpinButton * spin_button,GtkAdjustment * adjustment)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 (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
1871
1872 gtk_spin_button_configure (spin_button,
1873 adjustment,
1874 spin_button->climb_rate,
1875 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 *
gtk_spin_button_get_adjustment(GtkSpinButton * spin_button)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
gtk_spin_button_set_digits(GtkSpinButton * spin_button,guint digits)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 (spin_button->adjustment, spin_button);
1914 g_object_notify_by_pspec (G_OBJECT (spin_button), 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
gtk_spin_button_get_digits(GtkSpinButton * spin_button)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
gtk_spin_button_set_increments(GtkSpinButton * spin_button,double step,double page)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 (spin_button->adjustment,
1956 gtk_adjustment_get_value (spin_button->adjustment),
1957 gtk_adjustment_get_lower (spin_button->adjustment),
1958 gtk_adjustment_get_upper (spin_button->adjustment),
1959 step,
1960 page,
1961 gtk_adjustment_get_page_size (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
gtk_spin_button_get_increments(GtkSpinButton * spin_button,double * step,double * page)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 (spin_button->adjustment);
1984 if (page)
1985 *page = gtk_adjustment_get_page_increment (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
gtk_spin_button_set_range(GtkSpinButton * spin_button,double min,double max)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 min,
2013 max,
2014 gtk_adjustment_get_step_increment (adjustment),
2015 gtk_adjustment_get_page_increment (adjustment),
2016 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
gtk_spin_button_get_range(GtkSpinButton * spin_button,double * min,double * max)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 (spin_button->adjustment);
2038 if (max)
2039 *max = gtk_adjustment_get_upper (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
gtk_spin_button_get_value(GtkSpinButton * spin_button)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 (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
gtk_spin_button_get_value_as_int(GtkSpinButton * spin_button)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 (spin_button->adjustment);
2074 if (val - floor (val) < ceil (val) - val)
2075 return floor (val);
2076 else
2077 return ceil (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
gtk_spin_button_set_value(GtkSpinButton * spin_button,double value)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 (value - gtk_adjustment_get_value (spin_button->adjustment)) > EPSILON)
2094 gtk_adjustment_set_value (spin_button->adjustment, value);
2095 else
2096 {
2097 int return_val = FALSE;
2098 g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 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
gtk_spin_button_set_update_policy(GtkSpinButton * spin_button,GtkSpinButtonUpdatePolicy policy)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), 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
gtk_spin_button_get_update_policy(GtkSpinButton * spin_button)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
gtk_spin_button_set_numeric(GtkSpinButton * spin_button,gboolean numeric)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), 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
gtk_spin_button_get_numeric(GtkSpinButton * spin_button)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
gtk_spin_button_set_wrap(GtkSpinButton * spin_button,gboolean wrap)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), 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
gtk_spin_button_get_wrap(GtkSpinButton * spin_button)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
gtk_spin_button_set_snap_to_ticks(GtkSpinButton * spin_button,gboolean snap_to_ticks)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), 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
gtk_spin_button_get_snap_to_ticks(GtkSpinButton * spin_button)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
gtk_spin_button_set_climb_rate(GtkSpinButton * spin_button,double climb_rate)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), 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
gtk_spin_button_get_climb_rate(GtkSpinButton * spin_button)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
gtk_spin_button_spin(GtkSpinButton * spin_button,GtkSpinType direction,double increment)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, gtk_adjustment_get_step_increment (adjustment));
2348 break;
2349
2350 case GTK_SPIN_STEP_BACKWARD:
2351
2352 gtk_spin_button_real_spin (spin_button, -gtk_adjustment_get_step_increment (adjustment));
2353 break;
2354
2355 case GTK_SPIN_PAGE_FORWARD:
2356
2357 gtk_spin_button_real_spin (spin_button, gtk_adjustment_get_page_increment (adjustment));
2358 break;
2359
2360 case GTK_SPIN_PAGE_BACKWARD:
2361
2362 gtk_spin_button_real_spin (spin_button, -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, -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, 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
gtk_spin_button_update(GtkSpinButton * spin_button)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 (spin_button, spinbutton_signals[INPUT], 0, &val, &return_val);
2407 if (return_val == FALSE)
2408 {
2409 return_val = gtk_spin_button_default_input (spin_button, &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 (spin_button->adjustment))
2418 val = gtk_adjustment_get_lower (spin_button->adjustment);
2419 else if (val > gtk_adjustment_get_upper (spin_button->adjustment))
2420 val = gtk_adjustment_get_upper (spin_button->adjustment);
2421 }
2422 else if ((spin_button->update_policy == GTK_UPDATE_IF_VALID) &&
2423 (error ||
2424 val < gtk_adjustment_get_lower (spin_button->adjustment) ||
2425 val > gtk_adjustment_get_upper (spin_button->adjustment)))
2426 {
2427 gtk_spin_button_value_changed (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, val);
2435 }
2436
2437 GtkText *
gtk_spin_button_get_text_widget(GtkSpinButton * spin_button)2438 gtk_spin_button_get_text_widget (GtkSpinButton *spin_button)
2439 {
2440 return GTK_TEXT (spin_button->entry);
2441 }
2442
2443