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