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