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