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