]> Pileus Git - ~andy/gtk/blob - gtk/gtkspinbutton.c
gtkspinbutton: Respect value of inherited 'editable' property
[~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           } else
1361             gtk_widget_error_bell (widget);
1362
1363           return TRUE;
1364         }
1365       else
1366         return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->button_press_event (widget, event);
1367     }
1368   return FALSE;
1369 }
1370
1371 static gint
1372 gtk_spin_button_button_release (GtkWidget      *widget,
1373                                 GdkEventButton *event)
1374 {
1375   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1376   GtkSpinButtonPrivate *priv = spin->priv;
1377
1378   if (event->button == priv->button)
1379     {
1380       GdkWindow *click_child = priv->click_child;
1381
1382       gtk_spin_button_stop_spinning (spin);
1383
1384       if (event->button == 3)
1385         {
1386           gdouble diff;
1387
1388           if (event->window == priv->down_panel &&
1389               click_child == event->window)
1390             {
1391               diff = gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment);
1392               if (diff > EPSILON)
1393                 gtk_spin_button_real_spin (spin, -diff);
1394             }
1395           else if (event->window == priv->up_panel &&
1396                    click_child == event->window)
1397             {
1398               diff = gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_value (priv->adjustment);
1399               if (diff > EPSILON)
1400                 gtk_spin_button_real_spin (spin, diff);
1401             }
1402         }
1403       gtk_widget_queue_draw (GTK_WIDGET (spin));
1404
1405       return TRUE;
1406     }
1407   else
1408     return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->button_release_event (widget, event);
1409 }
1410
1411 static gint
1412 gtk_spin_button_motion_notify (GtkWidget      *widget,
1413                                GdkEventMotion *event)
1414 {
1415   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1416   GtkSpinButtonPrivate *priv = spin->priv;
1417
1418   if (priv->button)
1419     return FALSE;
1420
1421   if (event->window == priv->down_panel ||
1422       event->window == priv->up_panel)
1423     {
1424       gdk_event_request_motions (event);
1425
1426       priv->in_child = event->window;
1427       gtk_widget_queue_draw (widget);
1428
1429       return FALSE;
1430     }
1431
1432   return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->motion_notify_event (widget, event);
1433 }
1434
1435 static gint
1436 gtk_spin_button_timer (GtkSpinButton *spin_button)
1437 {
1438   GtkSpinButtonPrivate *priv = spin_button->priv;
1439   gboolean retval = FALSE;
1440
1441   if (priv->timer)
1442     {
1443       if (priv->click_child == priv->up_panel)
1444         gtk_spin_button_real_spin (spin_button, priv->timer_step);
1445       else
1446         gtk_spin_button_real_spin (spin_button, -priv->timer_step);
1447
1448       if (priv->need_timer)
1449         {
1450           GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (spin_button));
1451           guint        timeout;
1452
1453           g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL);
1454
1455           priv->need_timer = FALSE;
1456           priv->timer = gdk_threads_add_timeout (timeout,
1457                                               (GSourceFunc) gtk_spin_button_timer,
1458                                               (gpointer) spin_button);
1459         }
1460       else
1461         {
1462           if (priv->climb_rate > 0.0 && priv->timer_step
1463               < gtk_adjustment_get_page_increment (priv->adjustment))
1464             {
1465               if (priv->timer_calls < MAX_TIMER_CALLS)
1466                 priv->timer_calls++;
1467               else
1468                 {
1469                   priv->timer_calls = 0;
1470                   priv->timer_step += priv->climb_rate;
1471                 }
1472             }
1473           retval = TRUE;
1474         }
1475     }
1476
1477   return retval;
1478 }
1479
1480 static void
1481 gtk_spin_button_value_changed (GtkAdjustment *adjustment,
1482                                GtkSpinButton *spin_button)
1483 {
1484   gboolean return_val;
1485
1486   g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
1487
1488   return_val = FALSE;
1489   g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
1490   if (return_val == FALSE)
1491     gtk_spin_button_default_output (spin_button);
1492
1493   g_signal_emit (spin_button, spinbutton_signals[VALUE_CHANGED], 0);
1494
1495   gtk_widget_queue_draw (GTK_WIDGET (spin_button));
1496
1497   g_object_notify (G_OBJECT (spin_button), "value");
1498 }
1499
1500 static void
1501 gtk_spin_button_real_change_value (GtkSpinButton *spin,
1502                                    GtkScrollType  scroll)
1503 {
1504   GtkSpinButtonPrivate *priv = spin->priv;
1505   gdouble old_value;
1506
1507   /* When the key binding is activated, there may be an outstanding
1508    * value, so we first have to commit what is currently written in
1509    * the spin buttons text entry. See #106574
1510    */
1511   gtk_spin_button_update (spin);
1512
1513   old_value = gtk_adjustment_get_value (priv->adjustment);
1514
1515   /* We don't test whether the entry is editable, since
1516    * this key binding conceptually corresponds to changing
1517    * the value with the buttons using the mouse, which
1518    * we allow for non-editable spin buttons.
1519    */
1520   switch (scroll)
1521     {
1522     case GTK_SCROLL_STEP_BACKWARD:
1523     case GTK_SCROLL_STEP_DOWN:
1524     case GTK_SCROLL_STEP_LEFT:
1525       gtk_spin_button_real_spin (spin, -priv->timer_step);
1526
1527       if (priv->climb_rate > 0.0 && priv->timer_step
1528           < gtk_adjustment_get_page_increment (priv->adjustment))
1529         {
1530           if (priv->timer_calls < MAX_TIMER_CALLS)
1531             priv->timer_calls++;
1532           else
1533             {
1534               priv->timer_calls = 0;
1535               priv->timer_step += priv->climb_rate;
1536             }
1537         }
1538       break;
1539
1540     case GTK_SCROLL_STEP_FORWARD:
1541     case GTK_SCROLL_STEP_UP:
1542     case GTK_SCROLL_STEP_RIGHT:
1543       gtk_spin_button_real_spin (spin, priv->timer_step);
1544
1545       if (priv->climb_rate > 0.0 && priv->timer_step
1546           < gtk_adjustment_get_page_increment (priv->adjustment))
1547         {
1548           if (priv->timer_calls < MAX_TIMER_CALLS)
1549             priv->timer_calls++;
1550           else
1551             {
1552               priv->timer_calls = 0;
1553               priv->timer_step += priv->climb_rate;
1554             }
1555         }
1556       break;
1557
1558     case GTK_SCROLL_PAGE_BACKWARD:
1559     case GTK_SCROLL_PAGE_DOWN:
1560     case GTK_SCROLL_PAGE_LEFT:
1561       gtk_spin_button_real_spin (spin, -gtk_adjustment_get_page_increment (priv->adjustment));
1562       break;
1563
1564     case GTK_SCROLL_PAGE_FORWARD:
1565     case GTK_SCROLL_PAGE_UP:
1566     case GTK_SCROLL_PAGE_RIGHT:
1567       gtk_spin_button_real_spin (spin, gtk_adjustment_get_page_increment (priv->adjustment));
1568       break;
1569
1570     case GTK_SCROLL_START:
1571       {
1572         gdouble diff = gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment);
1573         if (diff > EPSILON)
1574           gtk_spin_button_real_spin (spin, -diff);
1575         break;
1576       }
1577
1578     case GTK_SCROLL_END:
1579       {
1580         gdouble diff = gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_value (priv->adjustment);
1581         if (diff > EPSILON)
1582           gtk_spin_button_real_spin (spin, diff);
1583         break;
1584       }
1585
1586     default:
1587       g_warning ("Invalid scroll type %d for GtkSpinButton::change-value", scroll);
1588       break;
1589     }
1590
1591   gtk_spin_button_update (spin);
1592
1593   if (gtk_adjustment_get_value (priv->adjustment) == old_value)
1594     gtk_widget_error_bell (GTK_WIDGET (spin));
1595 }
1596
1597 static gint
1598 gtk_spin_button_key_release (GtkWidget   *widget,
1599                              GdkEventKey *event)
1600 {
1601   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1602   GtkSpinButtonPrivate *priv = spin->priv;
1603
1604   /* We only get a release at the end of a key repeat run, so reset the timer_step */
1605   priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
1606   priv->timer_calls = 0;
1607
1608   return TRUE;
1609 }
1610
1611 static void
1612 gtk_spin_button_snap (GtkSpinButton *spin_button,
1613                       gdouble        val)
1614 {
1615   GtkSpinButtonPrivate *priv = spin_button->priv;
1616   gdouble inc;
1617   gdouble tmp;
1618
1619   inc = gtk_adjustment_get_step_increment (priv->adjustment);
1620   if (inc == 0)
1621     return;
1622
1623   tmp = (val - gtk_adjustment_get_lower (priv->adjustment)) / inc;
1624   if (tmp - floor (tmp) < ceil (tmp) - tmp)
1625     val = gtk_adjustment_get_lower (priv->adjustment) + floor (tmp) * inc;
1626   else
1627     val = gtk_adjustment_get_lower (priv->adjustment) + ceil (tmp) * inc;
1628
1629   gtk_spin_button_set_value (spin_button, val);
1630 }
1631
1632 static void
1633 gtk_spin_button_activate (GtkEntry *entry)
1634 {
1635   if (gtk_editable_get_editable (GTK_EDITABLE (entry)))
1636     gtk_spin_button_update (GTK_SPIN_BUTTON (entry));
1637
1638   /* Chain up so that entry->activates_default is honored */
1639   GTK_ENTRY_CLASS (gtk_spin_button_parent_class)->activate (entry);
1640 }
1641
1642 static void
1643 gtk_spin_button_get_text_area_size (GtkEntry *entry,
1644                                     gint     *x,
1645                                     gint     *y,
1646                                     gint     *width,
1647                                     gint     *height)
1648 {
1649   GtkSpinButtonPrivate *priv = GTK_SPIN_BUTTON (entry)->priv;
1650   gint up_panel_width, down_panel_width;
1651
1652   up_panel_width = gtk_spin_button_panel_get_width (GTK_SPIN_BUTTON (entry), priv->up_panel);
1653   down_panel_width = gtk_spin_button_panel_get_width (GTK_SPIN_BUTTON (entry), priv->down_panel);
1654
1655   GTK_ENTRY_CLASS (gtk_spin_button_parent_class)->get_text_area_size (entry, x, y, width, height);
1656
1657   if (gtk_widget_get_direction (GTK_WIDGET (entry)) == GTK_TEXT_DIR_RTL)
1658     {
1659       if (x)
1660         *x += up_panel_width + down_panel_width;
1661     }
1662
1663   if (width)
1664     *width -= up_panel_width + down_panel_width;
1665 }
1666
1667 static void
1668 gtk_spin_button_insert_text (GtkEditable *editable,
1669                              const gchar *new_text,
1670                              gint         new_text_length,
1671                              gint        *position)
1672 {
1673   GtkEntry *entry = GTK_ENTRY (editable);
1674   GtkSpinButton *spin = GTK_SPIN_BUTTON (editable);
1675   GtkSpinButtonPrivate *priv = spin->priv;
1676   GtkEditableInterface *parent_editable_iface;
1677
1678   parent_editable_iface = g_type_interface_peek (gtk_spin_button_parent_class,
1679                                                  GTK_TYPE_EDITABLE);
1680
1681   if (priv->numeric)
1682     {
1683       struct lconv *lc;
1684       gboolean sign;
1685       gint dotpos = -1;
1686       gint i;
1687       guint32 pos_sign;
1688       guint32 neg_sign;
1689       gint entry_length;
1690       const gchar *entry_text;
1691
1692       entry_length = gtk_entry_get_text_length (entry);
1693       entry_text = gtk_entry_get_text (entry);
1694
1695       lc = localeconv ();
1696
1697       if (*(lc->negative_sign))
1698         neg_sign = *(lc->negative_sign);
1699       else
1700         neg_sign = '-';
1701
1702       if (*(lc->positive_sign))
1703         pos_sign = *(lc->positive_sign);
1704       else
1705         pos_sign = '+';
1706
1707 #ifdef G_OS_WIN32
1708       /* Workaround for bug caused by some Windows application messing
1709        * up the positive sign of the current locale, more specifically
1710        * HKEY_CURRENT_USER\Control Panel\International\sPositiveSign.
1711        * See bug #330743 and for instance
1712        * http://www.msnewsgroups.net/group/microsoft.public.dotnet.languages.csharp/topic36024.aspx
1713        *
1714        * I don't know if the positive sign always gets bogusly set to
1715        * a digit when the above Registry value is corrupted as
1716        * described. (In my test case, it got set to "8", and in the
1717        * bug report above it presumably was set ot "0".) Probably it
1718        * might get set to almost anything? So how to distinguish a
1719        * bogus value from some correct one for some locale? That is
1720        * probably hard, but at least we should filter out the
1721        * digits...
1722        */
1723       if (pos_sign >= '0' && pos_sign <= '9')
1724         pos_sign = '+';
1725 #endif
1726
1727       for (sign=0, i=0; i<entry_length; i++)
1728         if ((entry_text[i] == neg_sign) ||
1729             (entry_text[i] == pos_sign))
1730           {
1731             sign = 1;
1732             break;
1733           }
1734
1735       if (sign && !(*position))
1736         return;
1737
1738       for (dotpos=-1, i=0; i<entry_length; i++)
1739         if (entry_text[i] == *(lc->decimal_point))
1740           {
1741             dotpos = i;
1742             break;
1743           }
1744
1745       if (dotpos > -1 && *position > dotpos &&
1746           (gint)priv->digits - entry_length
1747             + dotpos - new_text_length + 1 < 0)
1748         return;
1749
1750       for (i = 0; i < new_text_length; i++)
1751         {
1752           if (new_text[i] == neg_sign || new_text[i] == pos_sign)
1753             {
1754               if (sign || (*position) || i)
1755                 return;
1756               sign = TRUE;
1757             }
1758           else if (new_text[i] == *(lc->decimal_point))
1759             {
1760               if (!priv->digits || dotpos > -1 ||
1761                   (new_text_length - 1 - i + entry_length
1762                     - *position > (gint)priv->digits))
1763                 return;
1764               dotpos = *position + i;
1765             }
1766           else if (new_text[i] < 0x30 || new_text[i] > 0x39)
1767             return;
1768         }
1769     }
1770
1771   parent_editable_iface->insert_text (editable, new_text,
1772                                       new_text_length, position);
1773 }
1774
1775 static void
1776 gtk_spin_button_real_spin (GtkSpinButton *spin_button,
1777                            gdouble        increment)
1778 {
1779   GtkSpinButtonPrivate *priv = spin_button->priv;
1780   GtkAdjustment *adjustment;
1781   gdouble new_value = 0.0;
1782   gboolean wrapped = FALSE;
1783
1784   adjustment = priv->adjustment;
1785
1786   new_value = gtk_adjustment_get_value (adjustment) + increment;
1787
1788   if (increment > 0)
1789     {
1790       if (priv->wrap)
1791         {
1792           if (fabs (gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_upper (adjustment)) < EPSILON)
1793             {
1794               new_value = gtk_adjustment_get_lower (adjustment);
1795               wrapped = TRUE;
1796             }
1797           else if (new_value > gtk_adjustment_get_upper (adjustment))
1798             new_value = gtk_adjustment_get_upper (adjustment);
1799         }
1800       else
1801         new_value = MIN (new_value, gtk_adjustment_get_upper (adjustment));
1802     }
1803   else if (increment < 0)
1804     {
1805       if (priv->wrap)
1806         {
1807           if (fabs (gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_lower (adjustment)) < EPSILON)
1808             {
1809               new_value = gtk_adjustment_get_upper (adjustment);
1810               wrapped = TRUE;
1811             }
1812           else if (new_value < gtk_adjustment_get_lower (adjustment))
1813             new_value = gtk_adjustment_get_lower (adjustment);
1814         }
1815       else
1816         new_value = MAX (new_value, gtk_adjustment_get_lower (adjustment));
1817     }
1818
1819   if (fabs (new_value - gtk_adjustment_get_value (adjustment)) > EPSILON)
1820     gtk_adjustment_set_value (adjustment, new_value);
1821
1822   if (wrapped)
1823     g_signal_emit (spin_button, spinbutton_signals[WRAPPED], 0);
1824
1825   gtk_widget_queue_draw (GTK_WIDGET (spin_button));
1826 }
1827
1828 static gint
1829 gtk_spin_button_default_input (GtkSpinButton *spin_button,
1830                                gdouble       *new_val)
1831 {
1832   gchar *err = NULL;
1833
1834   *new_val = g_strtod (gtk_entry_get_text (GTK_ENTRY (spin_button)), &err);
1835   if (*err)
1836     return GTK_INPUT_ERROR;
1837   else
1838     return FALSE;
1839 }
1840
1841 static gint
1842 gtk_spin_button_default_output (GtkSpinButton *spin_button)
1843 {
1844   GtkSpinButtonPrivate *priv = spin_button->priv;
1845
1846   gchar *buf = g_strdup_printf ("%0.*f", priv->digits, gtk_adjustment_get_value (priv->adjustment));
1847
1848   if (strcmp (buf, gtk_entry_get_text (GTK_ENTRY (spin_button))))
1849     gtk_entry_set_text (GTK_ENTRY (spin_button), buf);
1850   g_free (buf);
1851   return FALSE;
1852 }
1853
1854
1855 /***********************************************************
1856  ***********************************************************
1857  ***                  Public interface                   ***
1858  ***********************************************************
1859  ***********************************************************/
1860
1861
1862 /**
1863  * gtk_spin_button_configure:
1864  * @spin_button: a #GtkSpinButton
1865  * @adjustment: (allow-none):  a #GtkAdjustment
1866  * @climb_rate: the new climb rate
1867  * @digits: the number of decimal places to display in the spin button
1868  *
1869  * Changes the properties of an existing spin button. The adjustment,
1870  * climb rate, and number of decimal places are all changed accordingly,
1871  * after this function call.
1872  */
1873 void
1874 gtk_spin_button_configure (GtkSpinButton *spin_button,
1875                            GtkAdjustment *adjustment,
1876                            gdouble        climb_rate,
1877                            guint          digits)
1878 {
1879   GtkSpinButtonPrivate *priv;
1880
1881   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
1882
1883   priv = spin_button->priv;
1884
1885   if (adjustment)
1886     gtk_spin_button_set_adjustment (spin_button, adjustment);
1887   else
1888     adjustment = priv->adjustment;
1889
1890   g_object_freeze_notify (G_OBJECT (spin_button));
1891   if (priv->digits != digits)
1892     {
1893       priv->digits = digits;
1894       g_object_notify (G_OBJECT (spin_button), "digits");
1895     }
1896
1897   if (priv->climb_rate != climb_rate)
1898     {
1899       priv->climb_rate = climb_rate;
1900       g_object_notify (G_OBJECT (spin_button), "climb-rate");
1901     }
1902   g_object_thaw_notify (G_OBJECT (spin_button));
1903
1904   gtk_adjustment_value_changed (adjustment);
1905 }
1906
1907 /**
1908  * gtk_spin_button_new:
1909  * @adjustment: (allow-none): the #GtkAdjustment object that this spin
1910  *     button should use, or %NULL
1911  * @climb_rate: specifies how much the spin button changes when an arrow
1912  *     is clicked on
1913  * @digits: the number of decimal places to display
1914  *
1915  * Creates a new #GtkSpinButton.
1916  *
1917  * Returns: The new spin button as a #GtkWidget
1918  */
1919 GtkWidget *
1920 gtk_spin_button_new (GtkAdjustment *adjustment,
1921                      gdouble        climb_rate,
1922                      guint          digits)
1923 {
1924   GtkSpinButton *spin;
1925
1926   if (adjustment)
1927     g_return_val_if_fail (GTK_IS_ADJUSTMENT (adjustment), NULL);
1928
1929   spin = g_object_new (GTK_TYPE_SPIN_BUTTON, NULL);
1930
1931   gtk_spin_button_configure (spin, adjustment, climb_rate, digits);
1932
1933   return GTK_WIDGET (spin);
1934 }
1935
1936 /**
1937  * gtk_spin_button_new_with_range:
1938  * @min: Minimum allowable value
1939  * @max: Maximum allowable value
1940  * @step: Increment added or subtracted by spinning the widget
1941  *
1942  * This is a convenience constructor that allows creation of a numeric
1943  * #GtkSpinButton without manually creating an adjustment. The value is
1944  * initially set to the minimum value and a page increment of 10 * @step
1945  * is the default. The precision of the spin button is equivalent to the
1946  * precision of @step.
1947  *
1948  * Note that the way in which the precision is derived works best if @step
1949  * is a power of ten. If the resulting precision is not suitable for your
1950  * needs, use gtk_spin_button_set_digits() to correct it.
1951  *
1952  * Return value: The new spin button as a #GtkWidget
1953  */
1954 GtkWidget *
1955 gtk_spin_button_new_with_range (gdouble min,
1956                                 gdouble max,
1957                                 gdouble step)
1958 {
1959   GtkAdjustment *adjustment;
1960   GtkSpinButton *spin;
1961   gint digits;
1962
1963   g_return_val_if_fail (min <= max, NULL);
1964   g_return_val_if_fail (step != 0.0, NULL);
1965
1966   spin = g_object_new (GTK_TYPE_SPIN_BUTTON, NULL);
1967
1968   adjustment = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
1969
1970   if (fabs (step) >= 1.0 || step == 0.0)
1971     digits = 0;
1972   else {
1973     digits = abs ((gint) floor (log10 (fabs (step))));
1974     if (digits > MAX_DIGITS)
1975       digits = MAX_DIGITS;
1976   }
1977
1978   gtk_spin_button_configure (spin, adjustment, step, digits);
1979
1980   gtk_spin_button_set_numeric (spin, TRUE);
1981
1982   return GTK_WIDGET (spin);
1983 }
1984
1985 /* Callback used when the spin button's adjustment changes.
1986  * We need to redraw the arrows when the adjustment's range
1987  * changes, and reevaluate our size request.
1988  */
1989 static void
1990 adjustment_changed_cb (GtkAdjustment *adjustment, gpointer data)
1991 {
1992   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (data);
1993   GtkSpinButtonPrivate *priv = spin_button->priv;
1994
1995   priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
1996   gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1997 }
1998
1999 /**
2000  * gtk_spin_button_set_adjustment:
2001  * @spin_button: a #GtkSpinButton
2002  * @adjustment: a #GtkAdjustment to replace the existing adjustment
2003  *
2004  * Replaces the #GtkAdjustment associated with @spin_button.
2005  */
2006 void
2007 gtk_spin_button_set_adjustment (GtkSpinButton *spin_button,
2008                                 GtkAdjustment *adjustment)
2009 {
2010   GtkSpinButtonPrivate *priv;
2011
2012   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2013
2014   priv = spin_button->priv;
2015
2016   if (priv->adjustment != adjustment)
2017     {
2018       if (priv->adjustment)
2019         {
2020           g_signal_handlers_disconnect_by_func (priv->adjustment,
2021                                                 gtk_spin_button_value_changed,
2022                                                 spin_button);
2023           g_signal_handlers_disconnect_by_func (priv->adjustment,
2024                                                 adjustment_changed_cb,
2025                                                 spin_button);
2026           g_object_unref (priv->adjustment);
2027         }
2028       priv->adjustment = adjustment;
2029       if (adjustment)
2030         {
2031           g_object_ref_sink (adjustment);
2032           g_signal_connect (adjustment, "value-changed",
2033                             G_CALLBACK (gtk_spin_button_value_changed),
2034                             spin_button);
2035           g_signal_connect (adjustment, "changed",
2036                             G_CALLBACK (adjustment_changed_cb),
2037                             spin_button);
2038           priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
2039         }
2040
2041       gtk_widget_queue_resize (GTK_WIDGET (spin_button));
2042     }
2043
2044   g_object_notify (G_OBJECT (spin_button), "adjustment");
2045 }
2046
2047 /**
2048  * gtk_spin_button_get_adjustment:
2049  * @spin_button: a #GtkSpinButton
2050  *
2051  * Get the adjustment associated with a #GtkSpinButton
2052  *
2053  * Return value: (transfer none): the #GtkAdjustment of @spin_button
2054  **/
2055 GtkAdjustment *
2056 gtk_spin_button_get_adjustment (GtkSpinButton *spin_button)
2057 {
2058   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), NULL);
2059
2060   return spin_button->priv->adjustment;
2061 }
2062
2063 /**
2064  * gtk_spin_button_set_digits:
2065  * @spin_button: a #GtkSpinButton
2066  * @digits: the number of digits after the decimal point to be displayed for the spin button's value
2067  *
2068  * Set the precision to be displayed by @spin_button. Up to 20 digit precision
2069  * is allowed.
2070  **/
2071 void
2072 gtk_spin_button_set_digits (GtkSpinButton *spin_button,
2073                             guint          digits)
2074 {
2075   GtkSpinButtonPrivate *priv;
2076
2077   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2078
2079   priv = spin_button->priv;
2080
2081   if (priv->digits != digits)
2082     {
2083       priv->digits = digits;
2084       gtk_spin_button_value_changed (priv->adjustment, spin_button);
2085       g_object_notify (G_OBJECT (spin_button), "digits");
2086
2087       /* since lower/upper may have changed */
2088       gtk_widget_queue_resize (GTK_WIDGET (spin_button));
2089     }
2090 }
2091
2092 /**
2093  * gtk_spin_button_get_digits:
2094  * @spin_button: a #GtkSpinButton
2095  *
2096  * Fetches the precision of @spin_button. See gtk_spin_button_set_digits().
2097  *
2098  * Returns: the current precision
2099  **/
2100 guint
2101 gtk_spin_button_get_digits (GtkSpinButton *spin_button)
2102 {
2103   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0);
2104
2105   return spin_button->priv->digits;
2106 }
2107
2108 /**
2109  * gtk_spin_button_set_increments:
2110  * @spin_button: a #GtkSpinButton
2111  * @step: increment applied for a button 1 press.
2112  * @page: increment applied for a button 2 press.
2113  *
2114  * Sets the step and page increments for spin_button.  This affects how
2115  * quickly the value changes when the spin button's arrows are activated.
2116  **/
2117 void
2118 gtk_spin_button_set_increments (GtkSpinButton *spin_button,
2119                                 gdouble        step,
2120                                 gdouble        page)
2121 {
2122   GtkSpinButtonPrivate *priv;
2123
2124   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2125
2126   priv = spin_button->priv;
2127
2128   gtk_adjustment_configure (priv->adjustment,
2129                             gtk_adjustment_get_value (priv->adjustment),
2130                             gtk_adjustment_get_lower (priv->adjustment),
2131                             gtk_adjustment_get_upper (priv->adjustment),
2132                             step,
2133                             page,
2134                             gtk_adjustment_get_page_size (priv->adjustment));
2135 }
2136
2137 /**
2138  * gtk_spin_button_get_increments:
2139  * @spin_button: a #GtkSpinButton
2140  * @step: (out) (allow-none): location to store step increment, or %NULL
2141  * @page: (out) (allow-none): location to store page increment, or %NULL
2142  *
2143  * Gets the current step and page the increments used by @spin_button. See
2144  * gtk_spin_button_set_increments().
2145  **/
2146 void
2147 gtk_spin_button_get_increments (GtkSpinButton *spin_button,
2148                                 gdouble       *step,
2149                                 gdouble       *page)
2150 {
2151   GtkSpinButtonPrivate *priv;
2152
2153   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2154
2155   priv = spin_button->priv;
2156
2157   if (step)
2158     *step = gtk_adjustment_get_step_increment (priv->adjustment);
2159   if (page)
2160     *page = gtk_adjustment_get_page_increment (priv->adjustment);
2161 }
2162
2163 /**
2164  * gtk_spin_button_set_range:
2165  * @spin_button: a #GtkSpinButton
2166  * @min: minimum allowable value
2167  * @max: maximum allowable value
2168  *
2169  * Sets the minimum and maximum allowable values for @spin_button.
2170  *
2171  * If the current value is outside this range, it will be adjusted
2172  * to fit within the range, otherwise it will remain unchanged.
2173  */
2174 void
2175 gtk_spin_button_set_range (GtkSpinButton *spin_button,
2176                            gdouble        min,
2177                            gdouble        max)
2178 {
2179   GtkAdjustment *adjustment;
2180
2181   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2182
2183   adjustment = spin_button->priv->adjustment;
2184
2185   gtk_adjustment_configure (adjustment,
2186                             CLAMP (gtk_adjustment_get_value (adjustment), min, max),
2187                             min,
2188                             max,
2189                             gtk_adjustment_get_step_increment (adjustment),
2190                             gtk_adjustment_get_page_increment (adjustment),
2191                             gtk_adjustment_get_page_size (adjustment));
2192 }
2193
2194 /**
2195  * gtk_spin_button_get_range:
2196  * @spin_button: a #GtkSpinButton
2197  * @min: (out) (allow-none): location to store minimum allowed value, or %NULL
2198  * @max: (out) (allow-none): location to store maximum allowed value, or %NULL
2199  *
2200  * Gets the range allowed for @spin_button.
2201  * See gtk_spin_button_set_range().
2202  */
2203 void
2204 gtk_spin_button_get_range (GtkSpinButton *spin_button,
2205                            gdouble       *min,
2206                            gdouble       *max)
2207 {
2208   GtkSpinButtonPrivate *priv;
2209
2210   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2211
2212   priv = spin_button->priv;
2213
2214   if (min)
2215     *min = gtk_adjustment_get_lower (priv->adjustment);
2216   if (max)
2217     *max = gtk_adjustment_get_upper (priv->adjustment);
2218 }
2219
2220 /**
2221  * gtk_spin_button_get_value:
2222  * @spin_button: a #GtkSpinButton
2223  *
2224  * Get the value in the @spin_button.
2225  *
2226  * Return value: the value of @spin_button
2227  */
2228 gdouble
2229 gtk_spin_button_get_value (GtkSpinButton *spin_button)
2230 {
2231   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0.0);
2232
2233   return gtk_adjustment_get_value (spin_button->priv->adjustment);
2234 }
2235
2236 /**
2237  * gtk_spin_button_get_value_as_int:
2238  * @spin_button: a #GtkSpinButton
2239  *
2240  * Get the value @spin_button represented as an integer.
2241  *
2242  * Return value: the value of @spin_button
2243  */
2244 gint
2245 gtk_spin_button_get_value_as_int (GtkSpinButton *spin_button)
2246 {
2247   GtkSpinButtonPrivate *priv;
2248   gdouble val;
2249
2250   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0);
2251
2252   priv = spin_button->priv;
2253
2254   val = gtk_adjustment_get_value (priv->adjustment);
2255   if (val - floor (val) < ceil (val) - val)
2256     return floor (val);
2257   else
2258     return ceil (val);
2259 }
2260
2261 /**
2262  * gtk_spin_button_set_value:
2263  * @spin_button: a #GtkSpinButton
2264  * @value: the new value
2265  *
2266  * Sets the value of @spin_button.
2267  */
2268 void
2269 gtk_spin_button_set_value (GtkSpinButton *spin_button,
2270                            gdouble        value)
2271 {
2272   GtkSpinButtonPrivate *priv;
2273
2274   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2275
2276   priv = spin_button->priv;
2277
2278   if (fabs (value - gtk_adjustment_get_value (priv->adjustment)) > EPSILON)
2279     gtk_adjustment_set_value (priv->adjustment, value);
2280   else
2281     {
2282       gint return_val = FALSE;
2283       g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
2284       if (return_val == FALSE)
2285         gtk_spin_button_default_output (spin_button);
2286     }
2287 }
2288
2289 /**
2290  * gtk_spin_button_set_update_policy:
2291  * @spin_button: a #GtkSpinButton
2292  * @policy: a #GtkSpinButtonUpdatePolicy value
2293  *
2294  * Sets the update behavior of a spin button.
2295  * This determines wether the spin button is always updated
2296  * or only when a valid value is set.
2297  */
2298 void
2299 gtk_spin_button_set_update_policy (GtkSpinButton             *spin_button,
2300                                    GtkSpinButtonUpdatePolicy  policy)
2301 {
2302   GtkSpinButtonPrivate *priv;
2303
2304   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2305
2306   priv = spin_button->priv;
2307
2308   if (priv->update_policy != policy)
2309     {
2310       priv->update_policy = policy;
2311       g_object_notify (G_OBJECT (spin_button), "update-policy");
2312     }
2313 }
2314
2315 /**
2316  * gtk_spin_button_get_update_policy:
2317  * @spin_button: a #GtkSpinButton
2318  *
2319  * Gets the update behavior of a spin button.
2320  * See gtk_spin_button_set_update_policy().
2321  *
2322  * Return value: the current update policy
2323  */
2324 GtkSpinButtonUpdatePolicy
2325 gtk_spin_button_get_update_policy (GtkSpinButton *spin_button)
2326 {
2327   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), GTK_UPDATE_ALWAYS);
2328
2329   return spin_button->priv->update_policy;
2330 }
2331
2332 /**
2333  * gtk_spin_button_set_numeric:
2334  * @spin_button: a #GtkSpinButton
2335  * @numeric: flag indicating if only numeric entry is allowed
2336  *
2337  * Sets the flag that determines if non-numeric text can be typed
2338  * into the spin button.
2339  */
2340 void
2341 gtk_spin_button_set_numeric (GtkSpinButton *spin_button,
2342                              gboolean       numeric)
2343 {
2344   GtkSpinButtonPrivate *priv;
2345
2346   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2347
2348   priv = spin_button->priv;
2349
2350   numeric = numeric != FALSE;
2351
2352   if (priv->numeric != numeric)
2353     {
2354        priv->numeric = numeric;
2355        g_object_notify (G_OBJECT (spin_button), "numeric");
2356     }
2357 }
2358
2359 /**
2360  * gtk_spin_button_get_numeric:
2361  * @spin_button: a #GtkSpinButton
2362  *
2363  * Returns whether non-numeric text can be typed into the spin button.
2364  * See gtk_spin_button_set_numeric().
2365  *
2366  * Return value: %TRUE if only numeric text can be entered
2367  */
2368 gboolean
2369 gtk_spin_button_get_numeric (GtkSpinButton *spin_button)
2370 {
2371   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2372
2373   return spin_button->priv->numeric;
2374 }
2375
2376 /**
2377  * gtk_spin_button_set_wrap:
2378  * @spin_button: a #GtkSpinButton
2379  * @wrap: a flag indicating if wrapping behavior is performed
2380  *
2381  * Sets the flag that determines if a spin button value wraps
2382  * around to the opposite limit when the upper or lower limit
2383  * of the range is exceeded.
2384  */
2385 void
2386 gtk_spin_button_set_wrap (GtkSpinButton  *spin_button,
2387                           gboolean        wrap)
2388 {
2389   GtkSpinButtonPrivate *priv;
2390
2391   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2392
2393   priv = spin_button->priv;
2394
2395   wrap = wrap != FALSE;
2396
2397   if (priv->wrap != wrap)
2398     {
2399        priv->wrap = wrap;
2400
2401        g_object_notify (G_OBJECT (spin_button), "wrap");
2402     }
2403 }
2404
2405 /**
2406  * gtk_spin_button_get_wrap:
2407  * @spin_button: a #GtkSpinButton
2408  *
2409  * Returns whether the spin button's value wraps around to the
2410  * opposite limit when the upper or lower limit of the range is
2411  * exceeded. See gtk_spin_button_set_wrap().
2412  *
2413  * Return value: %TRUE if the spin button wraps around
2414  */
2415 gboolean
2416 gtk_spin_button_get_wrap (GtkSpinButton *spin_button)
2417 {
2418   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2419
2420   return spin_button->priv->wrap;
2421 }
2422
2423 /**
2424  * gtk_spin_button_set_snap_to_ticks:
2425  * @spin_button: a #GtkSpinButton
2426  * @snap_to_ticks: a flag indicating if invalid values should be corrected
2427  *
2428  * Sets the policy as to whether values are corrected to the
2429  * nearest step increment when a spin button is activated after
2430  * providing an invalid value.
2431  */
2432 void
2433 gtk_spin_button_set_snap_to_ticks (GtkSpinButton *spin_button,
2434                                    gboolean       snap_to_ticks)
2435 {
2436   GtkSpinButtonPrivate *priv;
2437   guint new_val;
2438
2439   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2440
2441   priv = spin_button->priv;
2442
2443   new_val = (snap_to_ticks != 0);
2444
2445   if (new_val != priv->snap_to_ticks)
2446     {
2447       priv->snap_to_ticks = new_val;
2448       if (new_val && gtk_editable_get_editable (GTK_EDITABLE (spin_button)))
2449         gtk_spin_button_update (spin_button);
2450
2451       g_object_notify (G_OBJECT (spin_button), "snap-to-ticks");
2452     }
2453 }
2454
2455 /**
2456  * gtk_spin_button_get_snap_to_ticks:
2457  * @spin_button: a #GtkSpinButton
2458  *
2459  * Returns whether the values are corrected to the nearest step.
2460  * See gtk_spin_button_set_snap_to_ticks().
2461  *
2462  * Return value: %TRUE if values are snapped to the nearest step
2463  */
2464 gboolean
2465 gtk_spin_button_get_snap_to_ticks (GtkSpinButton *spin_button)
2466 {
2467   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2468
2469   return spin_button->priv->snap_to_ticks;
2470 }
2471
2472 /**
2473  * gtk_spin_button_spin:
2474  * @spin_button: a #GtkSpinButton
2475  * @direction: a #GtkSpinType indicating the direction to spin
2476  * @increment: step increment to apply in the specified direction
2477  *
2478  * Increment or decrement a spin button's value in a specified
2479  * direction by a specified amount.
2480  */
2481 void
2482 gtk_spin_button_spin (GtkSpinButton *spin_button,
2483                       GtkSpinType    direction,
2484                       gdouble        increment)
2485 {
2486   GtkSpinButtonPrivate *priv;
2487   GtkAdjustment *adjustment;
2488   gdouble diff;
2489
2490   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2491
2492   priv = spin_button->priv;
2493
2494   adjustment = priv->adjustment;
2495
2496   /* for compatibility with the 1.0.x version of this function */
2497   if (increment != 0 && increment != gtk_adjustment_get_step_increment (adjustment) &&
2498       (direction == GTK_SPIN_STEP_FORWARD ||
2499        direction == GTK_SPIN_STEP_BACKWARD))
2500     {
2501       if (direction == GTK_SPIN_STEP_BACKWARD && increment > 0)
2502         increment = -increment;
2503       direction = GTK_SPIN_USER_DEFINED;
2504     }
2505
2506   switch (direction)
2507     {
2508     case GTK_SPIN_STEP_FORWARD:
2509
2510       gtk_spin_button_real_spin (spin_button, gtk_adjustment_get_step_increment (adjustment));
2511       break;
2512
2513     case GTK_SPIN_STEP_BACKWARD:
2514
2515       gtk_spin_button_real_spin (spin_button, -gtk_adjustment_get_step_increment (adjustment));
2516       break;
2517
2518     case GTK_SPIN_PAGE_FORWARD:
2519
2520       gtk_spin_button_real_spin (spin_button, gtk_adjustment_get_page_increment (adjustment));
2521       break;
2522
2523     case GTK_SPIN_PAGE_BACKWARD:
2524
2525       gtk_spin_button_real_spin (spin_button, -gtk_adjustment_get_page_increment (adjustment));
2526       break;
2527
2528     case GTK_SPIN_HOME:
2529
2530       diff = gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_lower (adjustment);
2531       if (diff > EPSILON)
2532         gtk_spin_button_real_spin (spin_button, -diff);
2533       break;
2534
2535     case GTK_SPIN_END:
2536
2537       diff = gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_value (adjustment);
2538       if (diff > EPSILON)
2539         gtk_spin_button_real_spin (spin_button, diff);
2540       break;
2541
2542     case GTK_SPIN_USER_DEFINED:
2543
2544       if (increment != 0)
2545         gtk_spin_button_real_spin (spin_button, increment);
2546       break;
2547
2548     default:
2549       break;
2550     }
2551 }
2552
2553 /**
2554  * gtk_spin_button_update:
2555  * @spin_button: a #GtkSpinButton
2556  *
2557  * Manually force an update of the spin button.
2558  */
2559 void
2560 gtk_spin_button_update (GtkSpinButton *spin_button)
2561 {
2562   GtkSpinButtonPrivate *priv;
2563   gdouble val;
2564   gint error = 0;
2565   gint return_val;
2566
2567   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2568
2569   priv = spin_button->priv;
2570
2571   return_val = FALSE;
2572   g_signal_emit (spin_button, spinbutton_signals[INPUT], 0, &val, &return_val);
2573   if (return_val == FALSE)
2574     {
2575       return_val = gtk_spin_button_default_input (spin_button, &val);
2576       error = (return_val == GTK_INPUT_ERROR);
2577     }
2578   else if (return_val == GTK_INPUT_ERROR)
2579     error = 1;
2580
2581   gtk_widget_queue_draw (GTK_WIDGET (spin_button));
2582
2583   if (priv->update_policy == GTK_UPDATE_ALWAYS)
2584     {
2585       if (val < gtk_adjustment_get_lower (priv->adjustment))
2586         val = gtk_adjustment_get_lower (priv->adjustment);
2587       else if (val > gtk_adjustment_get_upper (priv->adjustment))
2588         val = gtk_adjustment_get_upper (priv->adjustment);
2589     }
2590   else if ((priv->update_policy == GTK_UPDATE_IF_VALID) &&
2591            (error ||
2592             val < gtk_adjustment_get_lower (priv->adjustment) ||
2593             val > gtk_adjustment_get_upper (priv->adjustment)))
2594     {
2595       gtk_spin_button_value_changed (priv->adjustment, spin_button);
2596       return;
2597     }
2598
2599   if (priv->snap_to_ticks)
2600     gtk_spin_button_snap (spin_button, val);
2601   else
2602     gtk_spin_button_set_value (spin_button, val);
2603 }
2604
2605 void
2606 _gtk_spin_button_get_panels (GtkSpinButton *spin_button,
2607                              GdkWindow **down_panel,
2608                              GdkWindow **up_panel)
2609 {
2610   if (down_panel != NULL)
2611     *down_panel = spin_button->priv->down_panel;
2612
2613   if (up_panel != NULL)
2614     *up_panel = spin_button->priv->up_panel;
2615 }