]> Pileus Git - ~andy/gtk/blob - gtk/gtkspinbutton.c
entry: don't consider inner-border for allocation and layout
[~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                                           GTK_ICON_LOOKUP_USE_BUILTIN);
631
632   if (icon_info != NULL)
633     {
634       pix = gtk_icon_info_load_symbolic_for_context (icon_info, context, 
635                                                      NULL, NULL);
636       gtk_icon_info_free (icon_info);
637     }
638   else
639     {
640       GtkIconSet *icon_set;
641
642       icon_set = gtk_style_context_lookup_icon_set (context, GTK_STOCK_MISSING_IMAGE);
643       pix = gtk_icon_set_render_icon_pixbuf (icon_set, context, GTK_ICON_SIZE_MENU);
644
645       g_warning ("Unable to fetch icon %s from the icon theme", icon_name);
646     }
647
648   return pix;
649 }
650
651 static void
652 gtk_spin_button_init (GtkSpinButton *spin_button)
653 {
654   GtkSpinButtonPrivate *priv;
655   GtkStyleContext *context;
656
657   spin_button->priv = G_TYPE_INSTANCE_GET_PRIVATE (spin_button,
658                                                    GTK_TYPE_SPIN_BUTTON,
659                                                    GtkSpinButtonPrivate);
660   priv = spin_button->priv;
661
662   priv->adjustment = NULL;
663   priv->down_panel = NULL;
664   priv->up_panel = NULL;
665   priv->timer = 0;
666   priv->climb_rate = 0.0;
667   priv->timer_step = 0.0;
668   priv->update_policy = GTK_UPDATE_ALWAYS;
669   priv->in_child = NULL;
670   priv->click_child = NULL;
671   priv->button = 0;
672   priv->need_timer = FALSE;
673   priv->timer_calls = 0;
674   priv->digits = 0;
675   priv->numeric = FALSE;
676   priv->wrap = FALSE;
677   priv->snap_to_ticks = FALSE;
678
679   gtk_spin_button_set_adjustment (spin_button,
680                                   gtk_adjustment_new (0, 0, 0, 0, 0, 0));
681
682   context = gtk_widget_get_style_context (GTK_WIDGET (spin_button));
683   gtk_style_context_add_class (context, GTK_STYLE_CLASS_SPINBUTTON);
684 }
685
686 static void
687 gtk_spin_button_finalize (GObject *object)
688 {
689   gtk_spin_button_set_adjustment (GTK_SPIN_BUTTON (object), NULL);
690
691   G_OBJECT_CLASS (gtk_spin_button_parent_class)->finalize (object);
692 }
693
694 static void
695 gtk_spin_button_destroy (GtkWidget *widget)
696 {
697   gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));
698
699   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->destroy (widget);
700 }
701
702 static void
703 gtk_spin_button_map (GtkWidget *widget)
704 {
705   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
706   GtkSpinButtonPrivate *priv = spin_button->priv;
707
708   if (gtk_widget_get_realized (widget) && !gtk_widget_get_mapped (widget))
709     {
710       GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->map (widget);
711       gdk_window_show (priv->down_panel);
712       gdk_window_show (priv->up_panel);
713     }
714 }
715
716 static void
717 gtk_spin_button_unmap (GtkWidget *widget)
718 {
719   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
720   GtkSpinButtonPrivate *priv = spin_button->priv;
721
722   if (gtk_widget_get_mapped (widget))
723     {
724       gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));
725
726       gdk_window_hide (priv->down_panel);
727       gdk_window_hide (priv->up_panel);
728       GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unmap (widget);
729     }
730 }
731
732 static void
733 gtk_spin_button_panel_nthchildize_context (GtkSpinButton *spin_button,
734                                            GtkStyleContext *context,
735                                            GdkWindow *panel)
736 {
737   GtkWidget *widget = GTK_WIDGET (spin_button);
738   GtkWidgetPath *path, *siblings_path;
739   GtkTextDirection direction;
740   gint up_pos, down_pos;
741
742   /* We are a subclass of GtkEntry, which is not a GtkContainer, so we
743    * have to emulate what gtk_container_get_path_for_child() would do
744    * for the button panels
745    */
746   path = gtk_widget_path_copy (gtk_widget_get_path (widget));
747   direction = gtk_widget_get_direction (widget);
748   siblings_path = gtk_widget_path_new ();
749
750   /* flip children order for RTL */
751   if (direction == GTK_TEXT_DIR_RTL)
752     {
753       up_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);  
754       down_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
755       gtk_widget_path_append_type (siblings_path, GTK_TYPE_ENTRY);
756     }
757   else
758     {
759       gtk_widget_path_append_type (siblings_path, GTK_TYPE_ENTRY);
760       down_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);  
761       up_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
762     }
763
764   gtk_widget_path_iter_add_class (siblings_path, up_pos, GTK_STYLE_CLASS_BUTTON);
765   gtk_widget_path_iter_add_class (siblings_path, down_pos, GTK_STYLE_CLASS_BUTTON);
766
767   /* this allows compatibility for themes that use .spinbutton.button */
768   gtk_widget_path_iter_add_class (siblings_path, up_pos, GTK_STYLE_CLASS_SPINBUTTON);
769   gtk_widget_path_iter_add_class (siblings_path, down_pos, GTK_STYLE_CLASS_SPINBUTTON);
770
771   if (panel == spin_button->priv->down_panel)
772     {
773       gtk_widget_path_append_with_siblings (path, siblings_path, up_pos);
774       gtk_widget_path_append_with_siblings (path, siblings_path, down_pos);
775     }
776   else
777     {
778       gtk_widget_path_append_with_siblings (path, siblings_path, down_pos);
779       gtk_widget_path_append_with_siblings (path, siblings_path, up_pos);
780     }
781
782   gtk_style_context_set_path (context, path);
783   gtk_widget_path_unref (path);
784   gtk_widget_path_unref (siblings_path);
785 }
786
787 static gboolean
788 gtk_spin_button_panel_at_limit (GtkSpinButton *spin_button,
789                                 GdkWindow     *panel)
790 {
791   GtkSpinButtonPrivate *priv = spin_button->priv;
792   GdkWindow *effective_panel;
793
794   if (priv->wrap)
795     return FALSE;
796
797   if (gtk_adjustment_get_step_increment (priv->adjustment) > 0)
798     effective_panel = panel;
799   else
800     effective_panel = panel == priv->up_panel ? priv->down_panel : priv->up_panel;
801
802   if (effective_panel == priv->up_panel &&
803       (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_value (priv->adjustment) <= EPSILON))
804     return TRUE;
805
806   if (effective_panel == priv->down_panel &&
807       (gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment) <= EPSILON))
808     return TRUE;
809
810   return FALSE;
811 }
812
813 static GtkStateFlags
814 gtk_spin_button_panel_get_state (GtkSpinButton *spin_button,
815                                  GdkWindow *panel)
816 {
817   GtkStateFlags state;
818   GtkSpinButtonPrivate *priv = spin_button->priv;
819
820   state = gtk_widget_get_state_flags (GTK_WIDGET (spin_button));
821
822   state &= ~(GTK_STATE_FLAG_ACTIVE | GTK_STATE_FLAG_PRELIGHT);
823
824   if ((state & GTK_STATE_FLAG_INSENSITIVE) ||
825       gtk_spin_button_panel_at_limit (spin_button, panel) ||
826       !gtk_editable_get_editable (GTK_EDITABLE (spin_button)))
827     {
828       state |= GTK_STATE_FLAG_INSENSITIVE;
829     }
830   else
831     {
832       if (priv->click_child == panel)
833         state |= GTK_STATE_ACTIVE;
834       else if (priv->in_child == panel &&
835                priv->click_child == NULL)
836         state |= GTK_STATE_FLAG_PRELIGHT;
837     }
838
839   return state;
840 }
841
842 static GtkStyleContext *
843 gtk_spin_button_panel_get_context (GtkSpinButton *spin_button,
844                                    GdkWindow *panel)
845 {
846   GtkStyleContext *context;
847
848   context = gtk_style_context_new ();
849   gtk_spin_button_panel_nthchildize_context (spin_button, context, panel);
850
851   return context;
852 }
853
854 static gint
855 gtk_spin_button_panel_get_width (GtkSpinButton *spin_button,
856                                  GdkWindow *panel)
857 {
858   GtkBorder button_padding, button_border;
859   GtkStyleContext *context;
860   GtkStateFlags state;
861
862   context = gtk_spin_button_panel_get_context (spin_button, panel);
863   state = gtk_spin_button_panel_get_state (spin_button, panel);
864
865   gtk_style_context_get_padding (context, state, &button_padding);
866   gtk_style_context_get_border (context, state, &button_border);
867
868   g_object_unref (context);
869
870   return get_icon_size () + button_padding.left + button_padding.right +
871     button_border.left + button_border.right;
872 }
873
874 static void
875 gtk_spin_button_panel_get_allocations (GtkSpinButton *spin_button,
876                                        GtkAllocation *down_allocation_out,
877                                        GtkAllocation *up_allocation_out)
878 {
879   GtkWidget *widget = GTK_WIDGET (spin_button);
880   GtkSpinButtonPrivate *priv = spin_button->priv;
881   GtkAllocation spin_allocation, down_allocation, up_allocation, allocation;
882   GtkRequisition requisition;
883   gint req_height;
884   gint up_panel_width, down_panel_width;
885   GtkStyleContext *context;
886   GtkBorder space;
887
888   gtk_widget_get_allocation (widget, &spin_allocation);
889   gtk_widget_get_preferred_size (widget, &requisition, NULL);
890
891   context = gtk_widget_get_style_context (GTK_WIDGET (spin_button));
892   gtk_style_context_get_border (context, GTK_STATE_NORMAL, &space);
893
894   req_height = requisition.height - gtk_widget_get_margin_top (widget) - gtk_widget_get_margin_bottom (widget);
895   down_panel_width = gtk_spin_button_panel_get_width (spin_button, priv->down_panel);
896   up_panel_width = gtk_spin_button_panel_get_width (spin_button, priv->up_panel);
897
898   /* both panels have the same size, and they're as big as the entry allocation,
899    * excluding margins
900    */
901   allocation.height = MIN (req_height, spin_allocation.height) - space.top - space.bottom;
902   allocation.y = spin_allocation.y + space.top + (spin_allocation.height - req_height) / 2;
903   down_allocation = up_allocation = allocation;
904
905   down_allocation.width = down_panel_width;
906   up_allocation.width = up_panel_width;
907
908   /* invert x axis allocation for RTL */
909   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
910     {
911       up_allocation.x = spin_allocation.x + space.left;
912       down_allocation.x = up_allocation.x + up_panel_width;
913     }
914   else
915     {
916       up_allocation.x = spin_allocation.x + spin_allocation.width - up_panel_width - space.right;
917       down_allocation.x = up_allocation.x - down_panel_width;
918     }
919
920   if (down_allocation_out)
921     *down_allocation_out = down_allocation;
922   if (up_allocation_out)
923     *up_allocation_out = up_allocation;
924 }
925
926 static void
927 gtk_spin_button_panel_draw (GtkSpinButton   *spin_button,
928                             cairo_t         *cr,
929                             GdkWindow       *panel)
930 {
931   GtkSpinButtonPrivate *priv = spin_button->priv;
932   GtkStyleContext *context;
933   GtkStateFlags state;
934   GtkWidget *widget;
935   gdouble width, height, x, y;
936   GdkPixbuf *pix;
937
938   widget = GTK_WIDGET (spin_button);
939
940   cairo_save (cr);
941   gtk_cairo_transform_to_window (cr, widget, panel);
942
943   context = gtk_spin_button_panel_get_context (spin_button, panel);
944   state = gtk_spin_button_panel_get_state (spin_button, panel);
945   gtk_style_context_set_state (context, state);
946
947   height = gdk_window_get_height (panel);
948   width = gdk_window_get_width (panel);
949
950   if (panel == priv->down_panel)
951     pix = create_one_pixbuf (context, "list-remove-symbolic");
952   else
953     pix = create_one_pixbuf (context, "list-add-symbolic");
954
955   gtk_render_background (context, cr,
956                          0, 0, width, height);
957   gtk_render_frame (context, cr,
958                     0, 0, width, height);
959
960   x = floor ((width - gdk_pixbuf_get_width (pix)) / 2.0);
961   y = floor ((height - gdk_pixbuf_get_height (pix)) / 2.0);
962
963   gtk_render_icon (context, cr, pix,
964                    x, y);
965   cairo_restore (cr);
966
967   g_object_unref (pix);
968   g_object_unref (context);
969 }
970
971 static void
972 gtk_spin_button_realize (GtkWidget *widget)
973 {
974   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
975   GtkSpinButtonPrivate *priv = spin_button->priv;
976   GtkAllocation down_allocation, up_allocation;
977   GdkWindowAttr attributes;
978   gint attributes_mask;
979   gboolean return_val;
980
981   gtk_widget_set_events (widget, gtk_widget_get_events (widget) |
982                          GDK_KEY_RELEASE_MASK);
983   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->realize (widget);
984
985   attributes.window_type = GDK_WINDOW_CHILD;
986   attributes.wclass = GDK_INPUT_ONLY;
987   attributes.visual = gtk_widget_get_visual (widget);
988   attributes.event_mask = gtk_widget_get_events (widget);
989   attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK
990     | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK
991     | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;
992
993   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
994
995   gtk_spin_button_panel_get_allocations (spin_button, &down_allocation, &up_allocation);
996
997   /* create the left panel window */
998   attributes.x = down_allocation.x;
999   attributes.y = down_allocation.y;
1000   attributes.width = down_allocation.width;
1001   attributes.height = down_allocation.height;
1002
1003   priv->down_panel = gdk_window_new (gtk_widget_get_window (widget),
1004                                      &attributes, attributes_mask);
1005   gdk_window_set_user_data (priv->down_panel, widget);
1006
1007   /* create the right panel window */
1008   attributes.x = up_allocation.x;
1009   attributes.y = up_allocation.y;
1010   attributes.width = up_allocation.width;
1011   attributes.height = up_allocation.height;
1012
1013   priv->up_panel = gdk_window_new (gtk_widget_get_window (widget),
1014                                       &attributes, attributes_mask);
1015   gdk_window_set_user_data (priv->up_panel, widget);
1016
1017   return_val = FALSE;
1018   g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
1019   if (return_val == FALSE)
1020     gtk_spin_button_default_output (spin_button);
1021
1022   gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1023 }
1024
1025 static void
1026 gtk_spin_button_unrealize (GtkWidget *widget)
1027 {
1028   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1029   GtkSpinButtonPrivate *priv = spin->priv;
1030
1031   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unrealize (widget);
1032
1033   if (priv->down_panel)
1034     {
1035       gdk_window_set_user_data (priv->down_panel, NULL);
1036       gdk_window_destroy (priv->down_panel);
1037       priv->down_panel = NULL;
1038     }
1039
1040   if (priv->up_panel)
1041     {
1042       gdk_window_set_user_data (priv->up_panel, NULL);
1043       gdk_window_destroy (priv->up_panel);
1044       priv->up_panel = NULL;
1045     }
1046 }
1047
1048 static int
1049 compute_double_length (double val, int digits)
1050 {
1051   int a;
1052   int extra;
1053
1054   a = 1;
1055   if (fabs (val) > 1.0)
1056     a = floor (log10 (fabs (val))) + 1;
1057
1058   extra = 0;
1059
1060   /* The dot: */
1061   if (digits > 0)
1062     extra++;
1063
1064   /* The sign: */
1065   if (val < 0)
1066     extra++;
1067
1068   return a + digits + extra;
1069 }
1070
1071 static void
1072 gtk_spin_button_get_preferred_width (GtkWidget *widget,
1073                                      gint      *minimum,
1074                                      gint      *natural)
1075 {
1076   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
1077   GtkSpinButtonPrivate *priv = spin_button->priv;
1078   GtkEntry *entry = GTK_ENTRY (widget);
1079   GtkStyleContext *style_context;
1080   gint up_panel_width, down_panel_width;
1081
1082   style_context = gtk_widget_get_style_context (widget);
1083   up_panel_width = gtk_spin_button_panel_get_width (spin_button, priv->up_panel);
1084   down_panel_width = gtk_spin_button_panel_get_width (spin_button, priv->down_panel);
1085
1086   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->get_preferred_width (widget, minimum, natural);
1087
1088   if (gtk_entry_get_width_chars (entry) < 0)
1089     {
1090       PangoContext *context;
1091       const PangoFontDescription *font_desc;
1092       PangoFontMetrics *metrics;
1093       gint width;
1094       gint w;
1095       gint string_len;
1096       gint max_string_len;
1097       gint digit_width;
1098       gboolean interior_focus;
1099       gint focus_width;
1100       GtkBorder borders;
1101       GtkBorder inner_border;
1102
1103       gtk_style_context_get_style (style_context,
1104                                    "interior-focus", &interior_focus,
1105                                    "focus-line-width", &focus_width,
1106                                    NULL);
1107       font_desc = gtk_style_context_get_font (style_context, GTK_STATE_FLAG_NORMAL);
1108
1109       context = gtk_widget_get_pango_context (widget);
1110       metrics = pango_context_get_metrics (context, font_desc,
1111                                            pango_context_get_language (context));
1112
1113       digit_width = pango_font_metrics_get_approximate_digit_width (metrics);
1114       digit_width = PANGO_SCALE *
1115         ((digit_width + PANGO_SCALE - 1) / PANGO_SCALE);
1116
1117       pango_font_metrics_unref (metrics);
1118
1119       /* Get max of MIN_SPIN_BUTTON_WIDTH, size of upper, size of lower */
1120       width = MIN_SPIN_BUTTON_WIDTH;
1121       max_string_len = MAX (10, compute_double_length (1e9 * gtk_adjustment_get_step_increment (priv->adjustment),
1122                                                        priv->digits));
1123
1124       string_len = compute_double_length (gtk_adjustment_get_upper (priv->adjustment),
1125                                           priv->digits);
1126       w = PANGO_PIXELS (MIN (string_len, max_string_len) * digit_width);
1127       width = MAX (width, w);
1128       string_len = compute_double_length (gtk_adjustment_get_lower (priv->adjustment), priv->digits);
1129       w = PANGO_PIXELS (MIN (string_len, max_string_len) * digit_width);
1130       width = MAX (width, w);
1131
1132       _gtk_entry_get_borders (entry, &borders);
1133       width += borders.left + borders.right;
1134
1135       *minimum = width;
1136       *natural = width;
1137     }
1138
1139   *minimum += up_panel_width + down_panel_width;
1140   *natural += up_panel_width + down_panel_width;
1141 }
1142
1143 static void
1144 gtk_spin_button_size_allocate (GtkWidget     *widget,
1145                                GtkAllocation *allocation)
1146 {
1147   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1148   GtkSpinButtonPrivate *priv = spin->priv;
1149   GtkAllocation down_panel_allocation, up_panel_allocation;
1150
1151   gtk_widget_set_allocation (widget, allocation);
1152   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->size_allocate (widget, allocation);
1153
1154   gtk_spin_button_panel_get_allocations (spin, &down_panel_allocation, &up_panel_allocation);
1155
1156   if (gtk_widget_get_realized (widget))
1157     {
1158       gdk_window_move_resize (priv->down_panel,
1159                               down_panel_allocation.x,
1160                               down_panel_allocation.y,
1161                               down_panel_allocation.width,
1162                               down_panel_allocation.height);
1163
1164       gdk_window_move_resize (priv->up_panel,
1165                               up_panel_allocation.x,
1166                               up_panel_allocation.y,
1167                               up_panel_allocation.width,
1168                               up_panel_allocation.height);
1169     }
1170
1171   gtk_widget_queue_draw (GTK_WIDGET (spin));
1172 }
1173
1174 static gint
1175 gtk_spin_button_draw (GtkWidget      *widget,
1176                       cairo_t        *cr)
1177 {
1178   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1179   GtkSpinButtonPrivate *priv = spin->priv;
1180
1181   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->draw (widget, cr);
1182
1183   /* Draw the buttons */
1184   gtk_spin_button_panel_draw (spin, cr, priv->down_panel);
1185   gtk_spin_button_panel_draw (spin, cr, priv->up_panel);
1186
1187   return FALSE;
1188 }
1189
1190 static gint
1191 gtk_spin_button_enter_notify (GtkWidget        *widget,
1192                               GdkEventCrossing *event)
1193 {
1194   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1195   GtkSpinButtonPrivate *priv = spin->priv;
1196
1197   if ((event->window == priv->down_panel) ||
1198       (event->window == priv->up_panel))
1199     {
1200       priv->in_child = event->window;
1201       gtk_widget_queue_draw (GTK_WIDGET (spin));
1202     }
1203
1204   return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->enter_notify_event (widget, event);
1205 }
1206
1207 static gint
1208 gtk_spin_button_leave_notify (GtkWidget        *widget,
1209                               GdkEventCrossing *event)
1210 {
1211   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1212   GtkSpinButtonPrivate *priv = spin->priv;
1213
1214   if (priv->in_child != NULL)
1215     {
1216       priv->in_child = NULL;
1217       gtk_widget_queue_draw (GTK_WIDGET (spin));
1218     }
1219
1220   return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->leave_notify_event (widget, event);
1221 }
1222
1223 static gint
1224 gtk_spin_button_focus_out (GtkWidget     *widget,
1225                            GdkEventFocus *event)
1226 {
1227   if (gtk_editable_get_editable (GTK_EDITABLE (widget)))
1228     gtk_spin_button_update (GTK_SPIN_BUTTON (widget));
1229
1230   return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->focus_out_event (widget, event);
1231 }
1232
1233 static void
1234 gtk_spin_button_grab_notify (GtkWidget *widget,
1235                              gboolean   was_grabbed)
1236 {
1237   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1238
1239   if (!was_grabbed)
1240     {
1241       if (gtk_spin_button_stop_spinning (spin))
1242         gtk_widget_queue_draw (GTK_WIDGET (spin));
1243     }
1244 }
1245
1246 static void
1247 gtk_spin_button_state_flags_changed (GtkWidget     *widget,
1248                                      GtkStateFlags  previous_state)
1249 {
1250   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1251
1252   if (!gtk_widget_is_sensitive (widget))
1253     {
1254       if (gtk_spin_button_stop_spinning (spin))
1255         gtk_widget_queue_draw (GTK_WIDGET (spin));
1256     }
1257 }
1258
1259 static gint
1260 gtk_spin_button_scroll (GtkWidget      *widget,
1261                         GdkEventScroll *event)
1262 {
1263   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1264   GtkSpinButtonPrivate *priv = spin->priv;
1265
1266   if (event->direction == GDK_SCROLL_UP)
1267     {
1268       if (!gtk_widget_has_focus (widget))
1269         gtk_widget_grab_focus (widget);
1270       gtk_spin_button_real_spin (spin, gtk_adjustment_get_step_increment (priv->adjustment));
1271     }
1272   else if (event->direction == GDK_SCROLL_DOWN)
1273     {
1274       if (!gtk_widget_has_focus (widget))
1275         gtk_widget_grab_focus (widget);
1276       gtk_spin_button_real_spin (spin, -gtk_adjustment_get_step_increment (priv->adjustment));
1277     }
1278   else
1279     return FALSE;
1280
1281   return TRUE;
1282 }
1283
1284 static gboolean
1285 gtk_spin_button_stop_spinning (GtkSpinButton *spin)
1286 {
1287   GtkSpinButtonPrivate *priv = spin->priv;
1288   gboolean did_spin = FALSE;
1289
1290   if (priv->timer)
1291     {
1292       g_source_remove (priv->timer);
1293       priv->timer = 0;
1294       priv->need_timer = FALSE;
1295
1296       did_spin = TRUE;
1297     }
1298
1299   priv->button = 0;
1300   priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
1301   priv->timer_calls = 0;
1302
1303   priv->click_child = NULL;
1304
1305   return did_spin;
1306 }
1307
1308 static void
1309 start_spinning (GtkSpinButton *spin,
1310                 GdkWindow     *click_child,
1311                 gdouble        step)
1312 {
1313   GtkSpinButtonPrivate *priv;
1314
1315   priv = spin->priv;
1316
1317   priv->click_child = click_child;
1318
1319   if (!priv->timer)
1320     {
1321       GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (spin));
1322       guint        timeout;
1323
1324       g_object_get (settings, "gtk-timeout-initial", &timeout, NULL);
1325
1326       priv->timer_step = step;
1327       priv->need_timer = TRUE;
1328       priv->timer = gdk_threads_add_timeout (timeout,
1329                                    (GSourceFunc) gtk_spin_button_timer,
1330                                    (gpointer) spin);
1331     }
1332   gtk_spin_button_real_spin (spin, click_child == priv->up_panel ? step : -step);
1333
1334   gtk_widget_queue_draw (GTK_WIDGET (spin));
1335 }
1336
1337 static gint
1338 gtk_spin_button_button_press (GtkWidget      *widget,
1339                               GdkEventButton *event)
1340 {
1341   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1342   GtkSpinButtonPrivate *priv = spin->priv;
1343
1344   if (!priv->button)
1345     {
1346       if ((event->window == priv->down_panel) ||
1347           (event->window == priv->up_panel))
1348         {
1349           if (!gtk_widget_has_focus (widget))
1350             gtk_widget_grab_focus (widget);
1351           priv->button = event->button;
1352
1353           if (gtk_editable_get_editable (GTK_EDITABLE (widget))) {
1354             gtk_spin_button_update (spin);
1355
1356             if (event->button == GDK_BUTTON_PRIMARY)
1357               start_spinning (spin, event->window, gtk_adjustment_get_step_increment (priv->adjustment));
1358             else if (event->button == GDK_BUTTON_MIDDLE)
1359               start_spinning (spin, event->window, gtk_adjustment_get_page_increment (priv->adjustment));
1360             else
1361               priv->click_child = event->window;
1362           } else
1363             gtk_widget_error_bell (widget);
1364
1365           return TRUE;
1366         }
1367       else
1368         return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->button_press_event (widget, event);
1369     }
1370   return FALSE;
1371 }
1372
1373 static gint
1374 gtk_spin_button_button_release (GtkWidget      *widget,
1375                                 GdkEventButton *event)
1376 {
1377   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1378   GtkSpinButtonPrivate *priv = spin->priv;
1379
1380   if (event->button == priv->button)
1381     {
1382       GdkWindow *click_child = priv->click_child;
1383
1384       gtk_spin_button_stop_spinning (spin);
1385
1386       if (event->button == GDK_BUTTON_SECONDARY)
1387         {
1388           gdouble diff;
1389
1390           if (event->window == priv->down_panel &&
1391               click_child == event->window)
1392             {
1393               diff = gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment);
1394               if (diff > EPSILON)
1395                 gtk_spin_button_real_spin (spin, -diff);
1396             }
1397           else if (event->window == priv->up_panel &&
1398                    click_child == event->window)
1399             {
1400               diff = gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_value (priv->adjustment);
1401               if (diff > EPSILON)
1402                 gtk_spin_button_real_spin (spin, diff);
1403             }
1404         }
1405       gtk_widget_queue_draw (GTK_WIDGET (spin));
1406
1407       return TRUE;
1408     }
1409   else
1410     return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->button_release_event (widget, event);
1411 }
1412
1413 static gint
1414 gtk_spin_button_motion_notify (GtkWidget      *widget,
1415                                GdkEventMotion *event)
1416 {
1417   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1418   GtkSpinButtonPrivate *priv = spin->priv;
1419
1420   if (priv->button)
1421     return FALSE;
1422
1423   if (event->window == priv->down_panel ||
1424       event->window == priv->up_panel)
1425     {
1426       gdk_event_request_motions (event);
1427
1428       priv->in_child = event->window;
1429       gtk_widget_queue_draw (widget);
1430
1431       return FALSE;
1432     }
1433
1434   return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->motion_notify_event (widget, event);
1435 }
1436
1437 static gint
1438 gtk_spin_button_timer (GtkSpinButton *spin_button)
1439 {
1440   GtkSpinButtonPrivate *priv = spin_button->priv;
1441   gboolean retval = FALSE;
1442
1443   if (priv->timer)
1444     {
1445       if (priv->click_child == priv->up_panel)
1446         gtk_spin_button_real_spin (spin_button, priv->timer_step);
1447       else
1448         gtk_spin_button_real_spin (spin_button, -priv->timer_step);
1449
1450       if (priv->need_timer)
1451         {
1452           GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (spin_button));
1453           guint        timeout;
1454
1455           g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL);
1456
1457           priv->need_timer = FALSE;
1458           priv->timer = gdk_threads_add_timeout (timeout,
1459                                               (GSourceFunc) gtk_spin_button_timer,
1460                                               (gpointer) spin_button);
1461         }
1462       else
1463         {
1464           if (priv->climb_rate > 0.0 && priv->timer_step
1465               < gtk_adjustment_get_page_increment (priv->adjustment))
1466             {
1467               if (priv->timer_calls < MAX_TIMER_CALLS)
1468                 priv->timer_calls++;
1469               else
1470                 {
1471                   priv->timer_calls = 0;
1472                   priv->timer_step += priv->climb_rate;
1473                 }
1474             }
1475           retval = TRUE;
1476         }
1477     }
1478
1479   return retval;
1480 }
1481
1482 static void
1483 gtk_spin_button_value_changed (GtkAdjustment *adjustment,
1484                                GtkSpinButton *spin_button)
1485 {
1486   gboolean return_val;
1487
1488   g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
1489
1490   return_val = FALSE;
1491   g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
1492   if (return_val == FALSE)
1493     gtk_spin_button_default_output (spin_button);
1494
1495   g_signal_emit (spin_button, spinbutton_signals[VALUE_CHANGED], 0);
1496
1497   gtk_widget_queue_draw (GTK_WIDGET (spin_button));
1498
1499   g_object_notify (G_OBJECT (spin_button), "value");
1500 }
1501
1502 static void
1503 gtk_spin_button_real_change_value (GtkSpinButton *spin,
1504                                    GtkScrollType  scroll)
1505 {
1506   GtkSpinButtonPrivate *priv = spin->priv;
1507   gdouble old_value;
1508
1509   if (!gtk_editable_get_editable (GTK_EDITABLE (spin)))
1510     {
1511       gtk_widget_error_bell (GTK_WIDGET (spin));
1512       return;
1513     }
1514
1515   /* When the key binding is activated, there may be an outstanding
1516    * value, so we first have to commit what is currently written in
1517    * the spin buttons text entry. See #106574
1518    */
1519   gtk_spin_button_update (spin);
1520
1521   old_value = gtk_adjustment_get_value (priv->adjustment);
1522
1523   switch (scroll)
1524     {
1525     case GTK_SCROLL_STEP_BACKWARD:
1526     case GTK_SCROLL_STEP_DOWN:
1527     case GTK_SCROLL_STEP_LEFT:
1528       gtk_spin_button_real_spin (spin, -priv->timer_step);
1529
1530       if (priv->climb_rate > 0.0 && priv->timer_step
1531           < gtk_adjustment_get_page_increment (priv->adjustment))
1532         {
1533           if (priv->timer_calls < MAX_TIMER_CALLS)
1534             priv->timer_calls++;
1535           else
1536             {
1537               priv->timer_calls = 0;
1538               priv->timer_step += priv->climb_rate;
1539             }
1540         }
1541       break;
1542
1543     case GTK_SCROLL_STEP_FORWARD:
1544     case GTK_SCROLL_STEP_UP:
1545     case GTK_SCROLL_STEP_RIGHT:
1546       gtk_spin_button_real_spin (spin, priv->timer_step);
1547
1548       if (priv->climb_rate > 0.0 && priv->timer_step
1549           < gtk_adjustment_get_page_increment (priv->adjustment))
1550         {
1551           if (priv->timer_calls < MAX_TIMER_CALLS)
1552             priv->timer_calls++;
1553           else
1554             {
1555               priv->timer_calls = 0;
1556               priv->timer_step += priv->climb_rate;
1557             }
1558         }
1559       break;
1560
1561     case GTK_SCROLL_PAGE_BACKWARD:
1562     case GTK_SCROLL_PAGE_DOWN:
1563     case GTK_SCROLL_PAGE_LEFT:
1564       gtk_spin_button_real_spin (spin, -gtk_adjustment_get_page_increment (priv->adjustment));
1565       break;
1566
1567     case GTK_SCROLL_PAGE_FORWARD:
1568     case GTK_SCROLL_PAGE_UP:
1569     case GTK_SCROLL_PAGE_RIGHT:
1570       gtk_spin_button_real_spin (spin, gtk_adjustment_get_page_increment (priv->adjustment));
1571       break;
1572
1573     case GTK_SCROLL_START:
1574       {
1575         gdouble diff = gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment);
1576         if (diff > EPSILON)
1577           gtk_spin_button_real_spin (spin, -diff);
1578         break;
1579       }
1580
1581     case GTK_SCROLL_END:
1582       {
1583         gdouble diff = gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_value (priv->adjustment);
1584         if (diff > EPSILON)
1585           gtk_spin_button_real_spin (spin, diff);
1586         break;
1587       }
1588
1589     default:
1590       g_warning ("Invalid scroll type %d for GtkSpinButton::change-value", scroll);
1591       break;
1592     }
1593
1594   gtk_spin_button_update (spin);
1595
1596   if (gtk_adjustment_get_value (priv->adjustment) == old_value)
1597     gtk_widget_error_bell (GTK_WIDGET (spin));
1598 }
1599
1600 static gint
1601 gtk_spin_button_key_release (GtkWidget   *widget,
1602                              GdkEventKey *event)
1603 {
1604   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1605   GtkSpinButtonPrivate *priv = spin->priv;
1606
1607   /* We only get a release at the end of a key repeat run, so reset the timer_step */
1608   priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
1609   priv->timer_calls = 0;
1610
1611   return TRUE;
1612 }
1613
1614 static void
1615 gtk_spin_button_snap (GtkSpinButton *spin_button,
1616                       gdouble        val)
1617 {
1618   GtkSpinButtonPrivate *priv = spin_button->priv;
1619   gdouble inc;
1620   gdouble tmp;
1621
1622   inc = gtk_adjustment_get_step_increment (priv->adjustment);
1623   if (inc == 0)
1624     return;
1625
1626   tmp = (val - gtk_adjustment_get_lower (priv->adjustment)) / inc;
1627   if (tmp - floor (tmp) < ceil (tmp) - tmp)
1628     val = gtk_adjustment_get_lower (priv->adjustment) + floor (tmp) * inc;
1629   else
1630     val = gtk_adjustment_get_lower (priv->adjustment) + ceil (tmp) * inc;
1631
1632   gtk_spin_button_set_value (spin_button, val);
1633 }
1634
1635 static void
1636 gtk_spin_button_activate (GtkEntry *entry)
1637 {
1638   if (gtk_editable_get_editable (GTK_EDITABLE (entry)))
1639     gtk_spin_button_update (GTK_SPIN_BUTTON (entry));
1640
1641   /* Chain up so that entry->activates_default is honored */
1642   GTK_ENTRY_CLASS (gtk_spin_button_parent_class)->activate (entry);
1643 }
1644
1645 static void
1646 gtk_spin_button_get_text_area_size (GtkEntry *entry,
1647                                     gint     *x,
1648                                     gint     *y,
1649                                     gint     *width,
1650                                     gint     *height)
1651 {
1652   GtkSpinButtonPrivate *priv = GTK_SPIN_BUTTON (entry)->priv;
1653   gint up_panel_width, down_panel_width;
1654
1655   up_panel_width = gtk_spin_button_panel_get_width (GTK_SPIN_BUTTON (entry), priv->up_panel);
1656   down_panel_width = gtk_spin_button_panel_get_width (GTK_SPIN_BUTTON (entry), priv->down_panel);
1657
1658   GTK_ENTRY_CLASS (gtk_spin_button_parent_class)->get_text_area_size (entry, x, y, width, height);
1659
1660   if (gtk_widget_get_direction (GTK_WIDGET (entry)) == GTK_TEXT_DIR_RTL)
1661     {
1662       if (x)
1663         *x += up_panel_width + down_panel_width;
1664     }
1665
1666   if (width)
1667     *width -= up_panel_width + down_panel_width;
1668 }
1669
1670 static void
1671 gtk_spin_button_insert_text (GtkEditable *editable,
1672                              const gchar *new_text,
1673                              gint         new_text_length,
1674                              gint        *position)
1675 {
1676   GtkEntry *entry = GTK_ENTRY (editable);
1677   GtkSpinButton *spin = GTK_SPIN_BUTTON (editable);
1678   GtkSpinButtonPrivate *priv = spin->priv;
1679   GtkEditableInterface *parent_editable_iface;
1680
1681   parent_editable_iface = g_type_interface_peek (gtk_spin_button_parent_class,
1682                                                  GTK_TYPE_EDITABLE);
1683
1684   if (priv->numeric)
1685     {
1686       struct lconv *lc;
1687       gboolean sign;
1688       gint dotpos = -1;
1689       gint i;
1690       guint32 pos_sign;
1691       guint32 neg_sign;
1692       gint entry_length;
1693       const gchar *entry_text;
1694
1695       entry_length = gtk_entry_get_text_length (entry);
1696       entry_text = gtk_entry_get_text (entry);
1697
1698       lc = localeconv ();
1699
1700       if (*(lc->negative_sign))
1701         neg_sign = *(lc->negative_sign);
1702       else
1703         neg_sign = '-';
1704
1705       if (*(lc->positive_sign))
1706         pos_sign = *(lc->positive_sign);
1707       else
1708         pos_sign = '+';
1709
1710 #ifdef G_OS_WIN32
1711       /* Workaround for bug caused by some Windows application messing
1712        * up the positive sign of the current locale, more specifically
1713        * HKEY_CURRENT_USER\Control Panel\International\sPositiveSign.
1714        * See bug #330743 and for instance
1715        * http://www.msnewsgroups.net/group/microsoft.public.dotnet.languages.csharp/topic36024.aspx
1716        *
1717        * I don't know if the positive sign always gets bogusly set to
1718        * a digit when the above Registry value is corrupted as
1719        * described. (In my test case, it got set to "8", and in the
1720        * bug report above it presumably was set ot "0".) Probably it
1721        * might get set to almost anything? So how to distinguish a
1722        * bogus value from some correct one for some locale? That is
1723        * probably hard, but at least we should filter out the
1724        * digits...
1725        */
1726       if (pos_sign >= '0' && pos_sign <= '9')
1727         pos_sign = '+';
1728 #endif
1729
1730       for (sign=0, i=0; i<entry_length; i++)
1731         if ((entry_text[i] == neg_sign) ||
1732             (entry_text[i] == pos_sign))
1733           {
1734             sign = 1;
1735             break;
1736           }
1737
1738       if (sign && !(*position))
1739         return;
1740
1741       for (dotpos=-1, i=0; i<entry_length; i++)
1742         if (entry_text[i] == *(lc->decimal_point))
1743           {
1744             dotpos = i;
1745             break;
1746           }
1747
1748       if (dotpos > -1 && *position > dotpos &&
1749           (gint)priv->digits - entry_length
1750             + dotpos - new_text_length + 1 < 0)
1751         return;
1752
1753       for (i = 0; i < new_text_length; i++)
1754         {
1755           if (new_text[i] == neg_sign || new_text[i] == pos_sign)
1756             {
1757               if (sign || (*position) || i)
1758                 return;
1759               sign = TRUE;
1760             }
1761           else if (new_text[i] == *(lc->decimal_point))
1762             {
1763               if (!priv->digits || dotpos > -1 ||
1764                   (new_text_length - 1 - i + entry_length
1765                     - *position > (gint)priv->digits))
1766                 return;
1767               dotpos = *position + i;
1768             }
1769           else if (new_text[i] < 0x30 || new_text[i] > 0x39)
1770             return;
1771         }
1772     }
1773
1774   parent_editable_iface->insert_text (editable, new_text,
1775                                       new_text_length, position);
1776 }
1777
1778 static void
1779 gtk_spin_button_real_spin (GtkSpinButton *spin_button,
1780                            gdouble        increment)
1781 {
1782   GtkSpinButtonPrivate *priv = spin_button->priv;
1783   GtkAdjustment *adjustment;
1784   gdouble new_value = 0.0;
1785   gboolean wrapped = FALSE;
1786
1787   adjustment = priv->adjustment;
1788
1789   new_value = gtk_adjustment_get_value (adjustment) + increment;
1790
1791   if (increment > 0)
1792     {
1793       if (priv->wrap)
1794         {
1795           if (fabs (gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_upper (adjustment)) < EPSILON)
1796             {
1797               new_value = gtk_adjustment_get_lower (adjustment);
1798               wrapped = TRUE;
1799             }
1800           else if (new_value > gtk_adjustment_get_upper (adjustment))
1801             new_value = gtk_adjustment_get_upper (adjustment);
1802         }
1803       else
1804         new_value = MIN (new_value, gtk_adjustment_get_upper (adjustment));
1805     }
1806   else if (increment < 0)
1807     {
1808       if (priv->wrap)
1809         {
1810           if (fabs (gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_lower (adjustment)) < EPSILON)
1811             {
1812               new_value = gtk_adjustment_get_upper (adjustment);
1813               wrapped = TRUE;
1814             }
1815           else if (new_value < gtk_adjustment_get_lower (adjustment))
1816             new_value = gtk_adjustment_get_lower (adjustment);
1817         }
1818       else
1819         new_value = MAX (new_value, gtk_adjustment_get_lower (adjustment));
1820     }
1821
1822   if (fabs (new_value - gtk_adjustment_get_value (adjustment)) > EPSILON)
1823     gtk_adjustment_set_value (adjustment, new_value);
1824
1825   if (wrapped)
1826     g_signal_emit (spin_button, spinbutton_signals[WRAPPED], 0);
1827
1828   gtk_widget_queue_draw (GTK_WIDGET (spin_button));
1829 }
1830
1831 static gint
1832 gtk_spin_button_default_input (GtkSpinButton *spin_button,
1833                                gdouble       *new_val)
1834 {
1835   gchar *err = NULL;
1836
1837   *new_val = g_strtod (gtk_entry_get_text (GTK_ENTRY (spin_button)), &err);
1838   if (*err)
1839     return GTK_INPUT_ERROR;
1840   else
1841     return FALSE;
1842 }
1843
1844 static gint
1845 gtk_spin_button_default_output (GtkSpinButton *spin_button)
1846 {
1847   GtkSpinButtonPrivate *priv = spin_button->priv;
1848
1849   gchar *buf = g_strdup_printf ("%0.*f", priv->digits, gtk_adjustment_get_value (priv->adjustment));
1850
1851   if (strcmp (buf, gtk_entry_get_text (GTK_ENTRY (spin_button))))
1852     gtk_entry_set_text (GTK_ENTRY (spin_button), buf);
1853   g_free (buf);
1854   return FALSE;
1855 }
1856
1857
1858 /***********************************************************
1859  ***********************************************************
1860  ***                  Public interface                   ***
1861  ***********************************************************
1862  ***********************************************************/
1863
1864
1865 /**
1866  * gtk_spin_button_configure:
1867  * @spin_button: a #GtkSpinButton
1868  * @adjustment: (allow-none):  a #GtkAdjustment
1869  * @climb_rate: the new climb rate
1870  * @digits: the number of decimal places to display in the spin button
1871  *
1872  * Changes the properties of an existing spin button. The adjustment,
1873  * climb rate, and number of decimal places are all changed accordingly,
1874  * after this function call.
1875  */
1876 void
1877 gtk_spin_button_configure (GtkSpinButton *spin_button,
1878                            GtkAdjustment *adjustment,
1879                            gdouble        climb_rate,
1880                            guint          digits)
1881 {
1882   GtkSpinButtonPrivate *priv;
1883
1884   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
1885
1886   priv = spin_button->priv;
1887
1888   if (adjustment)
1889     gtk_spin_button_set_adjustment (spin_button, adjustment);
1890   else
1891     adjustment = priv->adjustment;
1892
1893   g_object_freeze_notify (G_OBJECT (spin_button));
1894   if (priv->digits != digits)
1895     {
1896       priv->digits = digits;
1897       g_object_notify (G_OBJECT (spin_button), "digits");
1898     }
1899
1900   if (priv->climb_rate != climb_rate)
1901     {
1902       priv->climb_rate = climb_rate;
1903       g_object_notify (G_OBJECT (spin_button), "climb-rate");
1904     }
1905   g_object_thaw_notify (G_OBJECT (spin_button));
1906
1907   gtk_adjustment_value_changed (adjustment);
1908 }
1909
1910 /**
1911  * gtk_spin_button_new:
1912  * @adjustment: (allow-none): the #GtkAdjustment object that this spin
1913  *     button should use, or %NULL
1914  * @climb_rate: specifies how much the spin button changes when an arrow
1915  *     is clicked on
1916  * @digits: the number of decimal places to display
1917  *
1918  * Creates a new #GtkSpinButton.
1919  *
1920  * Returns: The new spin button as a #GtkWidget
1921  */
1922 GtkWidget *
1923 gtk_spin_button_new (GtkAdjustment *adjustment,
1924                      gdouble        climb_rate,
1925                      guint          digits)
1926 {
1927   GtkSpinButton *spin;
1928
1929   if (adjustment)
1930     g_return_val_if_fail (GTK_IS_ADJUSTMENT (adjustment), NULL);
1931
1932   spin = g_object_new (GTK_TYPE_SPIN_BUTTON, NULL);
1933
1934   gtk_spin_button_configure (spin, adjustment, climb_rate, digits);
1935
1936   return GTK_WIDGET (spin);
1937 }
1938
1939 /**
1940  * gtk_spin_button_new_with_range:
1941  * @min: Minimum allowable value
1942  * @max: Maximum allowable value
1943  * @step: Increment added or subtracted by spinning the widget
1944  *
1945  * This is a convenience constructor that allows creation of a numeric
1946  * #GtkSpinButton without manually creating an adjustment. The value is
1947  * initially set to the minimum value and a page increment of 10 * @step
1948  * is the default. The precision of the spin button is equivalent to the
1949  * precision of @step.
1950  *
1951  * Note that the way in which the precision is derived works best if @step
1952  * is a power of ten. If the resulting precision is not suitable for your
1953  * needs, use gtk_spin_button_set_digits() to correct it.
1954  *
1955  * Return value: The new spin button as a #GtkWidget
1956  */
1957 GtkWidget *
1958 gtk_spin_button_new_with_range (gdouble min,
1959                                 gdouble max,
1960                                 gdouble step)
1961 {
1962   GtkAdjustment *adjustment;
1963   GtkSpinButton *spin;
1964   gint digits;
1965
1966   g_return_val_if_fail (min <= max, NULL);
1967   g_return_val_if_fail (step != 0.0, NULL);
1968
1969   spin = g_object_new (GTK_TYPE_SPIN_BUTTON, NULL);
1970
1971   adjustment = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
1972
1973   if (fabs (step) >= 1.0 || step == 0.0)
1974     digits = 0;
1975   else {
1976     digits = abs ((gint) floor (log10 (fabs (step))));
1977     if (digits > MAX_DIGITS)
1978       digits = MAX_DIGITS;
1979   }
1980
1981   gtk_spin_button_configure (spin, adjustment, step, digits);
1982
1983   gtk_spin_button_set_numeric (spin, TRUE);
1984
1985   return GTK_WIDGET (spin);
1986 }
1987
1988 /* Callback used when the spin button's adjustment changes.
1989  * We need to redraw the arrows when the adjustment's range
1990  * changes, and reevaluate our size request.
1991  */
1992 static void
1993 adjustment_changed_cb (GtkAdjustment *adjustment, gpointer data)
1994 {
1995   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (data);
1996   GtkSpinButtonPrivate *priv = spin_button->priv;
1997
1998   priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
1999   gtk_widget_queue_resize (GTK_WIDGET (spin_button));
2000 }
2001
2002 /**
2003  * gtk_spin_button_set_adjustment:
2004  * @spin_button: a #GtkSpinButton
2005  * @adjustment: a #GtkAdjustment to replace the existing adjustment
2006  *
2007  * Replaces the #GtkAdjustment associated with @spin_button.
2008  */
2009 void
2010 gtk_spin_button_set_adjustment (GtkSpinButton *spin_button,
2011                                 GtkAdjustment *adjustment)
2012 {
2013   GtkSpinButtonPrivate *priv;
2014
2015   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2016
2017   priv = spin_button->priv;
2018
2019   if (priv->adjustment != adjustment)
2020     {
2021       if (priv->adjustment)
2022         {
2023           g_signal_handlers_disconnect_by_func (priv->adjustment,
2024                                                 gtk_spin_button_value_changed,
2025                                                 spin_button);
2026           g_signal_handlers_disconnect_by_func (priv->adjustment,
2027                                                 adjustment_changed_cb,
2028                                                 spin_button);
2029           g_object_unref (priv->adjustment);
2030         }
2031       priv->adjustment = adjustment;
2032       if (adjustment)
2033         {
2034           g_object_ref_sink (adjustment);
2035           g_signal_connect (adjustment, "value-changed",
2036                             G_CALLBACK (gtk_spin_button_value_changed),
2037                             spin_button);
2038           g_signal_connect (adjustment, "changed",
2039                             G_CALLBACK (adjustment_changed_cb),
2040                             spin_button);
2041           priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
2042         }
2043
2044       gtk_widget_queue_resize (GTK_WIDGET (spin_button));
2045     }
2046
2047   g_object_notify (G_OBJECT (spin_button), "adjustment");
2048 }
2049
2050 /**
2051  * gtk_spin_button_get_adjustment:
2052  * @spin_button: a #GtkSpinButton
2053  *
2054  * Get the adjustment associated with a #GtkSpinButton
2055  *
2056  * Return value: (transfer none): the #GtkAdjustment of @spin_button
2057  **/
2058 GtkAdjustment *
2059 gtk_spin_button_get_adjustment (GtkSpinButton *spin_button)
2060 {
2061   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), NULL);
2062
2063   return spin_button->priv->adjustment;
2064 }
2065
2066 /**
2067  * gtk_spin_button_set_digits:
2068  * @spin_button: a #GtkSpinButton
2069  * @digits: the number of digits after the decimal point to be displayed for the spin button's value
2070  *
2071  * Set the precision to be displayed by @spin_button. Up to 20 digit precision
2072  * is allowed.
2073  **/
2074 void
2075 gtk_spin_button_set_digits (GtkSpinButton *spin_button,
2076                             guint          digits)
2077 {
2078   GtkSpinButtonPrivate *priv;
2079
2080   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2081
2082   priv = spin_button->priv;
2083
2084   if (priv->digits != digits)
2085     {
2086       priv->digits = digits;
2087       gtk_spin_button_value_changed (priv->adjustment, spin_button);
2088       g_object_notify (G_OBJECT (spin_button), "digits");
2089
2090       /* since lower/upper may have changed */
2091       gtk_widget_queue_resize (GTK_WIDGET (spin_button));
2092     }
2093 }
2094
2095 /**
2096  * gtk_spin_button_get_digits:
2097  * @spin_button: a #GtkSpinButton
2098  *
2099  * Fetches the precision of @spin_button. See gtk_spin_button_set_digits().
2100  *
2101  * Returns: the current precision
2102  **/
2103 guint
2104 gtk_spin_button_get_digits (GtkSpinButton *spin_button)
2105 {
2106   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0);
2107
2108   return spin_button->priv->digits;
2109 }
2110
2111 /**
2112  * gtk_spin_button_set_increments:
2113  * @spin_button: a #GtkSpinButton
2114  * @step: increment applied for a button 1 press.
2115  * @page: increment applied for a button 2 press.
2116  *
2117  * Sets the step and page increments for spin_button.  This affects how
2118  * quickly the value changes when the spin button's arrows are activated.
2119  **/
2120 void
2121 gtk_spin_button_set_increments (GtkSpinButton *spin_button,
2122                                 gdouble        step,
2123                                 gdouble        page)
2124 {
2125   GtkSpinButtonPrivate *priv;
2126
2127   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2128
2129   priv = spin_button->priv;
2130
2131   gtk_adjustment_configure (priv->adjustment,
2132                             gtk_adjustment_get_value (priv->adjustment),
2133                             gtk_adjustment_get_lower (priv->adjustment),
2134                             gtk_adjustment_get_upper (priv->adjustment),
2135                             step,
2136                             page,
2137                             gtk_adjustment_get_page_size (priv->adjustment));
2138 }
2139
2140 /**
2141  * gtk_spin_button_get_increments:
2142  * @spin_button: a #GtkSpinButton
2143  * @step: (out) (allow-none): location to store step increment, or %NULL
2144  * @page: (out) (allow-none): location to store page increment, or %NULL
2145  *
2146  * Gets the current step and page the increments used by @spin_button. See
2147  * gtk_spin_button_set_increments().
2148  **/
2149 void
2150 gtk_spin_button_get_increments (GtkSpinButton *spin_button,
2151                                 gdouble       *step,
2152                                 gdouble       *page)
2153 {
2154   GtkSpinButtonPrivate *priv;
2155
2156   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2157
2158   priv = spin_button->priv;
2159
2160   if (step)
2161     *step = gtk_adjustment_get_step_increment (priv->adjustment);
2162   if (page)
2163     *page = gtk_adjustment_get_page_increment (priv->adjustment);
2164 }
2165
2166 /**
2167  * gtk_spin_button_set_range:
2168  * @spin_button: a #GtkSpinButton
2169  * @min: minimum allowable value
2170  * @max: maximum allowable value
2171  *
2172  * Sets the minimum and maximum allowable values for @spin_button.
2173  *
2174  * If the current value is outside this range, it will be adjusted
2175  * to fit within the range, otherwise it will remain unchanged.
2176  */
2177 void
2178 gtk_spin_button_set_range (GtkSpinButton *spin_button,
2179                            gdouble        min,
2180                            gdouble        max)
2181 {
2182   GtkAdjustment *adjustment;
2183
2184   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2185
2186   adjustment = spin_button->priv->adjustment;
2187
2188   gtk_adjustment_configure (adjustment,
2189                             CLAMP (gtk_adjustment_get_value (adjustment), min, max),
2190                             min,
2191                             max,
2192                             gtk_adjustment_get_step_increment (adjustment),
2193                             gtk_adjustment_get_page_increment (adjustment),
2194                             gtk_adjustment_get_page_size (adjustment));
2195 }
2196
2197 /**
2198  * gtk_spin_button_get_range:
2199  * @spin_button: a #GtkSpinButton
2200  * @min: (out) (allow-none): location to store minimum allowed value, or %NULL
2201  * @max: (out) (allow-none): location to store maximum allowed value, or %NULL
2202  *
2203  * Gets the range allowed for @spin_button.
2204  * See gtk_spin_button_set_range().
2205  */
2206 void
2207 gtk_spin_button_get_range (GtkSpinButton *spin_button,
2208                            gdouble       *min,
2209                            gdouble       *max)
2210 {
2211   GtkSpinButtonPrivate *priv;
2212
2213   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2214
2215   priv = spin_button->priv;
2216
2217   if (min)
2218     *min = gtk_adjustment_get_lower (priv->adjustment);
2219   if (max)
2220     *max = gtk_adjustment_get_upper (priv->adjustment);
2221 }
2222
2223 /**
2224  * gtk_spin_button_get_value:
2225  * @spin_button: a #GtkSpinButton
2226  *
2227  * Get the value in the @spin_button.
2228  *
2229  * Return value: the value of @spin_button
2230  */
2231 gdouble
2232 gtk_spin_button_get_value (GtkSpinButton *spin_button)
2233 {
2234   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0.0);
2235
2236   return gtk_adjustment_get_value (spin_button->priv->adjustment);
2237 }
2238
2239 /**
2240  * gtk_spin_button_get_value_as_int:
2241  * @spin_button: a #GtkSpinButton
2242  *
2243  * Get the value @spin_button represented as an integer.
2244  *
2245  * Return value: the value of @spin_button
2246  */
2247 gint
2248 gtk_spin_button_get_value_as_int (GtkSpinButton *spin_button)
2249 {
2250   GtkSpinButtonPrivate *priv;
2251   gdouble val;
2252
2253   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0);
2254
2255   priv = spin_button->priv;
2256
2257   val = gtk_adjustment_get_value (priv->adjustment);
2258   if (val - floor (val) < ceil (val) - val)
2259     return floor (val);
2260   else
2261     return ceil (val);
2262 }
2263
2264 /**
2265  * gtk_spin_button_set_value:
2266  * @spin_button: a #GtkSpinButton
2267  * @value: the new value
2268  *
2269  * Sets the value of @spin_button.
2270  */
2271 void
2272 gtk_spin_button_set_value (GtkSpinButton *spin_button,
2273                            gdouble        value)
2274 {
2275   GtkSpinButtonPrivate *priv;
2276
2277   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2278
2279   priv = spin_button->priv;
2280
2281   if (fabs (value - gtk_adjustment_get_value (priv->adjustment)) > EPSILON)
2282     gtk_adjustment_set_value (priv->adjustment, value);
2283   else
2284     {
2285       gint return_val = FALSE;
2286       g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
2287       if (return_val == FALSE)
2288         gtk_spin_button_default_output (spin_button);
2289     }
2290 }
2291
2292 /**
2293  * gtk_spin_button_set_update_policy:
2294  * @spin_button: a #GtkSpinButton
2295  * @policy: a #GtkSpinButtonUpdatePolicy value
2296  *
2297  * Sets the update behavior of a spin button.
2298  * This determines wether the spin button is always updated
2299  * or only when a valid value is set.
2300  */
2301 void
2302 gtk_spin_button_set_update_policy (GtkSpinButton             *spin_button,
2303                                    GtkSpinButtonUpdatePolicy  policy)
2304 {
2305   GtkSpinButtonPrivate *priv;
2306
2307   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2308
2309   priv = spin_button->priv;
2310
2311   if (priv->update_policy != policy)
2312     {
2313       priv->update_policy = policy;
2314       g_object_notify (G_OBJECT (spin_button), "update-policy");
2315     }
2316 }
2317
2318 /**
2319  * gtk_spin_button_get_update_policy:
2320  * @spin_button: a #GtkSpinButton
2321  *
2322  * Gets the update behavior of a spin button.
2323  * See gtk_spin_button_set_update_policy().
2324  *
2325  * Return value: the current update policy
2326  */
2327 GtkSpinButtonUpdatePolicy
2328 gtk_spin_button_get_update_policy (GtkSpinButton *spin_button)
2329 {
2330   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), GTK_UPDATE_ALWAYS);
2331
2332   return spin_button->priv->update_policy;
2333 }
2334
2335 /**
2336  * gtk_spin_button_set_numeric:
2337  * @spin_button: a #GtkSpinButton
2338  * @numeric: flag indicating if only numeric entry is allowed
2339  *
2340  * Sets the flag that determines if non-numeric text can be typed
2341  * into the spin button.
2342  */
2343 void
2344 gtk_spin_button_set_numeric (GtkSpinButton *spin_button,
2345                              gboolean       numeric)
2346 {
2347   GtkSpinButtonPrivate *priv;
2348
2349   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2350
2351   priv = spin_button->priv;
2352
2353   numeric = numeric != FALSE;
2354
2355   if (priv->numeric != numeric)
2356     {
2357        priv->numeric = numeric;
2358        g_object_notify (G_OBJECT (spin_button), "numeric");
2359     }
2360 }
2361
2362 /**
2363  * gtk_spin_button_get_numeric:
2364  * @spin_button: a #GtkSpinButton
2365  *
2366  * Returns whether non-numeric text can be typed into the spin button.
2367  * See gtk_spin_button_set_numeric().
2368  *
2369  * Return value: %TRUE if only numeric text can be entered
2370  */
2371 gboolean
2372 gtk_spin_button_get_numeric (GtkSpinButton *spin_button)
2373 {
2374   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2375
2376   return spin_button->priv->numeric;
2377 }
2378
2379 /**
2380  * gtk_spin_button_set_wrap:
2381  * @spin_button: a #GtkSpinButton
2382  * @wrap: a flag indicating if wrapping behavior is performed
2383  *
2384  * Sets the flag that determines if a spin button value wraps
2385  * around to the opposite limit when the upper or lower limit
2386  * of the range is exceeded.
2387  */
2388 void
2389 gtk_spin_button_set_wrap (GtkSpinButton  *spin_button,
2390                           gboolean        wrap)
2391 {
2392   GtkSpinButtonPrivate *priv;
2393
2394   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2395
2396   priv = spin_button->priv;
2397
2398   wrap = wrap != FALSE;
2399
2400   if (priv->wrap != wrap)
2401     {
2402        priv->wrap = wrap;
2403
2404        g_object_notify (G_OBJECT (spin_button), "wrap");
2405     }
2406 }
2407
2408 /**
2409  * gtk_spin_button_get_wrap:
2410  * @spin_button: a #GtkSpinButton
2411  *
2412  * Returns whether the spin button's value wraps around to the
2413  * opposite limit when the upper or lower limit of the range is
2414  * exceeded. See gtk_spin_button_set_wrap().
2415  *
2416  * Return value: %TRUE if the spin button wraps around
2417  */
2418 gboolean
2419 gtk_spin_button_get_wrap (GtkSpinButton *spin_button)
2420 {
2421   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2422
2423   return spin_button->priv->wrap;
2424 }
2425
2426 /**
2427  * gtk_spin_button_set_snap_to_ticks:
2428  * @spin_button: a #GtkSpinButton
2429  * @snap_to_ticks: a flag indicating if invalid values should be corrected
2430  *
2431  * Sets the policy as to whether values are corrected to the
2432  * nearest step increment when a spin button is activated after
2433  * providing an invalid value.
2434  */
2435 void
2436 gtk_spin_button_set_snap_to_ticks (GtkSpinButton *spin_button,
2437                                    gboolean       snap_to_ticks)
2438 {
2439   GtkSpinButtonPrivate *priv;
2440   guint new_val;
2441
2442   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2443
2444   priv = spin_button->priv;
2445
2446   new_val = (snap_to_ticks != 0);
2447
2448   if (new_val != priv->snap_to_ticks)
2449     {
2450       priv->snap_to_ticks = new_val;
2451       if (new_val && gtk_editable_get_editable (GTK_EDITABLE (spin_button)))
2452         gtk_spin_button_update (spin_button);
2453
2454       g_object_notify (G_OBJECT (spin_button), "snap-to-ticks");
2455     }
2456 }
2457
2458 /**
2459  * gtk_spin_button_get_snap_to_ticks:
2460  * @spin_button: a #GtkSpinButton
2461  *
2462  * Returns whether the values are corrected to the nearest step.
2463  * See gtk_spin_button_set_snap_to_ticks().
2464  *
2465  * Return value: %TRUE if values are snapped to the nearest step
2466  */
2467 gboolean
2468 gtk_spin_button_get_snap_to_ticks (GtkSpinButton *spin_button)
2469 {
2470   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2471
2472   return spin_button->priv->snap_to_ticks;
2473 }
2474
2475 /**
2476  * gtk_spin_button_spin:
2477  * @spin_button: a #GtkSpinButton
2478  * @direction: a #GtkSpinType indicating the direction to spin
2479  * @increment: step increment to apply in the specified direction
2480  *
2481  * Increment or decrement a spin button's value in a specified
2482  * direction by a specified amount.
2483  */
2484 void
2485 gtk_spin_button_spin (GtkSpinButton *spin_button,
2486                       GtkSpinType    direction,
2487                       gdouble        increment)
2488 {
2489   GtkSpinButtonPrivate *priv;
2490   GtkAdjustment *adjustment;
2491   gdouble diff;
2492
2493   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2494
2495   priv = spin_button->priv;
2496
2497   adjustment = priv->adjustment;
2498
2499   /* for compatibility with the 1.0.x version of this function */
2500   if (increment != 0 && increment != gtk_adjustment_get_step_increment (adjustment) &&
2501       (direction == GTK_SPIN_STEP_FORWARD ||
2502        direction == GTK_SPIN_STEP_BACKWARD))
2503     {
2504       if (direction == GTK_SPIN_STEP_BACKWARD && increment > 0)
2505         increment = -increment;
2506       direction = GTK_SPIN_USER_DEFINED;
2507     }
2508
2509   switch (direction)
2510     {
2511     case GTK_SPIN_STEP_FORWARD:
2512
2513       gtk_spin_button_real_spin (spin_button, gtk_adjustment_get_step_increment (adjustment));
2514       break;
2515
2516     case GTK_SPIN_STEP_BACKWARD:
2517
2518       gtk_spin_button_real_spin (spin_button, -gtk_adjustment_get_step_increment (adjustment));
2519       break;
2520
2521     case GTK_SPIN_PAGE_FORWARD:
2522
2523       gtk_spin_button_real_spin (spin_button, gtk_adjustment_get_page_increment (adjustment));
2524       break;
2525
2526     case GTK_SPIN_PAGE_BACKWARD:
2527
2528       gtk_spin_button_real_spin (spin_button, -gtk_adjustment_get_page_increment (adjustment));
2529       break;
2530
2531     case GTK_SPIN_HOME:
2532
2533       diff = gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_lower (adjustment);
2534       if (diff > EPSILON)
2535         gtk_spin_button_real_spin (spin_button, -diff);
2536       break;
2537
2538     case GTK_SPIN_END:
2539
2540       diff = gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_value (adjustment);
2541       if (diff > EPSILON)
2542         gtk_spin_button_real_spin (spin_button, diff);
2543       break;
2544
2545     case GTK_SPIN_USER_DEFINED:
2546
2547       if (increment != 0)
2548         gtk_spin_button_real_spin (spin_button, increment);
2549       break;
2550
2551     default:
2552       break;
2553     }
2554 }
2555
2556 /**
2557  * gtk_spin_button_update:
2558  * @spin_button: a #GtkSpinButton
2559  *
2560  * Manually force an update of the spin button.
2561  */
2562 void
2563 gtk_spin_button_update (GtkSpinButton *spin_button)
2564 {
2565   GtkSpinButtonPrivate *priv;
2566   gdouble val;
2567   gint error = 0;
2568   gint return_val;
2569
2570   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2571
2572   priv = spin_button->priv;
2573
2574   return_val = FALSE;
2575   g_signal_emit (spin_button, spinbutton_signals[INPUT], 0, &val, &return_val);
2576   if (return_val == FALSE)
2577     {
2578       return_val = gtk_spin_button_default_input (spin_button, &val);
2579       error = (return_val == GTK_INPUT_ERROR);
2580     }
2581   else if (return_val == GTK_INPUT_ERROR)
2582     error = 1;
2583
2584   gtk_widget_queue_draw (GTK_WIDGET (spin_button));
2585
2586   if (priv->update_policy == GTK_UPDATE_ALWAYS)
2587     {
2588       if (val < gtk_adjustment_get_lower (priv->adjustment))
2589         val = gtk_adjustment_get_lower (priv->adjustment);
2590       else if (val > gtk_adjustment_get_upper (priv->adjustment))
2591         val = gtk_adjustment_get_upper (priv->adjustment);
2592     }
2593   else if ((priv->update_policy == GTK_UPDATE_IF_VALID) &&
2594            (error ||
2595             val < gtk_adjustment_get_lower (priv->adjustment) ||
2596             val > gtk_adjustment_get_upper (priv->adjustment)))
2597     {
2598       gtk_spin_button_value_changed (priv->adjustment, spin_button);
2599       return;
2600     }
2601
2602   if (priv->snap_to_ticks)
2603     gtk_spin_button_snap (spin_button, val);
2604   else
2605     gtk_spin_button_set_value (spin_button, val);
2606 }
2607
2608 void
2609 _gtk_spin_button_get_panels (GtkSpinButton *spin_button,
2610                              GdkWindow **down_panel,
2611                              GdkWindow **up_panel)
2612 {
2613   if (down_panel != NULL)
2614     *down_panel = spin_button->priv->down_panel;
2615
2616   if (up_panel != NULL)
2617     *up_panel = spin_button->priv->up_panel;
2618 }