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 <stdio.h>
33 #include <stdlib.h>
34 #include <math.h>
35 #include <string.h>
36 #include <locale.h>
37 
38 #include "gtkadjustment.h"
39 #include "gtkbindings.h"
40 #include "gtkboxgadgetprivate.h"
41 #include "gtkcssgadgetprivate.h"
42 #include "gtkcsscustomgadgetprivate.h"
43 #include "gtkentryprivate.h"
44 #include "gtkiconhelperprivate.h"
45 #include "gtkicontheme.h"
46 #include "gtkintl.h"
47 #include "gtkmarshalers.h"
48 #include "gtkorientable.h"
49 #include "gtkorientableprivate.h"
50 #include "gtkprivate.h"
51 #include "gtksettings.h"
52 #include "gtktypebuiltins.h"
53 #include "gtkwidgetpath.h"
54 #include "gtkwidgetprivate.h"
55 #include "gtkstylecontextprivate.h"
56 #include "gtkcssstylepropertyprivate.h"
57 
58 #include "a11y/gtkspinbuttonaccessible.h"
59 
60 #define MIN_SPIN_BUTTON_WIDTH 30
61 #define MAX_TIMER_CALLS       5
62 #define EPSILON               1e-10
63 #define MAX_DIGITS            20
64 #define TIMEOUT_INITIAL       500
65 #define TIMEOUT_REPEAT        50
66 
67 /**
68  * SECTION:gtkspinbutton
69  * @Title: GtkSpinButton
70  * @Short_description: Retrieve an integer or floating-point number from
71  *     the user
72  * @See_also: #GtkEntry
73  *
74  * A #GtkSpinButton is an ideal way to allow the user to set the value of
75  * some attribute. Rather than having to directly type a number into a
76  * #GtkEntry, GtkSpinButton allows the user to click on one of two arrows
77  * to increment or decrement the displayed value. A value can still be
78  * typed in, with the bonus that it can be checked to ensure it is in a
79  * given range.
80  *
81  * The main properties of a GtkSpinButton are through an adjustment.
82  * See the #GtkAdjustment section for more details about an adjustment's
83  * properties. Note that GtkSpinButton will by default make its entry
84  * large enough to accomodate the lower and upper bounds of the adjustment,
85  * which can lead to surprising results. Best practice is to set both
86  * the #GtkEntry:width-chars and #GtkEntry:max-width-chars poperties
87  * to the desired number of characters to display in the entry.
88  *
89  * # CSS nodes
90  *
91  * |[<!-- language="plain" -->
92  * spinbutton.horizontal
93  * ├── undershoot.left
94  * ├── undershoot.right
95  * ├── entry
96  * │   ╰── ...
97  * ├── button.down
98  * ╰── button.up
99  * ]|
100  *
101  * |[<!-- language="plain" -->
102  * spinbutton.vertical
103  * ├── undershoot.left
104  * ├── undershoot.right
105  * ├── button.up
106  * ├── entry
107  * │   ╰── ...
108  * ╰── button.down
109  * ]|
110  *
111  * GtkSpinButtons main CSS node has the name spinbutton. It creates subnodes
112  * for the entry and the two buttons, with these names. The button nodes have
113  * the style classes .up and .down. The GtkEntry subnodes (if present) are put
114  * below the entry node. The orientation of the spin button is reflected in
115  * the .vertical or .horizontal style class on the main node.
116  *
117  * ## Using a GtkSpinButton to get an integer
118  *
119  * |[<!-- language="C" -->
120  * // Provides a function to retrieve an integer value from a GtkSpinButton
121  * // and creates a spin button to model percentage values.
122  *
123  * gint
124  * grab_int_value (GtkSpinButton *button,
125  *                 gpointer       user_data)
126  * {
127  *   return gtk_spin_button_get_value_as_int (button);
128  * }
129  *
130  * void
131  * create_integer_spin_button (void)
132  * {
133  *
134  *   GtkWidget *window, *button;
135  *   GtkAdjustment *adjustment;
136  *
137  *   adjustment = gtk_adjustment_new (50.0, 0.0, 100.0, 1.0, 5.0, 0.0);
138  *
139  *   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
140  *   gtk_container_set_border_width (GTK_CONTAINER (window), 5);
141  *
142  *   // creates the spinbutton, with no decimal places
143  *   button = gtk_spin_button_new (adjustment, 1.0, 0);
144  *   gtk_container_add (GTK_CONTAINER (window), button);
145  *
146  *   gtk_widget_show_all (window);
147  * }
148  * ]|
149  *
150  * ## Using a GtkSpinButton to get a floating point value
151  *
152  * |[<!-- language="C" -->
153  * // Provides a function to retrieve a floating point value from a
154  * // GtkSpinButton, and creates a high precision spin button.
155  *
156  * gfloat
157  * grab_float_value (GtkSpinButton *button,
158  *                   gpointer       user_data)
159  * {
160  *   return gtk_spin_button_get_value (button);
161  * }
162  *
163  * void
164  * create_floating_spin_button (void)
165  * {
166  *   GtkWidget *window, *button;
167  *   GtkAdjustment *adjustment;
168  *
169  *   adjustment = gtk_adjustment_new (2.500, 0.0, 5.0, 0.001, 0.1, 0.0);
170  *
171  *   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
172  *   gtk_container_set_border_width (GTK_CONTAINER (window), 5);
173  *
174  *   // creates the spinbutton, with three decimal places
175  *   button = gtk_spin_button_new (adjustment, 0.001, 3);
176  *   gtk_container_add (GTK_CONTAINER (window), button);
177  *
178  *   gtk_widget_show_all (window);
179  * }
180  * ]|
181  */
182 
183 enum {
184   UP_PANEL,
185   DOWN_PANEL
186 };
187 
188 struct _GtkSpinButtonPrivate
189 {
190   GtkAdjustment *adjustment;
191 
192   GdkWindow     *down_panel;
193   GdkWindow     *up_panel;
194 
195   GtkCssGadget  *gadget;
196   GtkCssGadget  *down_button;
197   GtkCssGadget  *up_button;
198 
199   GdkWindow     *click_child;
200   GdkWindow     *in_child;
201 
202   guint32        timer;
203 
204   GtkSpinButtonUpdatePolicy update_policy;
205 
206   gdouble        climb_rate;
207   gdouble        timer_step;
208 
209   GtkOrientation orientation;
210 
211   GtkGesture *swipe_gesture;
212 
213   guint          button        : 2;
214   guint          digits        : 10;
215   guint          need_timer    : 1;
216   guint          numeric       : 1;
217   guint          snap_to_ticks : 1;
218   guint          timer_calls   : 3;
219   guint          wrap          : 1;
220 };
221 
222 enum {
223   PROP_0,
224   PROP_ADJUSTMENT,
225   PROP_CLIMB_RATE,
226   PROP_DIGITS,
227   PROP_SNAP_TO_TICKS,
228   PROP_NUMERIC,
229   PROP_WRAP,
230   PROP_UPDATE_POLICY,
231   PROP_VALUE,
232   PROP_ORIENTATION
233 };
234 
235 /* Signals */
236 enum
237 {
238   INPUT,
239   OUTPUT,
240   VALUE_CHANGED,
241   CHANGE_VALUE,
242   WRAPPED,
243   LAST_SIGNAL
244 };
245 
246 static void gtk_spin_button_editable_init  (GtkEditableInterface *iface);
247 static void gtk_spin_button_finalize       (GObject            *object);
248 static void gtk_spin_button_set_property   (GObject         *object,
249                                             guint            prop_id,
250                                             const GValue    *value,
251                                             GParamSpec      *pspec);
252 static void gtk_spin_button_get_property   (GObject         *object,
253                                             guint            prop_id,
254                                             GValue          *value,
255                                             GParamSpec      *pspec);
256 static void gtk_spin_button_destroy        (GtkWidget          *widget);
257 static void gtk_spin_button_map            (GtkWidget          *widget);
258 static void gtk_spin_button_unmap          (GtkWidget          *widget);
259 static void gtk_spin_button_realize        (GtkWidget          *widget);
260 static void gtk_spin_button_unrealize      (GtkWidget          *widget);
261 static void gtk_spin_button_get_preferred_width  (GtkWidget          *widget,
262                                                   gint               *minimum,
263                                                   gint               *natural);
264 static void gtk_spin_button_get_preferred_height (GtkWidget          *widget,
265                                                   gint               *minimum,
266                                                   gint               *natural);
267 static void gtk_spin_button_get_preferred_height_and_baseline_for_width (GtkWidget *widget,
268 									 gint       width,
269 									 gint      *minimum,
270 									 gint      *natural,
271 									 gint      *minimum_baseline,
272 									 gint      *natural_baseline);
273 static void gtk_spin_button_size_allocate  (GtkWidget          *widget,
274                                             GtkAllocation      *allocation);
275 static gint gtk_spin_button_draw           (GtkWidget          *widget,
276                                             cairo_t            *cr);
277 static gint gtk_spin_button_button_press   (GtkWidget          *widget,
278                                             GdkEventButton     *event);
279 static gint gtk_spin_button_button_release (GtkWidget          *widget,
280                                             GdkEventButton     *event);
281 static gint gtk_spin_button_motion_notify  (GtkWidget          *widget,
282                                             GdkEventMotion     *event);
283 static gint gtk_spin_button_enter_notify   (GtkWidget          *widget,
284                                             GdkEventCrossing   *event);
285 static gint gtk_spin_button_leave_notify   (GtkWidget          *widget,
286                                             GdkEventCrossing   *event);
287 static gint gtk_spin_button_focus_out      (GtkWidget          *widget,
288                                             GdkEventFocus      *event);
289 static void gtk_spin_button_grab_notify    (GtkWidget          *widget,
290                                             gboolean            was_grabbed);
291 static void gtk_spin_button_state_flags_changed  (GtkWidget     *widget,
292                                                   GtkStateFlags  previous_state);
293 static gboolean gtk_spin_button_timer          (GtkSpinButton      *spin_button);
294 static gboolean gtk_spin_button_stop_spinning  (GtkSpinButton      *spin);
295 static void gtk_spin_button_value_changed  (GtkAdjustment      *adjustment,
296                                             GtkSpinButton      *spin_button);
297 static gint gtk_spin_button_key_release    (GtkWidget          *widget,
298                                             GdkEventKey        *event);
299 static gint gtk_spin_button_scroll         (GtkWidget          *widget,
300                                             GdkEventScroll     *event);
301 static void gtk_spin_button_direction_changed (GtkWidget        *widget,
302                                                GtkTextDirection  previous_dir);
303 static void gtk_spin_button_activate       (GtkEntry           *entry);
304 static void gtk_spin_button_unset_adjustment (GtkSpinButton *spin_button);
305 static void gtk_spin_button_set_orientation (GtkSpinButton     *spin_button,
306                                              GtkOrientation     orientation);
307 static void gtk_spin_button_snap           (GtkSpinButton      *spin_button,
308                                             gdouble             val);
309 static void gtk_spin_button_insert_text    (GtkEditable        *editable,
310                                             const gchar        *new_text,
311                                             gint                new_text_length,
312                                             gint               *position);
313 static void gtk_spin_button_real_spin      (GtkSpinButton      *spin_button,
314                                             gdouble             step);
315 static void gtk_spin_button_real_change_value (GtkSpinButton   *spin,
316                                                GtkScrollType    scroll);
317 
318 static gint gtk_spin_button_default_input  (GtkSpinButton      *spin_button,
319                                             gdouble            *new_val);
320 static void gtk_spin_button_default_output (GtkSpinButton      *spin_button);
321 static void update_node_state              (GtkSpinButton *spin_button);
322 
323 static guint spinbutton_signals[LAST_SIGNAL] = {0};
324 
G_DEFINE_TYPE_WITH_CODE(GtkSpinButton,gtk_spin_button,GTK_TYPE_ENTRY,G_ADD_PRIVATE (GtkSpinButton)G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,NULL)G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,gtk_spin_button_editable_init))325 G_DEFINE_TYPE_WITH_CODE (GtkSpinButton, gtk_spin_button, GTK_TYPE_ENTRY,
326                          G_ADD_PRIVATE (GtkSpinButton)
327                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
328                          G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
329                                                 gtk_spin_button_editable_init))
330 
331 #define add_spin_binding(binding_set, keyval, mask, scroll)            \
332   gtk_binding_entry_add_signal (binding_set, keyval, mask,             \
333                                 "change-value", 1,                     \
334                                 GTK_TYPE_SCROLL_TYPE, scroll)
335 
336 static void
337 gtk_spin_button_class_init (GtkSpinButtonClass *class)
338 {
339   GObjectClass     *gobject_class = G_OBJECT_CLASS (class);
340   GtkWidgetClass   *widget_class = GTK_WIDGET_CLASS (class);
341   GtkEntryClass    *entry_class = GTK_ENTRY_CLASS (class);
342   GtkBindingSet    *binding_set;
343 
344   gobject_class->finalize = gtk_spin_button_finalize;
345   gobject_class->set_property = gtk_spin_button_set_property;
346   gobject_class->get_property = gtk_spin_button_get_property;
347 
348   widget_class->destroy = gtk_spin_button_destroy;
349   widget_class->map = gtk_spin_button_map;
350   widget_class->unmap = gtk_spin_button_unmap;
351   widget_class->realize = gtk_spin_button_realize;
352   widget_class->unrealize = gtk_spin_button_unrealize;
353   widget_class->get_preferred_width = gtk_spin_button_get_preferred_width;
354   widget_class->get_preferred_height = gtk_spin_button_get_preferred_height;
355   widget_class->get_preferred_height_and_baseline_for_width = gtk_spin_button_get_preferred_height_and_baseline_for_width;
356   widget_class->size_allocate = gtk_spin_button_size_allocate;
357   widget_class->draw = gtk_spin_button_draw;
358   widget_class->scroll_event = gtk_spin_button_scroll;
359   widget_class->button_press_event = gtk_spin_button_button_press;
360   widget_class->button_release_event = gtk_spin_button_button_release;
361   widget_class->motion_notify_event = gtk_spin_button_motion_notify;
362   widget_class->key_release_event = gtk_spin_button_key_release;
363   widget_class->enter_notify_event = gtk_spin_button_enter_notify;
364   widget_class->leave_notify_event = gtk_spin_button_leave_notify;
365   widget_class->focus_out_event = gtk_spin_button_focus_out;
366   widget_class->grab_notify = gtk_spin_button_grab_notify;
367   widget_class->state_flags_changed = gtk_spin_button_state_flags_changed;
368   widget_class->direction_changed = gtk_spin_button_direction_changed;
369 
370   entry_class->activate = gtk_spin_button_activate;
371 
372   class->input = NULL;
373   class->output = NULL;
374   class->change_value = gtk_spin_button_real_change_value;
375 
376   g_object_class_install_property (gobject_class,
377                                    PROP_ADJUSTMENT,
378                                    g_param_spec_object ("adjustment",
379                                                         P_("Adjustment"),
380                                                         P_("The adjustment that holds the value of the spin button"),
381                                                         GTK_TYPE_ADJUSTMENT,
382                                                         GTK_PARAM_READWRITE));
383 
384   g_object_class_install_property (gobject_class,
385                                    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   g_object_class_install_property (gobject_class,
393                                    PROP_DIGITS,
394                                    g_param_spec_uint ("digits",
395                                                       P_("Digits"),
396                                                       P_("The number of decimal places to display"),
397                                                       0, MAX_DIGITS, 0,
398                                                       GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
399 
400   g_object_class_install_property (gobject_class,
401                                    PROP_SNAP_TO_TICKS,
402                                    g_param_spec_boolean ("snap-to-ticks",
403                                                          P_("Snap to Ticks"),
404                                                          P_("Whether erroneous values are automatically changed to a spin button's nearest step increment"),
405                                                          FALSE,
406                                                          GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
407 
408   g_object_class_install_property (gobject_class,
409                                    PROP_NUMERIC,
410                                    g_param_spec_boolean ("numeric",
411                                                          P_("Numeric"),
412                                                          P_("Whether non-numeric characters should be ignored"),
413                                                          FALSE,
414                                                          GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
415 
416   g_object_class_install_property (gobject_class,
417                                    PROP_WRAP,
418                                    g_param_spec_boolean ("wrap",
419                                                          P_("Wrap"),
420                                                          P_("Whether a spin button should wrap upon reaching its limits"),
421                                                          FALSE,
422                                                          GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
423 
424   g_object_class_install_property (gobject_class,
425                                    PROP_UPDATE_POLICY,
426                                    g_param_spec_enum ("update-policy",
427                                                       P_("Update Policy"),
428                                                       P_("Whether the spin button should update always, or only when the value is legal"),
429                                                       GTK_TYPE_SPIN_BUTTON_UPDATE_POLICY,
430                                                       GTK_UPDATE_ALWAYS,
431                                                       GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
432 
433   g_object_class_install_property (gobject_class,
434                                    PROP_VALUE,
435                                    g_param_spec_double ("value",
436                                                         P_("Value"),
437                                                         P_("Reads the current value, or sets a new value"),
438                                                         -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
439                                                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
440 
441   g_object_class_override_property (gobject_class,
442                                     PROP_ORIENTATION,
443                                     "orientation");
444 
445   /**
446    * GtkSpinButton:shadow-type:
447    *
448    * Style of bevel around the spin button.
449    *
450    * Deprecated: 3.20: Use CSS to determine the style of the border;
451    *     the value of this style property is ignored.
452    */
453   gtk_widget_class_install_style_property (widget_class,
454                                            g_param_spec_enum ("shadow-type",
455                                                               P_("Shadow Type"),
456                                                               P_("Style of bevel around the spin button"),
457                                                               GTK_TYPE_SHADOW_TYPE,
458                                                               GTK_SHADOW_IN,
459                                                               GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
460 
461   /**
462    * GtkSpinButton::input:
463    * @spin_button: the object on which the signal was emitted
464    * @new_value: (out) (type double): return location for the new value
465    *
466    * The ::input signal can be used to influence the conversion of
467    * the users input into a double value. The signal handler is
468    * expected to use gtk_entry_get_text() to retrieve the text of
469    * the entry and set @new_value to the new value.
470    *
471    * The default conversion uses g_strtod().
472    *
473    * Returns: %TRUE for a successful conversion, %FALSE if the input
474    *     was not handled, and %GTK_INPUT_ERROR if the conversion failed.
475    */
476   spinbutton_signals[INPUT] =
477     g_signal_new (I_("input"),
478                   G_TYPE_FROM_CLASS (gobject_class),
479                   G_SIGNAL_RUN_LAST,
480                   G_STRUCT_OFFSET (GtkSpinButtonClass, input),
481                   NULL, NULL,
482                   _gtk_marshal_INT__POINTER,
483                   G_TYPE_INT, 1,
484                   G_TYPE_POINTER);
485 
486   /**
487    * GtkSpinButton::output:
488    * @spin_button: the object on which the signal was emitted
489    *
490    * The ::output signal can be used to change to formatting
491    * of the value that is displayed in the spin buttons entry.
492    * |[<!-- language="C" -->
493    * // show leading zeros
494    * static gboolean
495    * on_output (GtkSpinButton *spin,
496    *            gpointer       data)
497    * {
498    *    GtkAdjustment *adjustment;
499    *    gchar *text;
500    *    int value;
501    *
502    *    adjustment = gtk_spin_button_get_adjustment (spin);
503    *    value = (int)gtk_adjustment_get_value (adjustment);
504    *    text = g_strdup_printf ("%02d", value);
505    *    gtk_entry_set_text (GTK_ENTRY (spin), text);
506    *    g_free (text);
507    *
508    *    return TRUE;
509    * }
510    * ]|
511    *
512    * Returns: %TRUE if the value has been displayed
513    */
514   spinbutton_signals[OUTPUT] =
515     g_signal_new (I_("output"),
516                   G_TYPE_FROM_CLASS (gobject_class),
517                   G_SIGNAL_RUN_LAST,
518                   G_STRUCT_OFFSET (GtkSpinButtonClass, output),
519                   _gtk_boolean_handled_accumulator, NULL,
520                   _gtk_marshal_BOOLEAN__VOID,
521                   G_TYPE_BOOLEAN, 0);
522 
523   /**
524    * GtkSpinButton::value-changed:
525    * @spin_button: the object on which the signal was emitted
526    *
527    * The ::value-changed signal is emitted when the value represented by
528    * @spinbutton changes. Also see the #GtkSpinButton::output signal.
529    */
530   spinbutton_signals[VALUE_CHANGED] =
531     g_signal_new (I_("value-changed"),
532                   G_TYPE_FROM_CLASS (gobject_class),
533                   G_SIGNAL_RUN_LAST,
534                   G_STRUCT_OFFSET (GtkSpinButtonClass, value_changed),
535                   NULL, NULL,
536                   NULL,
537                   G_TYPE_NONE, 0);
538 
539   /**
540    * GtkSpinButton::wrapped:
541    * @spin_button: the object on which the signal was emitted
542    *
543    * The ::wrapped signal is emitted right after the spinbutton wraps
544    * from its maximum to minimum value or vice-versa.
545    *
546    * Since: 2.10
547    */
548   spinbutton_signals[WRAPPED] =
549     g_signal_new (I_("wrapped"),
550                   G_TYPE_FROM_CLASS (gobject_class),
551                   G_SIGNAL_RUN_LAST,
552                   G_STRUCT_OFFSET (GtkSpinButtonClass, wrapped),
553                   NULL, NULL,
554                   NULL,
555                   G_TYPE_NONE, 0);
556 
557   /* Action signals */
558   /**
559    * GtkSpinButton::change-value:
560    * @spin_button: the object on which the signal was emitted
561    * @scroll: a #GtkScrollType to specify the speed and amount of change
562    *
563    * The ::change-value signal is a [keybinding signal][GtkBindingSignal]
564    * which gets emitted when the user initiates a value change.
565    *
566    * Applications should not connect to it, but may emit it with
567    * g_signal_emit_by_name() if they need to control the cursor
568    * programmatically.
569    *
570    * The default bindings for this signal are Up/Down and PageUp and/PageDown.
571    */
572   spinbutton_signals[CHANGE_VALUE] =
573     g_signal_new (I_("change-value"),
574                   G_TYPE_FROM_CLASS (gobject_class),
575                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
576                   G_STRUCT_OFFSET (GtkSpinButtonClass, change_value),
577                   NULL, NULL,
578                   NULL,
579                   G_TYPE_NONE, 1,
580                   GTK_TYPE_SCROLL_TYPE);
581 
582   binding_set = gtk_binding_set_by_class (class);
583 
584   add_spin_binding (binding_set, GDK_KEY_Up, 0, GTK_SCROLL_STEP_UP);
585   add_spin_binding (binding_set, GDK_KEY_KP_Up, 0, GTK_SCROLL_STEP_UP);
586   add_spin_binding (binding_set, GDK_KEY_Down, 0, GTK_SCROLL_STEP_DOWN);
587   add_spin_binding (binding_set, GDK_KEY_KP_Down, 0, GTK_SCROLL_STEP_DOWN);
588   add_spin_binding (binding_set, GDK_KEY_Page_Up, 0, GTK_SCROLL_PAGE_UP);
589   add_spin_binding (binding_set, GDK_KEY_Page_Down, 0, GTK_SCROLL_PAGE_DOWN);
590   add_spin_binding (binding_set, GDK_KEY_End, GDK_CONTROL_MASK, GTK_SCROLL_END);
591   add_spin_binding (binding_set, GDK_KEY_Home, GDK_CONTROL_MASK, GTK_SCROLL_START);
592   add_spin_binding (binding_set, GDK_KEY_Page_Up, GDK_CONTROL_MASK, GTK_SCROLL_END);
593   add_spin_binding (binding_set, GDK_KEY_Page_Down, GDK_CONTROL_MASK, GTK_SCROLL_START);
594 
595   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SPIN_BUTTON_ACCESSIBLE);
596   gtk_widget_class_set_css_name (widget_class, "spinbutton");
597 }
598 
599 static void
gtk_spin_button_editable_init(GtkEditableInterface * iface)600 gtk_spin_button_editable_init (GtkEditableInterface *iface)
601 {
602   iface->insert_text = gtk_spin_button_insert_text;
603 }
604 
605 static void
gtk_spin_button_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)606 gtk_spin_button_set_property (GObject      *object,
607                               guint         prop_id,
608                               const GValue *value,
609                               GParamSpec   *pspec)
610 {
611   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
612   GtkSpinButtonPrivate *priv = spin_button->priv;
613 
614   switch (prop_id)
615     {
616       GtkAdjustment *adjustment;
617 
618     case PROP_ADJUSTMENT:
619       adjustment = GTK_ADJUSTMENT (g_value_get_object (value));
620       gtk_spin_button_set_adjustment (spin_button, adjustment);
621       break;
622     case PROP_CLIMB_RATE:
623       gtk_spin_button_configure (spin_button,
624                                  priv->adjustment,
625                                  g_value_get_double (value),
626                                  priv->digits);
627       break;
628     case PROP_DIGITS:
629       gtk_spin_button_configure (spin_button,
630                                  priv->adjustment,
631                                  priv->climb_rate,
632                                  g_value_get_uint (value));
633       break;
634     case PROP_SNAP_TO_TICKS:
635       gtk_spin_button_set_snap_to_ticks (spin_button, g_value_get_boolean (value));
636       break;
637     case PROP_NUMERIC:
638       gtk_spin_button_set_numeric (spin_button, g_value_get_boolean (value));
639       break;
640     case PROP_WRAP:
641       gtk_spin_button_set_wrap (spin_button, g_value_get_boolean (value));
642       break;
643     case PROP_UPDATE_POLICY:
644       gtk_spin_button_set_update_policy (spin_button, g_value_get_enum (value));
645       break;
646     case PROP_VALUE:
647       gtk_spin_button_set_value (spin_button, g_value_get_double (value));
648       break;
649     case PROP_ORIENTATION:
650       gtk_spin_button_set_orientation (spin_button, g_value_get_enum (value));
651       break;
652     default:
653       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
654       break;
655     }
656 }
657 
658 static void
gtk_spin_button_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)659 gtk_spin_button_get_property (GObject      *object,
660                               guint         prop_id,
661                               GValue       *value,
662                               GParamSpec   *pspec)
663 {
664   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
665   GtkSpinButtonPrivate *priv = spin_button->priv;
666 
667   switch (prop_id)
668     {
669     case PROP_ADJUSTMENT:
670       g_value_set_object (value, priv->adjustment);
671       break;
672     case PROP_CLIMB_RATE:
673       g_value_set_double (value, priv->climb_rate);
674       break;
675     case PROP_DIGITS:
676       g_value_set_uint (value, priv->digits);
677       break;
678     case PROP_SNAP_TO_TICKS:
679       g_value_set_boolean (value, priv->snap_to_ticks);
680       break;
681     case PROP_NUMERIC:
682       g_value_set_boolean (value, priv->numeric);
683       break;
684     case PROP_WRAP:
685       g_value_set_boolean (value, priv->wrap);
686       break;
687     case PROP_UPDATE_POLICY:
688       g_value_set_enum (value, priv->update_policy);
689       break;
690      case PROP_VALUE:
691        g_value_set_double (value, gtk_adjustment_get_value (priv->adjustment));
692       break;
693     case PROP_ORIENTATION:
694       g_value_set_enum (value, priv->orientation);
695       break;
696     default:
697       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
698       break;
699     }
700 }
701 
702 static void
swipe_gesture_begin(GtkGesture * gesture,GdkEventSequence * sequence,GtkSpinButton * spin_button)703 swipe_gesture_begin (GtkGesture       *gesture,
704                      GdkEventSequence *sequence,
705                      GtkSpinButton    *spin_button)
706 {
707   GdkEventSequence *current;
708   const GdkEvent *event;
709 
710   current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
711   event = gtk_gesture_get_last_event (gesture, current);
712 
713   if (event->any.window == spin_button->priv->up_panel ||
714       event->any.window == spin_button->priv->down_panel)
715     gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
716 
717   gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
718   gtk_widget_grab_focus (GTK_WIDGET (spin_button));
719 }
720 
721 static void
swipe_gesture_update(GtkGesture * gesture,GdkEventSequence * sequence,GtkSpinButton * spin_button)722 swipe_gesture_update (GtkGesture       *gesture,
723                       GdkEventSequence *sequence,
724                       GtkSpinButton    *spin_button)
725 {
726   gdouble vel_y;
727 
728   gtk_gesture_swipe_get_velocity (GTK_GESTURE_SWIPE (gesture), NULL, &vel_y);
729   gtk_spin_button_real_spin (spin_button, -vel_y / 20);
730 }
731 
732 static void
update_node_ordering(GtkSpinButton * spin_button)733 update_node_ordering (GtkSpinButton *spin_button)
734 {
735   GtkSpinButtonPrivate *priv = spin_button->priv;
736   int down_button_pos, up_button_pos;
737 
738   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
739     {
740       if (gtk_widget_get_direction (GTK_WIDGET (spin_button)) == GTK_TEXT_DIR_LTR)
741         {
742           down_button_pos = 1;
743           up_button_pos = -1;
744         }
745       else
746         {
747           down_button_pos = 1;
748           up_button_pos = 0;
749         }
750     }
751   else
752     {
753       up_button_pos = 0;
754       down_button_pos = -1;
755     }
756 
757   gtk_box_gadget_set_orientation (GTK_BOX_GADGET (priv->gadget), priv->orientation);
758   gtk_box_gadget_remove_gadget (GTK_BOX_GADGET (priv->gadget), priv->up_button);
759   gtk_box_gadget_remove_gadget (GTK_BOX_GADGET (priv->gadget), priv->down_button);
760   gtk_box_gadget_insert_gadget (GTK_BOX_GADGET (priv->gadget),
761                                 up_button_pos, priv->up_button,
762                                 FALSE, GTK_ALIGN_FILL);
763   gtk_box_gadget_insert_gadget (GTK_BOX_GADGET (priv->gadget),
764                                 down_button_pos, priv->down_button,
765                                 FALSE, GTK_ALIGN_FILL);
766 }
767 
768 static void
gtk_spin_button_init(GtkSpinButton * spin_button)769 gtk_spin_button_init (GtkSpinButton *spin_button)
770 {
771   GtkSpinButtonPrivate *priv;
772   GtkCssNode *widget_node, *entry_node;
773 
774   spin_button->priv = gtk_spin_button_get_instance_private (spin_button);
775   priv = spin_button->priv;
776 
777   priv->adjustment = NULL;
778   priv->down_panel = NULL;
779   priv->up_panel = NULL;
780   priv->timer = 0;
781   priv->climb_rate = 0.0;
782   priv->timer_step = 0.0;
783   priv->update_policy = GTK_UPDATE_ALWAYS;
784   priv->in_child = NULL;
785   priv->click_child = NULL;
786   priv->button = 0;
787   priv->need_timer = FALSE;
788   priv->timer_calls = 0;
789   priv->digits = 0;
790   priv->numeric = FALSE;
791   priv->wrap = FALSE;
792   priv->snap_to_ticks = FALSE;
793 
794   priv->orientation = GTK_ORIENTATION_HORIZONTAL;
795 
796   _gtk_orientable_set_style_classes (GTK_ORIENTABLE (spin_button));
797 
798   widget_node = gtk_widget_get_css_node (GTK_WIDGET (spin_button));
799 
800   priv->gadget = gtk_box_gadget_new_for_node (widget_node, GTK_WIDGET (spin_button));
801 
802   entry_node = gtk_css_node_new ();
803   gtk_css_node_set_name (entry_node, I_("entry"));
804   gtk_css_node_set_parent (entry_node, widget_node);
805   gtk_css_node_set_state (entry_node, gtk_css_node_get_state (widget_node));
806   gtk_css_gadget_set_node (gtk_entry_get_gadget (GTK_ENTRY (spin_button)), entry_node);
807   g_object_unref (entry_node);
808   gtk_box_gadget_insert_gadget (GTK_BOX_GADGET (priv->gadget),
809                                 -1, gtk_entry_get_gadget (GTK_ENTRY (spin_button)),
810                                 TRUE, GTK_ALIGN_FILL);
811 
812   priv->down_button = gtk_icon_helper_new_named ("button",
813                                                  GTK_WIDGET (spin_button));
814   _gtk_icon_helper_set_use_fallback (GTK_ICON_HELPER (priv->down_button), TRUE);
815   _gtk_icon_helper_set_icon_name (GTK_ICON_HELPER (priv->down_button), "list-remove-symbolic", GTK_ICON_SIZE_MENU);
816   gtk_css_gadget_add_class (priv->down_button, "down");
817   gtk_css_node_set_parent (gtk_css_gadget_get_node (priv->down_button), widget_node);
818   gtk_css_node_set_state (gtk_css_gadget_get_node (priv->down_button), gtk_css_node_get_state (widget_node));
819   gtk_box_gadget_insert_gadget (GTK_BOX_GADGET (priv->gadget),
820                                 -1, priv->down_button,
821                                 FALSE, GTK_ALIGN_FILL);
822 
823   priv->up_button = gtk_icon_helper_new_named ("button",
824                                                GTK_WIDGET (spin_button));
825   _gtk_icon_helper_set_use_fallback (GTK_ICON_HELPER (priv->up_button), TRUE);
826   _gtk_icon_helper_set_icon_name (GTK_ICON_HELPER (priv->up_button), "list-add-symbolic", GTK_ICON_SIZE_MENU);
827   gtk_css_gadget_add_class (priv->up_button, "up");
828   gtk_css_node_set_parent (gtk_css_gadget_get_node (priv->up_button), widget_node);
829   gtk_css_node_set_state (gtk_css_gadget_get_node (priv->up_button), gtk_css_node_get_state (widget_node));
830   gtk_box_gadget_insert_gadget (GTK_BOX_GADGET (priv->gadget),
831                                 -1, priv->up_button,
832                                 FALSE, GTK_ALIGN_FILL);
833 
834   gtk_spin_button_set_adjustment (spin_button, NULL);
835 
836   update_node_ordering (spin_button);
837   update_node_state (spin_button);
838 
839   gtk_widget_add_events (GTK_WIDGET (spin_button), GDK_SCROLL_MASK);
840 
841   priv->swipe_gesture = gtk_gesture_swipe_new (GTK_WIDGET (spin_button));
842   gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->swipe_gesture), TRUE);
843   gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->swipe_gesture),
844                                               GTK_PHASE_CAPTURE);
845   g_signal_connect (priv->swipe_gesture, "begin",
846                     G_CALLBACK (swipe_gesture_begin), spin_button);
847   g_signal_connect (priv->swipe_gesture, "update",
848                     G_CALLBACK (swipe_gesture_update), spin_button);
849 }
850 
851 static void
gtk_spin_button_finalize(GObject * object)852 gtk_spin_button_finalize (GObject *object)
853 {
854   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
855   GtkSpinButtonPrivate *priv = spin_button->priv;
856 
857   gtk_spin_button_unset_adjustment (spin_button);
858   g_clear_object (&priv->gadget);
859   g_clear_object (&priv->down_button);
860   g_clear_object (&priv->up_button);
861 
862   g_object_unref (priv->swipe_gesture);
863 
864   G_OBJECT_CLASS (gtk_spin_button_parent_class)->finalize (object);
865 }
866 
867 static void
gtk_spin_button_destroy(GtkWidget * widget)868 gtk_spin_button_destroy (GtkWidget *widget)
869 {
870   gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));
871 
872   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->destroy (widget);
873 }
874 
875 static void
gtk_spin_button_map(GtkWidget * widget)876 gtk_spin_button_map (GtkWidget *widget)
877 {
878   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
879   GtkSpinButtonPrivate *priv = spin_button->priv;
880 
881   if (gtk_widget_get_realized (widget) && !gtk_widget_get_mapped (widget))
882     {
883       GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->map (widget);
884       gdk_window_show (priv->down_panel);
885       gdk_window_show (priv->up_panel);
886     }
887 }
888 
889 static void
gtk_spin_button_unmap(GtkWidget * widget)890 gtk_spin_button_unmap (GtkWidget *widget)
891 {
892   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
893   GtkSpinButtonPrivate *priv = spin_button->priv;
894 
895   if (gtk_widget_get_mapped (widget))
896     {
897       gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));
898 
899       gdk_window_hide (priv->down_panel);
900       gdk_window_hide (priv->up_panel);
901       GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unmap (widget);
902     }
903 }
904 
905 static gboolean
gtk_spin_button_panel_at_limit(GtkSpinButton * spin_button,gint panel)906 gtk_spin_button_panel_at_limit (GtkSpinButton *spin_button,
907                                 gint           panel)
908 {
909   GtkSpinButtonPrivate *priv = spin_button->priv;
910 
911   if (priv->wrap)
912     return FALSE;
913 
914   if (panel == UP_PANEL &&
915       (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_value (priv->adjustment) <= EPSILON))
916     return TRUE;
917 
918   if (panel == DOWN_PANEL &&
919       (gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment) <= EPSILON))
920     return TRUE;
921 
922   return FALSE;
923 }
924 
925 static GtkStateFlags
gtk_spin_button_panel_get_state(GtkSpinButton * spin_button,gint panel)926 gtk_spin_button_panel_get_state (GtkSpinButton *spin_button,
927                                  gint           panel)
928 {
929   GtkStateFlags state;
930   GtkSpinButtonPrivate *priv = spin_button->priv;
931 
932   state = gtk_widget_get_state_flags (GTK_WIDGET (spin_button));
933 
934   state &= ~(GTK_STATE_FLAG_ACTIVE | GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_DROP_ACTIVE);
935 
936   if ((state & GTK_STATE_FLAG_INSENSITIVE) ||
937       gtk_spin_button_panel_at_limit (spin_button, panel) ||
938       !gtk_editable_get_editable (GTK_EDITABLE (spin_button)))
939     {
940       state |= GTK_STATE_FLAG_INSENSITIVE;
941     }
942   else
943     {
944       GdkWindow *panel_win;
945 
946       panel_win = panel == UP_PANEL ? priv->up_panel : priv->down_panel;
947 
948       if (priv->click_child &&
949           priv->click_child == panel_win)
950         state |= GTK_STATE_FLAG_ACTIVE;
951       else if (priv->in_child &&
952                priv->in_child == panel_win &&
953                priv->click_child == NULL)
954         state |= GTK_STATE_FLAG_PRELIGHT;
955     }
956 
957   return state;
958 }
959 
960 static void
update_node_state(GtkSpinButton * spin_button)961 update_node_state (GtkSpinButton *spin_button)
962 {
963   GtkSpinButtonPrivate *priv = spin_button->priv;
964 
965   gtk_css_gadget_set_state (priv->up_button, gtk_spin_button_panel_get_state (spin_button, UP_PANEL));
966   gtk_css_gadget_set_state (priv->down_button, gtk_spin_button_panel_get_state (spin_button, DOWN_PANEL));
967 }
968 
969 static void
gtk_spin_button_realize(GtkWidget * widget)970 gtk_spin_button_realize (GtkWidget *widget)
971 {
972   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
973   GtkSpinButtonPrivate *priv = spin_button->priv;
974   GtkAllocation down_allocation, up_allocation;
975   GdkWindowAttr attributes;
976   gint attributes_mask;
977   gboolean return_val;
978 
979   gtk_widget_set_events (widget, gtk_widget_get_events (widget) |
980                          GDK_KEY_RELEASE_MASK);
981   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->realize (widget);
982 
983   attributes.window_type = GDK_WINDOW_CHILD;
984   attributes.wclass = GDK_INPUT_ONLY;
985   attributes.visual = gtk_widget_get_visual (widget);
986   attributes.event_mask = gtk_widget_get_events (widget);
987   attributes.event_mask |= GDK_BUTTON_PRESS_MASK
988     | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK
989     | GDK_POINTER_MOTION_MASK;
990 
991   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
992 
993   gtk_css_gadget_get_border_allocation (priv->up_button, &up_allocation, NULL);
994   gtk_css_gadget_get_border_allocation (priv->down_button, &down_allocation, NULL);
995 
996   /* create the left panel window */
997   attributes.x = down_allocation.x;
998   attributes.y = down_allocation.y;
999   attributes.width = down_allocation.width;
1000   attributes.height = down_allocation.height;
1001 
1002   priv->down_panel = gdk_window_new (gtk_widget_get_window (widget),
1003                                      &attributes, attributes_mask);
1004   gtk_widget_register_window (widget, priv->down_panel);
1005 
1006   /* create the right panel window */
1007   attributes.x = up_allocation.x;
1008   attributes.y = up_allocation.y;
1009   attributes.width = up_allocation.width;
1010   attributes.height = up_allocation.height;
1011 
1012   priv->up_panel = gdk_window_new (gtk_widget_get_window (widget),
1013                                       &attributes, attributes_mask);
1014   gtk_widget_register_window (widget, priv->up_panel);
1015 
1016   return_val = FALSE;
1017   g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
1018 
1019   /* If output wasn't processed explicitly by the method connected to the
1020    * 'output' signal; and if we don't have any explicit 'text' set initially,
1021    * fallback to the default output. */
1022   if (!return_val &&
1023       (spin_button->priv->numeric || gtk_entry_get_text (GTK_ENTRY (spin_button)) == NULL))
1024     gtk_spin_button_default_output (spin_button);
1025 
1026   gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1027 }
1028 
1029 static void
gtk_spin_button_unrealize(GtkWidget * widget)1030 gtk_spin_button_unrealize (GtkWidget *widget)
1031 {
1032   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1033   GtkSpinButtonPrivate *priv = spin->priv;
1034 
1035   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unrealize (widget);
1036 
1037   if (priv->down_panel)
1038     {
1039       gtk_widget_unregister_window (widget, priv->down_panel);
1040       gdk_window_destroy (priv->down_panel);
1041       priv->down_panel = NULL;
1042     }
1043 
1044   if (priv->up_panel)
1045     {
1046       gtk_widget_unregister_window (widget, priv->up_panel);
1047       gdk_window_destroy (priv->up_panel);
1048       priv->up_panel = NULL;
1049     }
1050 }
1051 
1052 /* Callback used when the spin button's adjustment changes.
1053  * We need to redraw the arrows when the adjustment’s range
1054  * changes, and reevaluate our size request.
1055  */
1056 static void
adjustment_changed_cb(GtkAdjustment * adjustment,gpointer data)1057 adjustment_changed_cb (GtkAdjustment *adjustment, gpointer data)
1058 {
1059   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (data);
1060   GtkSpinButtonPrivate *priv = spin_button->priv;
1061 
1062   priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
1063   update_node_state (spin_button);
1064   gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1065 }
1066 
1067 static void
gtk_spin_button_unset_adjustment(GtkSpinButton * spin_button)1068 gtk_spin_button_unset_adjustment (GtkSpinButton *spin_button)
1069 {
1070   GtkSpinButtonPrivate *priv = spin_button->priv;
1071 
1072   if (priv->adjustment)
1073     {
1074       g_signal_handlers_disconnect_by_func (priv->adjustment,
1075                                             gtk_spin_button_value_changed,
1076                                             spin_button);
1077       g_signal_handlers_disconnect_by_func (priv->adjustment,
1078                                             adjustment_changed_cb,
1079                                             spin_button);
1080       g_clear_object (&priv->adjustment);
1081     }
1082 }
1083 
1084 static void
gtk_spin_button_set_orientation(GtkSpinButton * spin,GtkOrientation orientation)1085 gtk_spin_button_set_orientation (GtkSpinButton  *spin,
1086                                  GtkOrientation  orientation)
1087 {
1088   GtkEntry *entry = GTK_ENTRY (spin);
1089   GtkSpinButtonPrivate *priv = spin->priv;
1090 
1091   if (priv->orientation == orientation)
1092     return;
1093 
1094   priv->orientation = orientation;
1095   _gtk_orientable_set_style_classes (GTK_ORIENTABLE (spin));
1096 
1097   /* change alignment if it's the default */
1098   if (priv->orientation == GTK_ORIENTATION_VERTICAL &&
1099       gtk_entry_get_alignment (entry) == 0.0)
1100     gtk_entry_set_alignment (entry, 0.5);
1101   else if (priv->orientation == GTK_ORIENTATION_HORIZONTAL &&
1102            gtk_entry_get_alignment (entry) == 0.5)
1103     gtk_entry_set_alignment (entry, 0.0);
1104 
1105   update_node_ordering (spin);
1106 
1107   g_object_notify (G_OBJECT (spin), "orientation");
1108   gtk_widget_queue_resize (GTK_WIDGET (spin));
1109 }
1110 
1111 static gint
measure_string_width(PangoLayout * layout,const gchar * string)1112 measure_string_width (PangoLayout *layout,
1113                       const gchar *string)
1114 {
1115   gint width;
1116 
1117   pango_layout_set_text (layout, string, -1);
1118   pango_layout_get_pixel_size (layout, &width, NULL);
1119 
1120   return width;
1121 }
1122 
1123 static gchar *
weed_out_neg_zero(gchar * str,gint digits)1124 weed_out_neg_zero (gchar *str,
1125                    gint   digits)
1126 {
1127   if (str[0] == '-')
1128     {
1129       gchar neg_zero[8];
1130       g_snprintf (neg_zero, 8, "%0.*f", digits, -0.0);
1131       if (strcmp (neg_zero, str) == 0)
1132         memmove (str, str + 1, strlen (str));
1133     }
1134   return str;
1135 }
1136 
1137 static gchar *
gtk_spin_button_format_for_value(GtkSpinButton * spin_button,gdouble value)1138 gtk_spin_button_format_for_value (GtkSpinButton *spin_button,
1139                                   gdouble        value)
1140 {
1141   GtkSpinButtonPrivate *priv = spin_button->priv;
1142   gchar *buf = g_strdup_printf ("%0.*f", priv->digits, value);
1143 
1144   return weed_out_neg_zero (buf, priv->digits);
1145 }
1146 
1147 gint
gtk_spin_button_get_text_width(GtkSpinButton * spin_button)1148 gtk_spin_button_get_text_width (GtkSpinButton *spin_button)
1149 {
1150   GtkSpinButtonPrivate *priv = spin_button->priv;
1151   gint width, w;
1152   PangoLayout *layout;
1153   gchar *str;
1154   gdouble value;
1155 
1156   layout = pango_layout_copy (gtk_entry_get_layout (GTK_ENTRY (spin_button)));
1157 
1158   /* Get max of MIN_SPIN_BUTTON_WIDTH, size of upper, size of lower */
1159   width = MIN_SPIN_BUTTON_WIDTH;
1160 
1161   value = CLAMP (gtk_adjustment_get_upper (priv->adjustment), -1e7, 1e7);
1162   str = gtk_spin_button_format_for_value (spin_button, value);
1163   w = measure_string_width (layout, str);
1164   width = MAX (width, w);
1165   g_free (str);
1166 
1167   value = CLAMP (gtk_adjustment_get_lower (priv->adjustment), -1e7, 1e7);
1168   str = gtk_spin_button_format_for_value (spin_button, value);
1169   w = measure_string_width (layout, str);
1170   width = MAX (width, w);
1171   g_free (str);
1172 
1173   g_object_unref (layout);
1174 
1175   return width;
1176 }
1177 
1178 static void
gtk_spin_button_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)1179 gtk_spin_button_get_preferred_width (GtkWidget *widget,
1180                                      gint      *minimum,
1181                                      gint      *natural)
1182 {
1183   gtk_css_gadget_get_preferred_size (GTK_SPIN_BUTTON (widget)->priv->gadget,
1184                                      GTK_ORIENTATION_HORIZONTAL,
1185                                      -1,
1186                                      minimum, natural,
1187                                      NULL, NULL);
1188 }
1189 
1190 static void
gtk_spin_button_get_preferred_height_and_baseline_for_width(GtkWidget * widget,gint width,gint * minimum,gint * natural,gint * minimum_baseline,gint * natural_baseline)1191 gtk_spin_button_get_preferred_height_and_baseline_for_width (GtkWidget *widget,
1192 							     gint       width,
1193 							     gint      *minimum,
1194 							     gint      *natural,
1195 							     gint      *minimum_baseline,
1196 							     gint      *natural_baseline)
1197 {
1198   gtk_css_gadget_get_preferred_size (GTK_SPIN_BUTTON (widget)->priv->gadget,
1199                                      GTK_ORIENTATION_VERTICAL,
1200                                      width,
1201                                      minimum, natural,
1202                                      minimum_baseline, natural_baseline);
1203 }
1204 
1205 static void
gtk_spin_button_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)1206 gtk_spin_button_get_preferred_height (GtkWidget *widget,
1207                                       gint      *minimum,
1208                                       gint      *natural)
1209 {
1210   gtk_spin_button_get_preferred_height_and_baseline_for_width (widget, -1, minimum, natural, NULL, NULL);
1211 }
1212 
1213 static void
gtk_spin_button_size_allocate(GtkWidget * widget,GtkAllocation * allocation)1214 gtk_spin_button_size_allocate (GtkWidget     *widget,
1215                                GtkAllocation *allocation)
1216 {
1217   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1218   GtkSpinButtonPrivate *priv = spin->priv;
1219   GtkAllocation clip;
1220 
1221   gtk_widget_set_allocation (widget, allocation);
1222 
1223   gtk_css_gadget_allocate (priv->gadget,
1224                            allocation,
1225                            gtk_widget_get_allocated_baseline (widget),
1226                            &clip);
1227 
1228   gtk_widget_set_clip (widget, &clip);
1229 
1230   if (gtk_widget_get_realized (widget))
1231     {
1232       GtkAllocation button_alloc;
1233 
1234       gtk_css_gadget_get_border_allocation (priv->down_button, &button_alloc, NULL);
1235       gdk_window_move_resize (priv->down_panel,
1236                               button_alloc.x, button_alloc.y,
1237                               button_alloc.width, button_alloc.height);
1238 
1239       gtk_css_gadget_get_border_allocation (priv->up_button, &button_alloc, NULL);
1240       gdk_window_move_resize (priv->up_panel,
1241                               button_alloc.x, button_alloc.y,
1242                               button_alloc.width, button_alloc.height);
1243     }
1244 }
1245 
1246 static gint
gtk_spin_button_draw(GtkWidget * widget,cairo_t * cr)1247 gtk_spin_button_draw (GtkWidget *widget,
1248                       cairo_t   *cr)
1249 {
1250   gtk_css_gadget_draw (GTK_SPIN_BUTTON(widget)->priv->gadget, cr);
1251 
1252   return GDK_EVENT_PROPAGATE;
1253 }
1254 
1255 static gint
gtk_spin_button_enter_notify(GtkWidget * widget,GdkEventCrossing * event)1256 gtk_spin_button_enter_notify (GtkWidget        *widget,
1257                               GdkEventCrossing *event)
1258 {
1259   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1260   GtkSpinButtonPrivate *priv = spin->priv;
1261 
1262   if (event->window == priv->down_panel ||
1263       event->window == priv->up_panel)
1264     {
1265       priv->in_child = event->window;
1266       update_node_state (spin);
1267       gtk_widget_queue_draw (GTK_WIDGET (spin));
1268     }
1269 
1270   return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->enter_notify_event (widget, event);
1271 }
1272 
1273 static gint
gtk_spin_button_leave_notify(GtkWidget * widget,GdkEventCrossing * event)1274 gtk_spin_button_leave_notify (GtkWidget        *widget,
1275                               GdkEventCrossing *event)
1276 {
1277   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1278   GtkSpinButtonPrivate *priv = spin->priv;
1279 
1280   if (priv->in_child != NULL)
1281     {
1282       priv->in_child = NULL;
1283       update_node_state (spin);
1284       gtk_widget_queue_draw (GTK_WIDGET (spin));
1285     }
1286 
1287   return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->leave_notify_event (widget, event);
1288 }
1289 
1290 static gint
gtk_spin_button_focus_out(GtkWidget * widget,GdkEventFocus * event)1291 gtk_spin_button_focus_out (GtkWidget     *widget,
1292                            GdkEventFocus *event)
1293 {
1294   if (gtk_editable_get_editable (GTK_EDITABLE (widget)))
1295     gtk_spin_button_update (GTK_SPIN_BUTTON (widget));
1296 
1297   return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->focus_out_event (widget, event);
1298 }
1299 
1300 static void
gtk_spin_button_grab_notify(GtkWidget * widget,gboolean was_grabbed)1301 gtk_spin_button_grab_notify (GtkWidget *widget,
1302                              gboolean   was_grabbed)
1303 {
1304   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1305 
1306   if (!was_grabbed)
1307     {
1308       if (gtk_spin_button_stop_spinning (spin))
1309         gtk_widget_queue_draw (GTK_WIDGET (spin));
1310     }
1311 }
1312 
1313 static void
gtk_spin_button_state_flags_changed(GtkWidget * widget,GtkStateFlags previous_state)1314 gtk_spin_button_state_flags_changed (GtkWidget     *widget,
1315                                      GtkStateFlags  previous_state)
1316 {
1317   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1318 
1319   if (!gtk_widget_is_sensitive (widget))
1320     {
1321       if (gtk_spin_button_stop_spinning (spin))
1322         gtk_widget_queue_draw (GTK_WIDGET (spin));
1323     }
1324 
1325   gtk_css_gadget_set_state (gtk_entry_get_gadget (GTK_ENTRY (widget)), gtk_widget_get_state_flags (widget));
1326   update_node_state (spin);
1327 
1328   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->state_flags_changed (widget, previous_state);
1329 }
1330 
1331 static gint
gtk_spin_button_scroll(GtkWidget * widget,GdkEventScroll * event)1332 gtk_spin_button_scroll (GtkWidget      *widget,
1333                         GdkEventScroll *event)
1334 {
1335   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1336   GtkSpinButtonPrivate *priv = spin->priv;
1337 
1338   if (event->direction == GDK_SCROLL_UP)
1339     {
1340       if (!gtk_widget_has_focus (widget))
1341         gtk_widget_grab_focus (widget);
1342       gtk_spin_button_real_spin (spin, gtk_adjustment_get_step_increment (priv->adjustment));
1343     }
1344   else if (event->direction == GDK_SCROLL_DOWN)
1345     {
1346       if (!gtk_widget_has_focus (widget))
1347         gtk_widget_grab_focus (widget);
1348       gtk_spin_button_real_spin (spin, -gtk_adjustment_get_step_increment (priv->adjustment));
1349     }
1350   else
1351     return FALSE;
1352 
1353   return TRUE;
1354 }
1355 
1356 static gboolean
gtk_spin_button_stop_spinning(GtkSpinButton * spin)1357 gtk_spin_button_stop_spinning (GtkSpinButton *spin)
1358 {
1359   GtkSpinButtonPrivate *priv = spin->priv;
1360   gboolean did_spin = FALSE;
1361 
1362   if (priv->timer)
1363     {
1364       g_source_remove (priv->timer);
1365       priv->timer = 0;
1366       priv->need_timer = FALSE;
1367 
1368       did_spin = TRUE;
1369     }
1370 
1371   priv->button = 0;
1372   priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
1373   priv->timer_calls = 0;
1374 
1375   priv->click_child = NULL;
1376 
1377   return did_spin;
1378 }
1379 
1380 static void
start_spinning(GtkSpinButton * spin,GdkWindow * click_child,gdouble step)1381 start_spinning (GtkSpinButton *spin,
1382                 GdkWindow     *click_child,
1383                 gdouble        step)
1384 {
1385   GtkSpinButtonPrivate *priv;
1386 
1387   priv = spin->priv;
1388 
1389   priv->click_child = click_child;
1390 
1391   if (!priv->timer)
1392     {
1393       priv->timer_step = step;
1394       priv->need_timer = TRUE;
1395       priv->timer = gdk_threads_add_timeout (TIMEOUT_INITIAL,
1396                                    (GSourceFunc) gtk_spin_button_timer,
1397                                    (gpointer) spin);
1398       g_source_set_name_by_id (priv->timer, "[gtk+] gtk_spin_button_timer");
1399     }
1400   gtk_spin_button_real_spin (spin, click_child == priv->up_panel ? step : -step);
1401 
1402   gtk_widget_queue_draw (GTK_WIDGET (spin));
1403 }
1404 
1405 static gint
gtk_spin_button_button_press(GtkWidget * widget,GdkEventButton * event)1406 gtk_spin_button_button_press (GtkWidget      *widget,
1407                               GdkEventButton *event)
1408 {
1409   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1410   GtkSpinButtonPrivate *priv = spin->priv;
1411 
1412   if (!priv->button)
1413     {
1414       if ((event->window == priv->down_panel) ||
1415           (event->window == priv->up_panel))
1416         {
1417           if (!gtk_widget_has_focus (widget))
1418             gtk_widget_grab_focus (widget);
1419           priv->button = event->button;
1420 
1421           if (gtk_editable_get_editable (GTK_EDITABLE (widget))) {
1422             gtk_spin_button_update (spin);
1423 
1424             if (event->button == GDK_BUTTON_PRIMARY)
1425               start_spinning (spin, event->window, gtk_adjustment_get_step_increment (priv->adjustment));
1426             else if (event->button == GDK_BUTTON_MIDDLE)
1427               start_spinning (spin, event->window, gtk_adjustment_get_page_increment (priv->adjustment));
1428             else
1429               priv->click_child = event->window;
1430           } else
1431             gtk_widget_error_bell (widget);
1432 
1433           return TRUE;
1434         }
1435       else
1436         {
1437           return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->button_press_event (widget, event);
1438         }
1439     }
1440   return FALSE;
1441 }
1442 
1443 static gint
gtk_spin_button_button_release(GtkWidget * widget,GdkEventButton * event)1444 gtk_spin_button_button_release (GtkWidget      *widget,
1445                                 GdkEventButton *event)
1446 {
1447   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1448   GtkSpinButtonPrivate *priv = spin->priv;
1449 
1450   if (event->button == priv->button)
1451     {
1452       GdkWindow *click_child = priv->click_child;
1453 
1454       gtk_spin_button_stop_spinning (spin);
1455 
1456       if (event->button == GDK_BUTTON_SECONDARY)
1457         {
1458           gdouble diff;
1459 
1460           if (event->window == priv->down_panel &&
1461               click_child == event->window)
1462             {
1463               diff = gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment);
1464               if (diff > EPSILON)
1465                 gtk_spin_button_real_spin (spin, -diff);
1466             }
1467           else if (event->window == priv->up_panel &&
1468                    click_child == event->window)
1469             {
1470               diff = gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_value (priv->adjustment);
1471               if (diff > EPSILON)
1472                 gtk_spin_button_real_spin (spin, diff);
1473             }
1474         }
1475 
1476       update_node_state (spin);
1477       gtk_widget_queue_draw (GTK_WIDGET (spin));
1478 
1479       return TRUE;
1480     }
1481   else
1482     {
1483       return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->button_release_event (widget, event);
1484     }
1485 }
1486 
1487 static gint
gtk_spin_button_motion_notify(GtkWidget * widget,GdkEventMotion * event)1488 gtk_spin_button_motion_notify (GtkWidget      *widget,
1489                                GdkEventMotion *event)
1490 {
1491   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1492   GtkSpinButtonPrivate *priv = spin->priv;
1493 
1494   if (priv->button)
1495     return FALSE;
1496 
1497   if (event->window == priv->down_panel ||
1498       event->window == priv->up_panel)
1499     {
1500       gdk_event_request_motions (event);
1501 
1502       priv->in_child = event->window;
1503       gtk_widget_queue_draw (widget);
1504 
1505       return FALSE;
1506     }
1507 
1508   if (gtk_gesture_is_recognized (priv->swipe_gesture))
1509     return TRUE;
1510 
1511   return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->motion_notify_event (widget, event);
1512 }
1513 
1514 static gint
gtk_spin_button_timer(GtkSpinButton * spin_button)1515 gtk_spin_button_timer (GtkSpinButton *spin_button)
1516 {
1517   GtkSpinButtonPrivate *priv = spin_button->priv;
1518   gboolean retval = FALSE;
1519 
1520   if (priv->timer)
1521     {
1522       if (priv->click_child == priv->up_panel)
1523         gtk_spin_button_real_spin (spin_button, priv->timer_step);
1524       else
1525         gtk_spin_button_real_spin (spin_button, -priv->timer_step);
1526 
1527       if (priv->need_timer)
1528         {
1529           priv->need_timer = FALSE;
1530           priv->timer = gdk_threads_add_timeout (TIMEOUT_REPEAT,
1531                                               (GSourceFunc) gtk_spin_button_timer,
1532                                               (gpointer) spin_button);
1533           g_source_set_name_by_id (priv->timer, "[gtk+] gtk_spin_button_timer");
1534         }
1535       else
1536         {
1537           if (priv->climb_rate > 0.0 && priv->timer_step
1538               < gtk_adjustment_get_page_increment (priv->adjustment))
1539             {
1540               if (priv->timer_calls < MAX_TIMER_CALLS)
1541                 priv->timer_calls++;
1542               else
1543                 {
1544                   priv->timer_calls = 0;
1545                   priv->timer_step += priv->climb_rate;
1546                 }
1547             }
1548           retval = TRUE;
1549         }
1550     }
1551 
1552   return retval;
1553 }
1554 
1555 static void
gtk_spin_button_value_changed(GtkAdjustment * adjustment,GtkSpinButton * spin_button)1556 gtk_spin_button_value_changed (GtkAdjustment *adjustment,
1557                                GtkSpinButton *spin_button)
1558 {
1559   gboolean return_val;
1560 
1561   g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
1562 
1563   return_val = FALSE;
1564   g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
1565   if (return_val == FALSE)
1566     gtk_spin_button_default_output (spin_button);
1567 
1568   g_signal_emit (spin_button, spinbutton_signals[VALUE_CHANGED], 0);
1569 
1570   update_node_state (spin_button);
1571 
1572   gtk_widget_queue_draw (GTK_WIDGET (spin_button));
1573 
1574   g_object_notify (G_OBJECT (spin_button), "value");
1575 }
1576 
1577 static void
gtk_spin_button_real_change_value(GtkSpinButton * spin,GtkScrollType scroll)1578 gtk_spin_button_real_change_value (GtkSpinButton *spin,
1579                                    GtkScrollType  scroll)
1580 {
1581   GtkSpinButtonPrivate *priv = spin->priv;
1582   gdouble old_value;
1583 
1584   if (!gtk_editable_get_editable (GTK_EDITABLE (spin)))
1585     {
1586       gtk_widget_error_bell (GTK_WIDGET (spin));
1587       return;
1588     }
1589 
1590   /* When the key binding is activated, there may be an outstanding
1591    * value, so we first have to commit what is currently written in
1592    * the spin buttons text entry. See #106574
1593    */
1594   gtk_spin_button_update (spin);
1595 
1596   old_value = gtk_adjustment_get_value (priv->adjustment);
1597 
1598   switch (scroll)
1599     {
1600     case GTK_SCROLL_STEP_BACKWARD:
1601     case GTK_SCROLL_STEP_DOWN:
1602     case GTK_SCROLL_STEP_LEFT:
1603       gtk_spin_button_real_spin (spin, -priv->timer_step);
1604 
1605       if (priv->climb_rate > 0.0 && priv->timer_step
1606           < gtk_adjustment_get_page_increment (priv->adjustment))
1607         {
1608           if (priv->timer_calls < MAX_TIMER_CALLS)
1609             priv->timer_calls++;
1610           else
1611             {
1612               priv->timer_calls = 0;
1613               priv->timer_step += priv->climb_rate;
1614             }
1615         }
1616       break;
1617 
1618     case GTK_SCROLL_STEP_FORWARD:
1619     case GTK_SCROLL_STEP_UP:
1620     case GTK_SCROLL_STEP_RIGHT:
1621       gtk_spin_button_real_spin (spin, priv->timer_step);
1622 
1623       if (priv->climb_rate > 0.0 && priv->timer_step
1624           < gtk_adjustment_get_page_increment (priv->adjustment))
1625         {
1626           if (priv->timer_calls < MAX_TIMER_CALLS)
1627             priv->timer_calls++;
1628           else
1629             {
1630               priv->timer_calls = 0;
1631               priv->timer_step += priv->climb_rate;
1632             }
1633         }
1634       break;
1635 
1636     case GTK_SCROLL_PAGE_BACKWARD:
1637     case GTK_SCROLL_PAGE_DOWN:
1638     case GTK_SCROLL_PAGE_LEFT:
1639       gtk_spin_button_real_spin (spin, -gtk_adjustment_get_page_increment (priv->adjustment));
1640       break;
1641 
1642     case GTK_SCROLL_PAGE_FORWARD:
1643     case GTK_SCROLL_PAGE_UP:
1644     case GTK_SCROLL_PAGE_RIGHT:
1645       gtk_spin_button_real_spin (spin, gtk_adjustment_get_page_increment (priv->adjustment));
1646       break;
1647 
1648     case GTK_SCROLL_START:
1649       {
1650         gdouble diff = gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment);
1651         if (diff > EPSILON)
1652           gtk_spin_button_real_spin (spin, -diff);
1653         break;
1654       }
1655 
1656     case GTK_SCROLL_END:
1657       {
1658         gdouble diff = gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_value (priv->adjustment);
1659         if (diff > EPSILON)
1660           gtk_spin_button_real_spin (spin, diff);
1661         break;
1662       }
1663 
1664     default:
1665       g_warning ("Invalid scroll type %d for GtkSpinButton::change-value", scroll);
1666       break;
1667     }
1668 
1669   gtk_spin_button_update (spin);
1670 
1671   if (gtk_adjustment_get_value (priv->adjustment) == old_value)
1672     gtk_widget_error_bell (GTK_WIDGET (spin));
1673 }
1674 
1675 static gint
gtk_spin_button_key_release(GtkWidget * widget,GdkEventKey * event)1676 gtk_spin_button_key_release (GtkWidget   *widget,
1677                              GdkEventKey *event)
1678 {
1679   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1680   GtkSpinButtonPrivate *priv = spin->priv;
1681 
1682   /* We only get a release at the end of a key repeat run, so reset the timer_step */
1683   priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
1684   priv->timer_calls = 0;
1685 
1686   return TRUE;
1687 }
1688 
1689 static void
gtk_spin_button_snap(GtkSpinButton * spin_button,gdouble val)1690 gtk_spin_button_snap (GtkSpinButton *spin_button,
1691                       gdouble        val)
1692 {
1693   GtkSpinButtonPrivate *priv = spin_button->priv;
1694   gdouble inc;
1695   gdouble tmp;
1696 
1697   inc = gtk_adjustment_get_step_increment (priv->adjustment);
1698   if (inc == 0)
1699     return;
1700 
1701   tmp = (val - gtk_adjustment_get_lower (priv->adjustment)) / inc;
1702   if (tmp - floor (tmp) < ceil (tmp) - tmp)
1703     val = gtk_adjustment_get_lower (priv->adjustment) + floor (tmp) * inc;
1704   else
1705     val = gtk_adjustment_get_lower (priv->adjustment) + ceil (tmp) * inc;
1706 
1707   gtk_spin_button_set_value (spin_button, val);
1708 }
1709 
1710 static void
gtk_spin_button_activate(GtkEntry * entry)1711 gtk_spin_button_activate (GtkEntry *entry)
1712 {
1713   if (gtk_editable_get_editable (GTK_EDITABLE (entry)))
1714     gtk_spin_button_update (GTK_SPIN_BUTTON (entry));
1715 
1716   /* Chain up so that entry->activates_default is honored */
1717   GTK_ENTRY_CLASS (gtk_spin_button_parent_class)->activate (entry);
1718 }
1719 
1720 static void
gtk_spin_button_insert_text(GtkEditable * editable,const gchar * new_text,gint new_text_length,gint * position)1721 gtk_spin_button_insert_text (GtkEditable *editable,
1722                              const gchar *new_text,
1723                              gint         new_text_length,
1724                              gint        *position)
1725 {
1726   GtkEntry *entry = GTK_ENTRY (editable);
1727   GtkSpinButton *spin = GTK_SPIN_BUTTON (editable);
1728   GtkSpinButtonPrivate *priv = spin->priv;
1729   GtkEditableInterface *parent_editable_iface;
1730 
1731   parent_editable_iface = g_type_interface_peek (gtk_spin_button_parent_class,
1732                                                  GTK_TYPE_EDITABLE);
1733 
1734   if (priv->numeric)
1735     {
1736       struct lconv *lc;
1737       gboolean sign;
1738       gint dotpos = -1;
1739       gint i;
1740       guint32 pos_sign;
1741       guint32 neg_sign;
1742       gint entry_length;
1743       const gchar *entry_text;
1744 
1745       entry_length = gtk_entry_get_text_length (entry);
1746       entry_text = gtk_entry_get_text (entry);
1747 
1748       lc = localeconv ();
1749 
1750       if (*(lc->negative_sign))
1751         neg_sign = *(lc->negative_sign);
1752       else
1753         neg_sign = '-';
1754 
1755       if (*(lc->positive_sign))
1756         pos_sign = *(lc->positive_sign);
1757       else
1758         pos_sign = '+';
1759 
1760 #ifdef G_OS_WIN32
1761       /* Workaround for bug caused by some Windows application messing
1762        * up the positive sign of the current locale, more specifically
1763        * HKEY_CURRENT_USER\Control Panel\International\sPositiveSign.
1764        * See bug #330743 and for instance
1765        * http://www.msnewsgroups.net/group/microsoft.public.dotnet.languages.csharp/topic36024.aspx
1766        *
1767        * I don't know if the positive sign always gets bogusly set to
1768        * a digit when the above Registry value is corrupted as
1769        * described. (In my test case, it got set to "8", and in the
1770        * bug report above it presumably was set ot "0".) Probably it
1771        * might get set to almost anything? So how to distinguish a
1772        * bogus value from some correct one for some locale? That is
1773        * probably hard, but at least we should filter out the
1774        * digits...
1775        */
1776       if (pos_sign >= '0' && pos_sign <= '9')
1777         pos_sign = '+';
1778 #endif
1779 
1780       for (sign=0, i=0; i<entry_length; i++)
1781         if ((entry_text[i] == neg_sign) ||
1782             (entry_text[i] == pos_sign))
1783           {
1784             sign = 1;
1785             break;
1786           }
1787 
1788       if (sign && !(*position))
1789         return;
1790 
1791       for (dotpos=-1, i=0; i<entry_length; i++)
1792         if (entry_text[i] == *(lc->decimal_point))
1793           {
1794             dotpos = i;
1795             break;
1796           }
1797 
1798       if (dotpos > -1 && *position > dotpos &&
1799           (gint)priv->digits - entry_length
1800             + dotpos - new_text_length + 1 < 0)
1801         return;
1802 
1803       for (i = 0; i < new_text_length; i++)
1804         {
1805           if (new_text[i] == neg_sign || new_text[i] == pos_sign)
1806             {
1807               if (sign || (*position) || i)
1808                 return;
1809               sign = TRUE;
1810             }
1811           else if (new_text[i] == *(lc->decimal_point))
1812             {
1813               if (!priv->digits || dotpos > -1 ||
1814                   (new_text_length - 1 - i + entry_length
1815                     - *position > (gint)priv->digits))
1816                 return;
1817               dotpos = *position + i;
1818             }
1819           else if (new_text[i] < 0x30 || new_text[i] > 0x39)
1820             return;
1821         }
1822     }
1823 
1824   parent_editable_iface->insert_text (editable, new_text,
1825                                       new_text_length, position);
1826 }
1827 
1828 static void
gtk_spin_button_real_spin(GtkSpinButton * spin_button,gdouble increment)1829 gtk_spin_button_real_spin (GtkSpinButton *spin_button,
1830                            gdouble        increment)
1831 {
1832   GtkSpinButtonPrivate *priv = spin_button->priv;
1833   GtkAdjustment *adjustment;
1834   gdouble new_value = 0.0;
1835   gboolean wrapped = FALSE;
1836 
1837   adjustment = priv->adjustment;
1838 
1839   new_value = gtk_adjustment_get_value (adjustment) + increment;
1840 
1841   if (increment > 0)
1842     {
1843       if (priv->wrap)
1844         {
1845           if (fabs (gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_upper (adjustment)) < EPSILON)
1846             {
1847               new_value = gtk_adjustment_get_lower (adjustment);
1848               wrapped = TRUE;
1849             }
1850           else if (new_value > gtk_adjustment_get_upper (adjustment))
1851             new_value = gtk_adjustment_get_upper (adjustment);
1852         }
1853       else
1854         new_value = MIN (new_value, gtk_adjustment_get_upper (adjustment));
1855     }
1856   else if (increment < 0)
1857     {
1858       if (priv->wrap)
1859         {
1860           if (fabs (gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_lower (adjustment)) < EPSILON)
1861             {
1862               new_value = gtk_adjustment_get_upper (adjustment);
1863               wrapped = TRUE;
1864             }
1865           else if (new_value < gtk_adjustment_get_lower (adjustment))
1866             new_value = gtk_adjustment_get_lower (adjustment);
1867         }
1868       else
1869         new_value = MAX (new_value, gtk_adjustment_get_lower (adjustment));
1870     }
1871 
1872   if (fabs (new_value - gtk_adjustment_get_value (adjustment)) > EPSILON)
1873     gtk_adjustment_set_value (adjustment, new_value);
1874 
1875   if (wrapped)
1876     g_signal_emit (spin_button, spinbutton_signals[WRAPPED], 0);
1877 
1878   gtk_widget_queue_draw (GTK_WIDGET (spin_button));
1879 }
1880 
1881 static gint
gtk_spin_button_default_input(GtkSpinButton * spin_button,gdouble * new_val)1882 gtk_spin_button_default_input (GtkSpinButton *spin_button,
1883                                gdouble       *new_val)
1884 {
1885   gchar *err = NULL;
1886   const char *text;
1887 
1888   text = gtk_entry_get_text (GTK_ENTRY (spin_button));
1889 
1890   *new_val = g_strtod (text, &err);
1891   if (*err)
1892     {
1893       /* try to convert with local digits */
1894       gint64 val = 0;
1895       int sign = 1;
1896       const char *p;
1897 
1898       for (p = text; *p; p = g_utf8_next_char (p))
1899         {
1900           gunichar ch = g_utf8_get_char (p);
1901 
1902           if (p == text && ch == '-')
1903             {
1904               sign = -1;
1905               continue;
1906             }
1907 
1908           if (!g_unichar_isdigit (ch))
1909             break;
1910 
1911           val = val * 10 + g_unichar_digit_value (ch);
1912         }
1913 
1914       if (*p)
1915         return GTK_INPUT_ERROR;
1916 
1917       *new_val = sign * val;
1918     }
1919 
1920   return FALSE;
1921 }
1922 
1923 static void
gtk_spin_button_default_output(GtkSpinButton * spin_button)1924 gtk_spin_button_default_output (GtkSpinButton *spin_button)
1925 {
1926   GtkSpinButtonPrivate *priv = spin_button->priv;
1927   gchar *buf = gtk_spin_button_format_for_value (spin_button,
1928                                                  gtk_adjustment_get_value (priv->adjustment));
1929 
1930   if (strcmp (buf, gtk_entry_get_text (GTK_ENTRY (spin_button))))
1931     gtk_entry_set_text (GTK_ENTRY (spin_button), buf);
1932 
1933   g_free (buf);
1934 }
1935 
1936 
1937 /***********************************************************
1938  ***********************************************************
1939  ***                  Public interface                   ***
1940  ***********************************************************
1941  ***********************************************************/
1942 
1943 
1944 /**
1945  * gtk_spin_button_configure:
1946  * @spin_button: a #GtkSpinButton
1947  * @adjustment: (nullable): a #GtkAdjustment to replace the spin button’s
1948  *     existing adjustment, or %NULL to leave its current adjustment unchanged
1949  * @climb_rate: the new climb rate
1950  * @digits: the number of decimal places to display in the spin button
1951  *
1952  * Changes the properties of an existing spin button. The adjustment,
1953  * climb rate, and number of decimal places are updated accordingly.
1954  */
1955 void
gtk_spin_button_configure(GtkSpinButton * spin_button,GtkAdjustment * adjustment,gdouble climb_rate,guint digits)1956 gtk_spin_button_configure (GtkSpinButton *spin_button,
1957                            GtkAdjustment *adjustment,
1958                            gdouble        climb_rate,
1959                            guint          digits)
1960 {
1961   GtkSpinButtonPrivate *priv;
1962 
1963   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
1964 
1965   priv = spin_button->priv;
1966 
1967   if (!adjustment)
1968     adjustment = priv->adjustment;
1969 
1970   g_object_freeze_notify (G_OBJECT (spin_button));
1971 
1972   if (priv->adjustment != adjustment)
1973     {
1974       gtk_spin_button_unset_adjustment (spin_button);
1975 
1976       priv->adjustment = adjustment;
1977       g_object_ref_sink (adjustment);
1978       g_signal_connect (adjustment, "value-changed",
1979                         G_CALLBACK (gtk_spin_button_value_changed),
1980                         spin_button);
1981       g_signal_connect (adjustment, "changed",
1982                         G_CALLBACK (adjustment_changed_cb),
1983                         spin_button);
1984       priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
1985 
1986       g_object_notify (G_OBJECT (spin_button), "adjustment");
1987       gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1988     }
1989 
1990   if (priv->digits != digits)
1991     {
1992       priv->digits = digits;
1993       g_object_notify (G_OBJECT (spin_button), "digits");
1994     }
1995 
1996   if (priv->climb_rate != climb_rate)
1997     {
1998       priv->climb_rate = climb_rate;
1999       g_object_notify (G_OBJECT (spin_button), "climb-rate");
2000     }
2001 
2002   g_object_thaw_notify (G_OBJECT (spin_button));
2003 
2004   gtk_spin_button_value_changed (adjustment, spin_button);
2005 }
2006 
2007 /**
2008  * gtk_spin_button_new:
2009  * @adjustment: (allow-none): the #GtkAdjustment object that this spin
2010  *     button should use, or %NULL
2011  * @climb_rate: specifies by how much the rate of change in the value will
2012  *     accelerate if you continue to hold down an up/down button or arrow key
2013  * @digits: the number of decimal places to display
2014  *
2015  * Creates a new #GtkSpinButton.
2016  *
2017  * Returns: The new spin button as a #GtkWidget
2018  */
2019 GtkWidget *
gtk_spin_button_new(GtkAdjustment * adjustment,gdouble climb_rate,guint digits)2020 gtk_spin_button_new (GtkAdjustment *adjustment,
2021                      gdouble        climb_rate,
2022                      guint          digits)
2023 {
2024   GtkSpinButton *spin;
2025 
2026   if (adjustment)
2027     g_return_val_if_fail (GTK_IS_ADJUSTMENT (adjustment), NULL);
2028 
2029   spin = g_object_new (GTK_TYPE_SPIN_BUTTON, NULL);
2030 
2031   gtk_spin_button_configure (spin, adjustment, climb_rate, digits);
2032 
2033   return GTK_WIDGET (spin);
2034 }
2035 
2036 /**
2037  * gtk_spin_button_new_with_range:
2038  * @min: Minimum allowable value
2039  * @max: Maximum allowable value
2040  * @step: Increment added or subtracted by spinning the widget
2041  *
2042  * This is a convenience constructor that allows creation of a numeric
2043  * #GtkSpinButton without manually creating an adjustment. The value is
2044  * initially set to the minimum value and a page increment of 10 * @step
2045  * is the default. The precision of the spin button is equivalent to the
2046  * precision of @step.
2047  *
2048  * Note that the way in which the precision is derived works best if @step
2049  * is a power of ten. If the resulting precision is not suitable for your
2050  * needs, use gtk_spin_button_set_digits() to correct it.
2051  *
2052  * Returns: The new spin button as a #GtkWidget
2053  */
2054 GtkWidget *
gtk_spin_button_new_with_range(gdouble min,gdouble max,gdouble step)2055 gtk_spin_button_new_with_range (gdouble min,
2056                                 gdouble max,
2057                                 gdouble step)
2058 {
2059   GtkAdjustment *adjustment;
2060   GtkSpinButton *spin;
2061   gint digits;
2062 
2063   g_return_val_if_fail (min <= max, NULL);
2064   g_return_val_if_fail (step != 0.0, NULL);
2065 
2066   spin = g_object_new (GTK_TYPE_SPIN_BUTTON, NULL);
2067 
2068   adjustment = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
2069 
2070   if (fabs (step) >= 1.0 || step == 0.0)
2071     digits = 0;
2072   else {
2073     digits = abs ((gint) floor (log10 (fabs (step))));
2074     if (digits > MAX_DIGITS)
2075       digits = MAX_DIGITS;
2076   }
2077 
2078   gtk_spin_button_configure (spin, adjustment, step, digits);
2079 
2080   gtk_spin_button_set_numeric (spin, TRUE);
2081 
2082   return GTK_WIDGET (spin);
2083 }
2084 
2085 /**
2086  * gtk_spin_button_set_adjustment:
2087  * @spin_button: a #GtkSpinButton
2088  * @adjustment: a #GtkAdjustment to replace the existing adjustment
2089  *
2090  * Replaces the #GtkAdjustment associated with @spin_button.
2091  */
2092 void
gtk_spin_button_set_adjustment(GtkSpinButton * spin_button,GtkAdjustment * adjustment)2093 gtk_spin_button_set_adjustment (GtkSpinButton *spin_button,
2094                                 GtkAdjustment *adjustment)
2095 {
2096   GtkSpinButtonPrivate *priv;
2097 
2098   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2099 
2100   priv = spin_button->priv;
2101 
2102   if (!adjustment)
2103     adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
2104 
2105   gtk_spin_button_configure (spin_button,
2106                              adjustment,
2107                              priv->climb_rate,
2108                              priv->digits);
2109 }
2110 
2111 /**
2112  * gtk_spin_button_get_adjustment:
2113  * @spin_button: a #GtkSpinButton
2114  *
2115  * Get the adjustment associated with a #GtkSpinButton
2116  *
2117  * Returns: (transfer none): the #GtkAdjustment of @spin_button
2118  **/
2119 GtkAdjustment *
gtk_spin_button_get_adjustment(GtkSpinButton * spin_button)2120 gtk_spin_button_get_adjustment (GtkSpinButton *spin_button)
2121 {
2122   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), NULL);
2123 
2124   return spin_button->priv->adjustment;
2125 }
2126 
2127 /**
2128  * gtk_spin_button_set_digits:
2129  * @spin_button: a #GtkSpinButton
2130  * @digits: the number of digits after the decimal point to be displayed for the spin button’s value
2131  *
2132  * Set the precision to be displayed by @spin_button. Up to 20 digit precision
2133  * is allowed.
2134  **/
2135 void
gtk_spin_button_set_digits(GtkSpinButton * spin_button,guint digits)2136 gtk_spin_button_set_digits (GtkSpinButton *spin_button,
2137                             guint          digits)
2138 {
2139   GtkSpinButtonPrivate *priv;
2140 
2141   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2142 
2143   priv = spin_button->priv;
2144 
2145   if (priv->digits != digits)
2146     {
2147       priv->digits = digits;
2148       gtk_spin_button_value_changed (priv->adjustment, spin_button);
2149       g_object_notify (G_OBJECT (spin_button), "digits");
2150 
2151       /* since lower/upper may have changed */
2152       gtk_widget_queue_resize (GTK_WIDGET (spin_button));
2153     }
2154 }
2155 
2156 /**
2157  * gtk_spin_button_get_digits:
2158  * @spin_button: a #GtkSpinButton
2159  *
2160  * Fetches the precision of @spin_button. See gtk_spin_button_set_digits().
2161  *
2162  * Returns: the current precision
2163  **/
2164 guint
gtk_spin_button_get_digits(GtkSpinButton * spin_button)2165 gtk_spin_button_get_digits (GtkSpinButton *spin_button)
2166 {
2167   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0);
2168 
2169   return spin_button->priv->digits;
2170 }
2171 
2172 /**
2173  * gtk_spin_button_set_increments:
2174  * @spin_button: a #GtkSpinButton
2175  * @step: increment applied for a button 1 press.
2176  * @page: increment applied for a button 2 press.
2177  *
2178  * Sets the step and page increments for spin_button.  This affects how
2179  * quickly the value changes when the spin button’s arrows are activated.
2180  **/
2181 void
gtk_spin_button_set_increments(GtkSpinButton * spin_button,gdouble step,gdouble page)2182 gtk_spin_button_set_increments (GtkSpinButton *spin_button,
2183                                 gdouble        step,
2184                                 gdouble        page)
2185 {
2186   GtkSpinButtonPrivate *priv;
2187 
2188   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2189 
2190   priv = spin_button->priv;
2191 
2192   gtk_adjustment_configure (priv->adjustment,
2193                             gtk_adjustment_get_value (priv->adjustment),
2194                             gtk_adjustment_get_lower (priv->adjustment),
2195                             gtk_adjustment_get_upper (priv->adjustment),
2196                             step,
2197                             page,
2198                             gtk_adjustment_get_page_size (priv->adjustment));
2199 }
2200 
2201 /**
2202  * gtk_spin_button_get_increments:
2203  * @spin_button: a #GtkSpinButton
2204  * @step: (out) (allow-none): location to store step increment, or %NULL
2205  * @page: (out) (allow-none): location to store page increment, or %NULL
2206  *
2207  * Gets the current step and page the increments used by @spin_button. See
2208  * gtk_spin_button_set_increments().
2209  **/
2210 void
gtk_spin_button_get_increments(GtkSpinButton * spin_button,gdouble * step,gdouble * page)2211 gtk_spin_button_get_increments (GtkSpinButton *spin_button,
2212                                 gdouble       *step,
2213                                 gdouble       *page)
2214 {
2215   GtkSpinButtonPrivate *priv;
2216 
2217   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2218 
2219   priv = spin_button->priv;
2220 
2221   if (step)
2222     *step = gtk_adjustment_get_step_increment (priv->adjustment);
2223   if (page)
2224     *page = gtk_adjustment_get_page_increment (priv->adjustment);
2225 }
2226 
2227 /**
2228  * gtk_spin_button_set_range:
2229  * @spin_button: a #GtkSpinButton
2230  * @min: minimum allowable value
2231  * @max: maximum allowable value
2232  *
2233  * Sets the minimum and maximum allowable values for @spin_button.
2234  *
2235  * If the current value is outside this range, it will be adjusted
2236  * to fit within the range, otherwise it will remain unchanged.
2237  */
2238 void
gtk_spin_button_set_range(GtkSpinButton * spin_button,gdouble min,gdouble max)2239 gtk_spin_button_set_range (GtkSpinButton *spin_button,
2240                            gdouble        min,
2241                            gdouble        max)
2242 {
2243   GtkAdjustment *adjustment;
2244 
2245   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2246 
2247   adjustment = spin_button->priv->adjustment;
2248 
2249   gtk_adjustment_configure (adjustment,
2250                             CLAMP (gtk_adjustment_get_value (adjustment), min, max),
2251                             min,
2252                             max,
2253                             gtk_adjustment_get_step_increment (adjustment),
2254                             gtk_adjustment_get_page_increment (adjustment),
2255                             gtk_adjustment_get_page_size (adjustment));
2256 }
2257 
2258 /**
2259  * gtk_spin_button_get_range:
2260  * @spin_button: a #GtkSpinButton
2261  * @min: (out) (allow-none): location to store minimum allowed value, or %NULL
2262  * @max: (out) (allow-none): location to store maximum allowed value, or %NULL
2263  *
2264  * Gets the range allowed for @spin_button.
2265  * See gtk_spin_button_set_range().
2266  */
2267 void
gtk_spin_button_get_range(GtkSpinButton * spin_button,gdouble * min,gdouble * max)2268 gtk_spin_button_get_range (GtkSpinButton *spin_button,
2269                            gdouble       *min,
2270                            gdouble       *max)
2271 {
2272   GtkSpinButtonPrivate *priv;
2273 
2274   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2275 
2276   priv = spin_button->priv;
2277 
2278   if (min)
2279     *min = gtk_adjustment_get_lower (priv->adjustment);
2280   if (max)
2281     *max = gtk_adjustment_get_upper (priv->adjustment);
2282 }
2283 
2284 /**
2285  * gtk_spin_button_get_value:
2286  * @spin_button: a #GtkSpinButton
2287  *
2288  * Get the value in the @spin_button.
2289  *
2290  * Returns: the value of @spin_button
2291  */
2292 gdouble
gtk_spin_button_get_value(GtkSpinButton * spin_button)2293 gtk_spin_button_get_value (GtkSpinButton *spin_button)
2294 {
2295   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0.0);
2296 
2297   return gtk_adjustment_get_value (spin_button->priv->adjustment);
2298 }
2299 
2300 /**
2301  * gtk_spin_button_get_value_as_int:
2302  * @spin_button: a #GtkSpinButton
2303  *
2304  * Get the value @spin_button represented as an integer.
2305  *
2306  * Returns: the value of @spin_button
2307  */
2308 gint
gtk_spin_button_get_value_as_int(GtkSpinButton * spin_button)2309 gtk_spin_button_get_value_as_int (GtkSpinButton *spin_button)
2310 {
2311   GtkSpinButtonPrivate *priv;
2312   gdouble val;
2313 
2314   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0);
2315 
2316   priv = spin_button->priv;
2317 
2318   val = gtk_adjustment_get_value (priv->adjustment);
2319   if (val - floor (val) < ceil (val) - val)
2320     return floor (val);
2321   else
2322     return ceil (val);
2323 }
2324 
2325 /**
2326  * gtk_spin_button_set_value:
2327  * @spin_button: a #GtkSpinButton
2328  * @value: the new value
2329  *
2330  * Sets the value of @spin_button.
2331  */
2332 void
gtk_spin_button_set_value(GtkSpinButton * spin_button,gdouble value)2333 gtk_spin_button_set_value (GtkSpinButton *spin_button,
2334                            gdouble        value)
2335 {
2336   GtkSpinButtonPrivate *priv;
2337 
2338   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2339 
2340   priv = spin_button->priv;
2341 
2342   if (fabs (value - gtk_adjustment_get_value (priv->adjustment)) > EPSILON)
2343     gtk_adjustment_set_value (priv->adjustment, value);
2344   else
2345     {
2346       gint return_val = FALSE;
2347       g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
2348       if (!return_val)
2349         gtk_spin_button_default_output (spin_button);
2350     }
2351 }
2352 
2353 /**
2354  * gtk_spin_button_set_update_policy:
2355  * @spin_button: a #GtkSpinButton
2356  * @policy: a #GtkSpinButtonUpdatePolicy value
2357  *
2358  * Sets the update behavior of a spin button.
2359  * This determines whether the spin button is always updated
2360  * or only when a valid value is set.
2361  */
2362 void
gtk_spin_button_set_update_policy(GtkSpinButton * spin_button,GtkSpinButtonUpdatePolicy policy)2363 gtk_spin_button_set_update_policy (GtkSpinButton             *spin_button,
2364                                    GtkSpinButtonUpdatePolicy  policy)
2365 {
2366   GtkSpinButtonPrivate *priv;
2367 
2368   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2369 
2370   priv = spin_button->priv;
2371 
2372   if (priv->update_policy != policy)
2373     {
2374       priv->update_policy = policy;
2375       g_object_notify (G_OBJECT (spin_button), "update-policy");
2376     }
2377 }
2378 
2379 /**
2380  * gtk_spin_button_get_update_policy:
2381  * @spin_button: a #GtkSpinButton
2382  *
2383  * Gets the update behavior of a spin button.
2384  * See gtk_spin_button_set_update_policy().
2385  *
2386  * Returns: the current update policy
2387  */
2388 GtkSpinButtonUpdatePolicy
gtk_spin_button_get_update_policy(GtkSpinButton * spin_button)2389 gtk_spin_button_get_update_policy (GtkSpinButton *spin_button)
2390 {
2391   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), GTK_UPDATE_ALWAYS);
2392 
2393   return spin_button->priv->update_policy;
2394 }
2395 
2396 /**
2397  * gtk_spin_button_set_numeric:
2398  * @spin_button: a #GtkSpinButton
2399  * @numeric: flag indicating if only numeric entry is allowed
2400  *
2401  * Sets the flag that determines if non-numeric text can be typed
2402  * into the spin button.
2403  */
2404 void
gtk_spin_button_set_numeric(GtkSpinButton * spin_button,gboolean numeric)2405 gtk_spin_button_set_numeric (GtkSpinButton *spin_button,
2406                              gboolean       numeric)
2407 {
2408   GtkSpinButtonPrivate *priv;
2409 
2410   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2411 
2412   priv = spin_button->priv;
2413 
2414   numeric = numeric != FALSE;
2415 
2416   if (priv->numeric != numeric)
2417     {
2418        priv->numeric = numeric;
2419        g_object_notify (G_OBJECT (spin_button), "numeric");
2420     }
2421 }
2422 
2423 /**
2424  * gtk_spin_button_get_numeric:
2425  * @spin_button: a #GtkSpinButton
2426  *
2427  * Returns whether non-numeric text can be typed into the spin button.
2428  * See gtk_spin_button_set_numeric().
2429  *
2430  * Returns: %TRUE if only numeric text can be entered
2431  */
2432 gboolean
gtk_spin_button_get_numeric(GtkSpinButton * spin_button)2433 gtk_spin_button_get_numeric (GtkSpinButton *spin_button)
2434 {
2435   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2436 
2437   return spin_button->priv->numeric;
2438 }
2439 
2440 /**
2441  * gtk_spin_button_set_wrap:
2442  * @spin_button: a #GtkSpinButton
2443  * @wrap: a flag indicating if wrapping behavior is performed
2444  *
2445  * Sets the flag that determines if a spin button value wraps
2446  * around to the opposite limit when the upper or lower limit
2447  * of the range is exceeded.
2448  */
2449 void
gtk_spin_button_set_wrap(GtkSpinButton * spin_button,gboolean wrap)2450 gtk_spin_button_set_wrap (GtkSpinButton  *spin_button,
2451                           gboolean        wrap)
2452 {
2453   GtkSpinButtonPrivate *priv;
2454 
2455   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2456 
2457   priv = spin_button->priv;
2458 
2459   wrap = wrap != FALSE;
2460 
2461   if (priv->wrap != wrap)
2462     {
2463       priv->wrap = wrap;
2464       g_object_notify (G_OBJECT (spin_button), "wrap");
2465 
2466       update_node_state (spin_button);
2467     }
2468 }
2469 
2470 /**
2471  * gtk_spin_button_get_wrap:
2472  * @spin_button: a #GtkSpinButton
2473  *
2474  * Returns whether the spin button’s value wraps around to the
2475  * opposite limit when the upper or lower limit of the range is
2476  * exceeded. See gtk_spin_button_set_wrap().
2477  *
2478  * Returns: %TRUE if the spin button wraps around
2479  */
2480 gboolean
gtk_spin_button_get_wrap(GtkSpinButton * spin_button)2481 gtk_spin_button_get_wrap (GtkSpinButton *spin_button)
2482 {
2483   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2484 
2485   return spin_button->priv->wrap;
2486 }
2487 
2488 /**
2489  * gtk_spin_button_set_snap_to_ticks:
2490  * @spin_button: a #GtkSpinButton
2491  * @snap_to_ticks: a flag indicating if invalid values should be corrected
2492  *
2493  * Sets the policy as to whether values are corrected to the
2494  * nearest step increment when a spin button is activated after
2495  * providing an invalid value.
2496  */
2497 void
gtk_spin_button_set_snap_to_ticks(GtkSpinButton * spin_button,gboolean snap_to_ticks)2498 gtk_spin_button_set_snap_to_ticks (GtkSpinButton *spin_button,
2499                                    gboolean       snap_to_ticks)
2500 {
2501   GtkSpinButtonPrivate *priv;
2502   guint new_val;
2503 
2504   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2505 
2506   priv = spin_button->priv;
2507 
2508   new_val = (snap_to_ticks != 0);
2509 
2510   if (new_val != priv->snap_to_ticks)
2511     {
2512       priv->snap_to_ticks = new_val;
2513       if (new_val && gtk_editable_get_editable (GTK_EDITABLE (spin_button)))
2514         gtk_spin_button_update (spin_button);
2515 
2516       g_object_notify (G_OBJECT (spin_button), "snap-to-ticks");
2517     }
2518 }
2519 
2520 /**
2521  * gtk_spin_button_get_snap_to_ticks:
2522  * @spin_button: a #GtkSpinButton
2523  *
2524  * Returns whether the values are corrected to the nearest step.
2525  * See gtk_spin_button_set_snap_to_ticks().
2526  *
2527  * Returns: %TRUE if values are snapped to the nearest step
2528  */
2529 gboolean
gtk_spin_button_get_snap_to_ticks(GtkSpinButton * spin_button)2530 gtk_spin_button_get_snap_to_ticks (GtkSpinButton *spin_button)
2531 {
2532   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2533 
2534   return spin_button->priv->snap_to_ticks;
2535 }
2536 
2537 /**
2538  * gtk_spin_button_spin:
2539  * @spin_button: a #GtkSpinButton
2540  * @direction: a #GtkSpinType indicating the direction to spin
2541  * @increment: step increment to apply in the specified direction
2542  *
2543  * Increment or decrement a spin button’s value in a specified
2544  * direction by a specified amount.
2545  */
2546 void
gtk_spin_button_spin(GtkSpinButton * spin_button,GtkSpinType direction,gdouble increment)2547 gtk_spin_button_spin (GtkSpinButton *spin_button,
2548                       GtkSpinType    direction,
2549                       gdouble        increment)
2550 {
2551   GtkSpinButtonPrivate *priv;
2552   GtkAdjustment *adjustment;
2553   gdouble diff;
2554 
2555   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2556 
2557   priv = spin_button->priv;
2558 
2559   adjustment = priv->adjustment;
2560 
2561   /* for compatibility with the 1.0.x version of this function */
2562   if (increment != 0 && increment != gtk_adjustment_get_step_increment (adjustment) &&
2563       (direction == GTK_SPIN_STEP_FORWARD ||
2564        direction == GTK_SPIN_STEP_BACKWARD))
2565     {
2566       if (direction == GTK_SPIN_STEP_BACKWARD && increment > 0)
2567         increment = -increment;
2568       direction = GTK_SPIN_USER_DEFINED;
2569     }
2570 
2571   switch (direction)
2572     {
2573     case GTK_SPIN_STEP_FORWARD:
2574 
2575       gtk_spin_button_real_spin (spin_button, gtk_adjustment_get_step_increment (adjustment));
2576       break;
2577 
2578     case GTK_SPIN_STEP_BACKWARD:
2579 
2580       gtk_spin_button_real_spin (spin_button, -gtk_adjustment_get_step_increment (adjustment));
2581       break;
2582 
2583     case GTK_SPIN_PAGE_FORWARD:
2584 
2585       gtk_spin_button_real_spin (spin_button, gtk_adjustment_get_page_increment (adjustment));
2586       break;
2587 
2588     case GTK_SPIN_PAGE_BACKWARD:
2589 
2590       gtk_spin_button_real_spin (spin_button, -gtk_adjustment_get_page_increment (adjustment));
2591       break;
2592 
2593     case GTK_SPIN_HOME:
2594 
2595       diff = gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_lower (adjustment);
2596       if (diff > EPSILON)
2597         gtk_spin_button_real_spin (spin_button, -diff);
2598       break;
2599 
2600     case GTK_SPIN_END:
2601 
2602       diff = gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_value (adjustment);
2603       if (diff > EPSILON)
2604         gtk_spin_button_real_spin (spin_button, diff);
2605       break;
2606 
2607     case GTK_SPIN_USER_DEFINED:
2608 
2609       if (increment != 0)
2610         gtk_spin_button_real_spin (spin_button, increment);
2611       break;
2612 
2613     default:
2614       break;
2615     }
2616 }
2617 
2618 /**
2619  * gtk_spin_button_update:
2620  * @spin_button: a #GtkSpinButton
2621  *
2622  * Manually force an update of the spin button.
2623  */
2624 void
gtk_spin_button_update(GtkSpinButton * spin_button)2625 gtk_spin_button_update (GtkSpinButton *spin_button)
2626 {
2627   GtkSpinButtonPrivate *priv;
2628   gdouble val;
2629   gint error = 0;
2630   gint return_val;
2631 
2632   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2633 
2634   priv = spin_button->priv;
2635 
2636   return_val = FALSE;
2637   g_signal_emit (spin_button, spinbutton_signals[INPUT], 0, &val, &return_val);
2638   if (return_val == FALSE)
2639     {
2640       return_val = gtk_spin_button_default_input (spin_button, &val);
2641       error = (return_val == GTK_INPUT_ERROR);
2642     }
2643   else if (return_val == GTK_INPUT_ERROR)
2644     error = 1;
2645 
2646   gtk_widget_queue_draw (GTK_WIDGET (spin_button));
2647 
2648   if (priv->update_policy == GTK_UPDATE_ALWAYS)
2649     {
2650       if (val < gtk_adjustment_get_lower (priv->adjustment))
2651         val = gtk_adjustment_get_lower (priv->adjustment);
2652       else if (val > gtk_adjustment_get_upper (priv->adjustment))
2653         val = gtk_adjustment_get_upper (priv->adjustment);
2654     }
2655   else if ((priv->update_policy == GTK_UPDATE_IF_VALID) &&
2656            (error ||
2657             val < gtk_adjustment_get_lower (priv->adjustment) ||
2658             val > gtk_adjustment_get_upper (priv->adjustment)))
2659     {
2660       gtk_spin_button_value_changed (priv->adjustment, spin_button);
2661       return;
2662     }
2663 
2664   if (priv->snap_to_ticks)
2665     gtk_spin_button_snap (spin_button, val);
2666   else
2667     gtk_spin_button_set_value (spin_button, val);
2668 }
2669 
2670 void
_gtk_spin_button_get_panels(GtkSpinButton * spin_button,GdkWindow ** down_panel,GdkWindow ** up_panel)2671 _gtk_spin_button_get_panels (GtkSpinButton  *spin_button,
2672                              GdkWindow     **down_panel,
2673                              GdkWindow     **up_panel)
2674 {
2675   if (down_panel != NULL)
2676     *down_panel = spin_button->priv->down_panel;
2677 
2678   if (up_panel != NULL)
2679     *up_panel = spin_button->priv->up_panel;
2680 }
2681 
2682 static void
gtk_spin_button_direction_changed(GtkWidget * widget,GtkTextDirection previous_dir)2683 gtk_spin_button_direction_changed (GtkWidget        *widget,
2684                                    GtkTextDirection  previous_dir)
2685 {
2686   update_node_ordering (GTK_SPIN_BUTTON (widget));
2687 
2688   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->direction_changed (widget, previous_dir);
2689 }
2690