]> Pileus Git - ~andy/gtk/blob - gtk/gtkspinbutton.c
Change FSF Address
[~andy/gtk] / gtk / gtkspinbutton.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * GtkSpinButton widget for GTK+
5  * Copyright (C) 1998 Lars Hamann and Stefan Jeske
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19  */
20
21 /*
22  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
23  * file for a list of people on the GTK+ Team.  See the ChangeLog
24  * files for a list of changes.  These files are distributed with
25  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
26  */
27
28 #include "config.h"
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <math.h>
33 #include <string.h>
34 #include <locale.h>
35
36 #include "gtkbindings.h"
37 #include "gtkspinbutton.h"
38 #include "gtkentryprivate.h"
39 #include "gtkicontheme.h"
40 #include "gtkmarshalers.h"
41 #include "gtksettings.h"
42 #include "gtkstock.h"
43 #include "gtkprivate.h"
44 #include "gtkintl.h"
45 #include "gtktypebuiltins.h"
46
47 #include "a11y/gtkspinbuttonaccessible.h"
48
49 #define MIN_SPIN_BUTTON_WIDTH 30
50 #define MAX_TIMER_CALLS       5
51 #define EPSILON               1e-10
52 #define MAX_DIGITS            20
53 #define MIN_ARROW_WIDTH       6
54
55
56 /**
57  * SECTION:gtkspinbutton
58  * @Title: GtkSpinButton
59  * @Short_description: Retrieve an integer or floating-point number from
60  *     the user
61  * @See_also: #GtkEntry
62  *
63  * A #GtkSpinButton is an ideal way to allow the user to set the value of
64  * some attribute. Rather than having to directly type a number into a
65  * #GtkEntry, GtkSpinButton allows the user to click on one of two arrows
66  * to increment or decrement the displayed value. A value can still be
67  * typed in, with the bonus that it can be checked to ensure it is in a
68  * given range.
69  *
70  * The main properties of a GtkSpinButton are through an adjustment.
71  * See the #GtkAdjustment section for more details about an adjustment's
72  * properties.
73  *
74  * <example>
75  * <title>Using a GtkSpinButton to get an integer</title>
76  * <programlisting>
77  * /&ast; Provides a function to retrieve an integer value from a
78  *  &ast; GtkSpinButton and creates a spin button to model percentage
79  *  &ast; values.
80  *  &ast;/
81  *
82  * gint
83  * grab_int_value (GtkSpinButton *button,
84  *                 gpointer       user_data)
85  * {
86  *   return gtk_spin_button_get_value_as_int (button);
87  * }
88  *
89  * void
90  * create_integer_spin_button (void)
91  * {
92  *
93  *   GtkWidget *window, *button;
94  *   GtkAdjustment *adjustment;
95  *
96  *   adjustment = gtk_adjustment_new (50.0, 0.0, 100.0, 1.0, 5.0, 0.0);
97  *
98  *   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
99  *   gtk_container_set_border_width (GTK_CONTAINER (window), 5);
100  *
101  *   /&ast; creates the spinbutton, with no decimal places &ast;/
102  *   button = gtk_spin_button_new (adjustment, 1.0, 0);
103  *   gtk_container_add (GTK_CONTAINER (window), button);
104  *
105  *   gtk_widget_show_all (window);
106  * }
107  * </programlisting>
108  * </example>
109  *
110  * <example>
111  * <title>Using a GtkSpinButton to get a floating point value</title>
112  * <programlisting>
113  * /&ast; Provides a function to retrieve a floating point value from a
114  *  &ast; GtkSpinButton, and creates a high precision spin button.
115  *  &ast;/
116  *
117  * gfloat
118  * grab_float_value (GtkSpinButton *button,
119  *                   gpointer       user_data)
120  * {
121  *   return gtk_spin_button_get_value (button);
122  * }
123  *
124  * void
125  * create_floating_spin_button (void)
126  * {
127  *   GtkWidget *window, *button;
128  *   GtkAdjustment *adjustment;
129  *
130  *   adjustment = gtk_adjustment_new (2.500, 0.0, 5.0, 0.001, 0.1, 0.0);
131  *
132  *   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
133  *   gtk_container_set_border_width (GTK_CONTAINER (window), 5);
134  *
135  *   /&ast; creates the spinbutton, with three decimal places &ast;/
136  *   button = gtk_spin_button_new (adjustment, 0.001, 3);
137  *   gtk_container_add (GTK_CONTAINER (window), button);
138  *
139  *   gtk_widget_show_all (window);
140  * }
141  * </programlisting>
142  * </example>
143  */
144
145 struct _GtkSpinButtonPrivate
146 {
147   GtkAdjustment *adjustment;
148
149   GdkWindow     *down_panel;
150   GdkWindow     *up_panel;
151
152   GdkWindow     *click_child;
153   GdkWindow     *in_child;
154
155   guint32        timer;
156
157   GtkSpinButtonUpdatePolicy update_policy;
158
159   gdouble        climb_rate;
160   gdouble        timer_step;
161
162   guint          button        : 2;
163   guint          digits        : 10;
164   guint          need_timer    : 1;
165   guint          numeric       : 1;
166   guint          snap_to_ticks : 1;
167   guint          timer_calls   : 3;
168   guint          wrap          : 1;
169 };
170
171 enum {
172   PROP_0,
173   PROP_ADJUSTMENT,
174   PROP_CLIMB_RATE,
175   PROP_DIGITS,
176   PROP_SNAP_TO_TICKS,
177   PROP_NUMERIC,
178   PROP_WRAP,
179   PROP_UPDATE_POLICY,
180   PROP_VALUE
181 };
182
183 /* Signals */
184 enum
185 {
186   INPUT,
187   OUTPUT,
188   VALUE_CHANGED,
189   CHANGE_VALUE,
190   WRAPPED,
191   LAST_SIGNAL
192 };
193
194 static void gtk_spin_button_editable_init  (GtkEditableInterface *iface);
195 static void gtk_spin_button_finalize       (GObject            *object);
196 static void gtk_spin_button_set_property   (GObject         *object,
197                                             guint            prop_id,
198                                             const GValue    *value,
199                                             GParamSpec      *pspec);
200 static void gtk_spin_button_get_property   (GObject         *object,
201                                             guint            prop_id,
202                                             GValue          *value,
203                                             GParamSpec      *pspec);
204 static void gtk_spin_button_destroy        (GtkWidget          *widget);
205 static void gtk_spin_button_map            (GtkWidget          *widget);
206 static void gtk_spin_button_unmap          (GtkWidget          *widget);
207 static void gtk_spin_button_realize        (GtkWidget          *widget);
208 static void gtk_spin_button_unrealize      (GtkWidget          *widget);
209 static void gtk_spin_button_get_preferred_width  (GtkWidget          *widget,
210                                                   gint               *minimum,
211                                                   gint               *natural);
212
213 static void gtk_spin_button_size_allocate  (GtkWidget          *widget,
214                                             GtkAllocation      *allocation);
215 static gint gtk_spin_button_draw           (GtkWidget          *widget,
216                                             cairo_t            *cr);
217 static gint gtk_spin_button_button_press   (GtkWidget          *widget,
218                                             GdkEventButton     *event);
219 static gint gtk_spin_button_button_release (GtkWidget          *widget,
220                                             GdkEventButton     *event);
221 static gint gtk_spin_button_motion_notify  (GtkWidget          *widget,
222                                             GdkEventMotion     *event);
223 static gint gtk_spin_button_enter_notify   (GtkWidget          *widget,
224                                             GdkEventCrossing   *event);
225 static gint gtk_spin_button_leave_notify   (GtkWidget          *widget,
226                                             GdkEventCrossing   *event);
227 static gint gtk_spin_button_focus_out      (GtkWidget          *widget,
228                                             GdkEventFocus      *event);
229 static void gtk_spin_button_grab_notify    (GtkWidget          *widget,
230                                             gboolean            was_grabbed);
231 static void gtk_spin_button_state_flags_changed  (GtkWidget     *widget,
232                                                   GtkStateFlags  previous_state);
233 static gboolean gtk_spin_button_timer          (GtkSpinButton      *spin_button);
234 static gboolean gtk_spin_button_stop_spinning  (GtkSpinButton      *spin);
235 static void gtk_spin_button_value_changed  (GtkAdjustment      *adjustment,
236                                             GtkSpinButton      *spin_button);
237 static gint gtk_spin_button_key_release    (GtkWidget          *widget,
238                                             GdkEventKey        *event);
239 static gint gtk_spin_button_scroll         (GtkWidget          *widget,
240                                             GdkEventScroll     *event);
241 static void gtk_spin_button_activate       (GtkEntry           *entry);
242 static void gtk_spin_button_get_text_area_size (GtkEntry *entry,
243                                                 gint     *x,
244                                                 gint     *y,
245                                                 gint     *width,
246                                                 gint     *height);
247 static void gtk_spin_button_snap           (GtkSpinButton      *spin_button,
248                                             gdouble             val);
249 static void gtk_spin_button_insert_text    (GtkEditable        *editable,
250                                             const gchar        *new_text,
251                                             gint                new_text_length,
252                                             gint               *position);
253 static void gtk_spin_button_real_spin      (GtkSpinButton      *spin_button,
254                                             gdouble             step);
255 static void gtk_spin_button_real_change_value (GtkSpinButton   *spin,
256                                                GtkScrollType    scroll);
257
258 static gint gtk_spin_button_default_input  (GtkSpinButton      *spin_button,
259                                             gdouble            *new_val);
260 static gint gtk_spin_button_default_output (GtkSpinButton      *spin_button);
261
262 static guint spinbutton_signals[LAST_SIGNAL] = {0};
263
264 G_DEFINE_TYPE_WITH_CODE (GtkSpinButton, gtk_spin_button, GTK_TYPE_ENTRY,
265                          G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
266                                                 gtk_spin_button_editable_init))
267
268 #define add_spin_binding(binding_set, keyval, mask, scroll)            \
269   gtk_binding_entry_add_signal (binding_set, keyval, mask,             \
270                                 "change_value", 1,                     \
271                                 GTK_TYPE_SCROLL_TYPE, scroll)
272
273 static void
274 gtk_spin_button_class_init (GtkSpinButtonClass *class)
275 {
276   GObjectClass     *gobject_class = G_OBJECT_CLASS (class);
277   GtkWidgetClass   *widget_class = GTK_WIDGET_CLASS (class);
278   GtkEntryClass    *entry_class = GTK_ENTRY_CLASS (class);
279   GtkBindingSet    *binding_set;
280
281   gobject_class->finalize = gtk_spin_button_finalize;
282   gobject_class->set_property = gtk_spin_button_set_property;
283   gobject_class->get_property = gtk_spin_button_get_property;
284
285   widget_class->destroy = gtk_spin_button_destroy;
286   widget_class->map = gtk_spin_button_map;
287   widget_class->unmap = gtk_spin_button_unmap;
288   widget_class->realize = gtk_spin_button_realize;
289   widget_class->unrealize = gtk_spin_button_unrealize;
290   widget_class->get_preferred_width = gtk_spin_button_get_preferred_width;
291   widget_class->size_allocate = gtk_spin_button_size_allocate;
292   widget_class->draw = gtk_spin_button_draw;
293   widget_class->scroll_event = gtk_spin_button_scroll;
294   widget_class->button_press_event = gtk_spin_button_button_press;
295   widget_class->button_release_event = gtk_spin_button_button_release;
296   widget_class->motion_notify_event = gtk_spin_button_motion_notify;
297   widget_class->key_release_event = gtk_spin_button_key_release;
298   widget_class->enter_notify_event = gtk_spin_button_enter_notify;
299   widget_class->leave_notify_event = gtk_spin_button_leave_notify;
300   widget_class->focus_out_event = gtk_spin_button_focus_out;
301   widget_class->grab_notify = gtk_spin_button_grab_notify;
302   widget_class->state_flags_changed = gtk_spin_button_state_flags_changed;
303
304   entry_class->activate = gtk_spin_button_activate;
305   entry_class->get_text_area_size = gtk_spin_button_get_text_area_size;
306
307   class->input = NULL;
308   class->output = NULL;
309   class->change_value = gtk_spin_button_real_change_value;
310
311   g_object_class_install_property (gobject_class,
312                                    PROP_ADJUSTMENT,
313                                    g_param_spec_object ("adjustment",
314                                                         P_("Adjustment"),
315                                                         P_("The adjustment that holds the value of the spin button"),
316                                                         GTK_TYPE_ADJUSTMENT,
317                                                         GTK_PARAM_READWRITE));
318
319   g_object_class_install_property (gobject_class,
320                                    PROP_CLIMB_RATE,
321                                    g_param_spec_double ("climb-rate",
322                                                         P_("Climb Rate"),
323                                                         P_("The acceleration rate when you hold down a button"),
324                                                         0.0,
325                                                         G_MAXDOUBLE,
326                                                         0.0,
327                                                         GTK_PARAM_READWRITE));
328
329   g_object_class_install_property (gobject_class,
330                                    PROP_DIGITS,
331                                    g_param_spec_uint ("digits",
332                                                       P_("Digits"),
333                                                       P_("The number of decimal places to display"),
334                                                       0,
335                                                       MAX_DIGITS,
336                                                       0,
337                                                       GTK_PARAM_READWRITE));
338
339   g_object_class_install_property (gobject_class,
340                                    PROP_SNAP_TO_TICKS,
341                                    g_param_spec_boolean ("snap-to-ticks",
342                                                          P_("Snap to Ticks"),
343                                                          P_("Whether erroneous values are automatically changed to a spin button's nearest step increment"),
344                                                          FALSE,
345                                                          GTK_PARAM_READWRITE));
346
347   g_object_class_install_property (gobject_class,
348                                    PROP_NUMERIC,
349                                    g_param_spec_boolean ("numeric",
350                                                          P_("Numeric"),
351                                                          P_("Whether non-numeric characters should be ignored"),
352                                                          FALSE,
353                                                          GTK_PARAM_READWRITE));
354
355   g_object_class_install_property (gobject_class,
356                                    PROP_WRAP,
357                                    g_param_spec_boolean ("wrap",
358                                                          P_("Wrap"),
359                                                          P_("Whether a spin button should wrap upon reaching its limits"),
360                                                          FALSE,
361                                                          GTK_PARAM_READWRITE));
362
363   g_object_class_install_property (gobject_class,
364                                    PROP_UPDATE_POLICY,
365                                    g_param_spec_enum ("update-policy",
366                                                       P_("Update Policy"),
367                                                       P_("Whether the spin button should update always, or only when the value is legal"),
368                                                       GTK_TYPE_SPIN_BUTTON_UPDATE_POLICY,
369                                                       GTK_UPDATE_ALWAYS,
370                                                       GTK_PARAM_READWRITE));
371
372   g_object_class_install_property (gobject_class,
373                                    PROP_VALUE,
374                                    g_param_spec_double ("value",
375                                                         P_("Value"),
376                                                         P_("Reads the current value, or sets a new value"),
377                                                         -G_MAXDOUBLE,
378                                                         G_MAXDOUBLE,
379                                                         0.0,
380                                                         GTK_PARAM_READWRITE));
381
382   gtk_widget_class_install_style_property_parser (widget_class,
383                                                   g_param_spec_enum ("shadow-type",
384                                                                      "Shadow Type",
385                                                                      P_("Style of bevel around the spin button"),
386                                                                      GTK_TYPE_SHADOW_TYPE,
387                                                                      GTK_SHADOW_IN,
388                                                                      GTK_PARAM_READABLE),
389                                                   gtk_rc_property_parse_enum);
390
391   /**
392    * GtkSpinButton::input:
393    * @spin_button: the object on which the signal was emitted
394    * @new_value: (out) (type double): return location for the new value
395    *
396    * The ::input signal can be used to influence the conversion of
397    * the users input into a double value. The signal handler is
398    * expected to use gtk_entry_get_text() to retrieve the text of
399    * the entry and set @new_value to the new value.
400    *
401    * The default conversion uses g_strtod().
402    *
403    * Returns: %TRUE for a successful conversion, %FALSE if the input
404    *     was not handled, and %GTK_INPUT_ERROR if the conversion failed.
405    */
406   spinbutton_signals[INPUT] =
407     g_signal_new (I_("input"),
408                   G_TYPE_FROM_CLASS (gobject_class),
409                   G_SIGNAL_RUN_LAST,
410                   G_STRUCT_OFFSET (GtkSpinButtonClass, input),
411                   NULL, NULL,
412                   _gtk_marshal_INT__POINTER,
413                   G_TYPE_INT, 1,
414                   G_TYPE_POINTER);
415
416   /**
417    * GtkSpinButton::output:
418    * @spin_button: the object which received the signal
419    *
420    * The ::output signal can be used to change to formatting
421    * of the value that is displayed in the spin buttons entry.
422    * |[
423    * /&ast; show leading zeros &ast;/
424    * static gboolean
425    * on_output (GtkSpinButton *spin,
426    *            gpointer       data)
427    * {
428    *    GtkAdjustment *adjustment;
429    *    gchar *text;
430    *    int value;
431    *
432    *    adjustment = gtk_spin_button_get_adjustment (spin);
433    *    value = (int)gtk_adjustment_get_value (adjustment);
434    *    text = g_strdup_printf ("%02d", value);
435    *    gtk_entry_set_text (GTK_ENTRY (spin), text);
436    *    g_free (text);
437    *
438    *    return TRUE;
439    * }
440    * ]|
441    *
442    * Returns: %TRUE if the value has been displayed
443    */
444   spinbutton_signals[OUTPUT] =
445     g_signal_new (I_("output"),
446                   G_TYPE_FROM_CLASS (gobject_class),
447                   G_SIGNAL_RUN_LAST,
448                   G_STRUCT_OFFSET (GtkSpinButtonClass, output),
449                   _gtk_boolean_handled_accumulator, NULL,
450                   _gtk_marshal_BOOLEAN__VOID,
451                   G_TYPE_BOOLEAN, 0);
452
453   spinbutton_signals[VALUE_CHANGED] =
454     g_signal_new (I_("value-changed"),
455                   G_TYPE_FROM_CLASS (gobject_class),
456                   G_SIGNAL_RUN_LAST,
457                   G_STRUCT_OFFSET (GtkSpinButtonClass, value_changed),
458                   NULL, NULL,
459                   _gtk_marshal_VOID__VOID,
460                   G_TYPE_NONE, 0);
461
462   /**
463    * GtkSpinButton::wrapped:
464    * @spinbutton: the object which received the signal
465    *
466    * The wrapped signal is emitted right after the spinbutton wraps
467    * from its maximum to minimum value or vice-versa.
468    *
469    * Since: 2.10
470    */
471   spinbutton_signals[WRAPPED] =
472     g_signal_new (I_("wrapped"),
473                   G_TYPE_FROM_CLASS (gobject_class),
474                   G_SIGNAL_RUN_LAST,
475                   G_STRUCT_OFFSET (GtkSpinButtonClass, wrapped),
476                   NULL, NULL,
477                   _gtk_marshal_VOID__VOID,
478                   G_TYPE_NONE, 0);
479
480   /* Action signals */
481   spinbutton_signals[CHANGE_VALUE] =
482     g_signal_new (I_("change-value"),
483                   G_TYPE_FROM_CLASS (gobject_class),
484                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
485                   G_STRUCT_OFFSET (GtkSpinButtonClass, change_value),
486                   NULL, NULL,
487                   _gtk_marshal_VOID__ENUM,
488                   G_TYPE_NONE, 1,
489                   GTK_TYPE_SCROLL_TYPE);
490
491   binding_set = gtk_binding_set_by_class (class);
492
493   add_spin_binding (binding_set, GDK_KEY_Up, 0, GTK_SCROLL_STEP_UP);
494   add_spin_binding (binding_set, GDK_KEY_KP_Up, 0, GTK_SCROLL_STEP_UP);
495   add_spin_binding (binding_set, GDK_KEY_Down, 0, GTK_SCROLL_STEP_DOWN);
496   add_spin_binding (binding_set, GDK_KEY_KP_Down, 0, GTK_SCROLL_STEP_DOWN);
497   add_spin_binding (binding_set, GDK_KEY_Page_Up, 0, GTK_SCROLL_PAGE_UP);
498   add_spin_binding (binding_set, GDK_KEY_Page_Down, 0, GTK_SCROLL_PAGE_DOWN);
499   add_spin_binding (binding_set, GDK_KEY_Page_Up, GDK_CONTROL_MASK, GTK_SCROLL_END);
500   add_spin_binding (binding_set, GDK_KEY_Page_Down, GDK_CONTROL_MASK, GTK_SCROLL_START);
501
502   g_type_class_add_private (class, sizeof (GtkSpinButtonPrivate));
503
504   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SPIN_BUTTON_ACCESSIBLE);
505 }
506
507 static void
508 gtk_spin_button_editable_init (GtkEditableInterface *iface)
509 {
510   iface->insert_text = gtk_spin_button_insert_text;
511 }
512
513 static void
514 gtk_spin_button_set_property (GObject      *object,
515                               guint         prop_id,
516                               const GValue *value,
517                               GParamSpec   *pspec)
518 {
519   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
520   GtkSpinButtonPrivate *priv = spin_button->priv;
521
522   switch (prop_id)
523     {
524       GtkAdjustment *adjustment;
525
526     case PROP_ADJUSTMENT:
527       adjustment = GTK_ADJUSTMENT (g_value_get_object (value));
528       if (!adjustment)
529         adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
530       gtk_spin_button_set_adjustment (spin_button, adjustment);
531       break;
532     case PROP_CLIMB_RATE:
533       gtk_spin_button_configure (spin_button,
534                                  priv->adjustment,
535                                  g_value_get_double (value),
536                                  priv->digits);
537       break;
538     case PROP_DIGITS:
539       gtk_spin_button_configure (spin_button,
540                                  priv->adjustment,
541                                  priv->climb_rate,
542                                  g_value_get_uint (value));
543       break;
544     case PROP_SNAP_TO_TICKS:
545       gtk_spin_button_set_snap_to_ticks (spin_button, g_value_get_boolean (value));
546       break;
547     case PROP_NUMERIC:
548       gtk_spin_button_set_numeric (spin_button, g_value_get_boolean (value));
549       break;
550     case PROP_WRAP:
551       gtk_spin_button_set_wrap (spin_button, g_value_get_boolean (value));
552       break;
553     case PROP_UPDATE_POLICY:
554       gtk_spin_button_set_update_policy (spin_button, g_value_get_enum (value));
555       break;
556     case PROP_VALUE:
557       gtk_spin_button_set_value (spin_button, g_value_get_double (value));
558       break;
559     default:
560       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
561       break;
562     }
563 }
564
565 static void
566 gtk_spin_button_get_property (GObject      *object,
567                               guint         prop_id,
568                               GValue       *value,
569                               GParamSpec   *pspec)
570 {
571   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
572   GtkSpinButtonPrivate *priv = spin_button->priv;
573
574   switch (prop_id)
575     {
576     case PROP_ADJUSTMENT:
577       g_value_set_object (value, priv->adjustment);
578       break;
579     case PROP_CLIMB_RATE:
580       g_value_set_double (value, priv->climb_rate);
581       break;
582     case PROP_DIGITS:
583       g_value_set_uint (value, priv->digits);
584       break;
585     case PROP_SNAP_TO_TICKS:
586       g_value_set_boolean (value, priv->snap_to_ticks);
587       break;
588     case PROP_NUMERIC:
589       g_value_set_boolean (value, priv->numeric);
590       break;
591     case PROP_WRAP:
592       g_value_set_boolean (value, priv->wrap);
593       break;
594     case PROP_UPDATE_POLICY:
595       g_value_set_enum (value, priv->update_policy);
596       break;
597      case PROP_VALUE:
598        g_value_set_double (value, gtk_adjustment_get_value (priv->adjustment));
599       break;
600     default:
601       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
602       break;
603     }
604 }
605
606 static gint
607 get_icon_size (void)
608 {
609   gint width, height, icon_size;
610
611   gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height);
612   icon_size = MAX (width, height);
613
614   return icon_size;
615 }
616
617 static GdkPixbuf *
618 create_one_pixbuf (GtkStyleContext *context,
619                    const gchar *icon_name)
620 {
621   GtkIconInfo *icon_info;
622   GdkPixbuf *pix;
623   gint size = get_icon_size ();
624
625   icon_info = gtk_icon_theme_lookup_icon (gtk_icon_theme_get_default (),
626                                           icon_name, size,
627                                           GTK_ICON_LOOKUP_GENERIC_FALLBACK |
628                                           GTK_ICON_LOOKUP_USE_BUILTIN);
629
630   if (icon_info != NULL)
631     {
632       pix = gtk_icon_info_load_symbolic_for_context (icon_info, context, 
633                                                      NULL, NULL);
634       gtk_icon_info_free (icon_info);
635     }
636   else
637     {
638       GtkIconSet *icon_set;
639
640       icon_set = gtk_style_context_lookup_icon_set (context, GTK_STOCK_MISSING_IMAGE);
641       pix = gtk_icon_set_render_icon_pixbuf (icon_set, context, GTK_ICON_SIZE_MENU);
642
643       g_warning ("Unable to fetch icon %s from the icon theme", icon_name);
644     }
645
646   return pix;
647 }
648
649 static void
650 gtk_spin_button_init (GtkSpinButton *spin_button)
651 {
652   GtkSpinButtonPrivate *priv;
653   GtkStyleContext *context;
654
655   spin_button->priv = G_TYPE_INSTANCE_GET_PRIVATE (spin_button,
656                                                    GTK_TYPE_SPIN_BUTTON,
657                                                    GtkSpinButtonPrivate);
658   priv = spin_button->priv;
659
660   priv->adjustment = NULL;
661   priv->down_panel = NULL;
662   priv->up_panel = NULL;
663   priv->timer = 0;
664   priv->climb_rate = 0.0;
665   priv->timer_step = 0.0;
666   priv->update_policy = GTK_UPDATE_ALWAYS;
667   priv->in_child = NULL;
668   priv->click_child = NULL;
669   priv->button = 0;
670   priv->need_timer = FALSE;
671   priv->timer_calls = 0;
672   priv->digits = 0;
673   priv->numeric = FALSE;
674   priv->wrap = FALSE;
675   priv->snap_to_ticks = FALSE;
676
677   gtk_spin_button_set_adjustment (spin_button,
678                                   gtk_adjustment_new (0, 0, 0, 0, 0, 0));
679
680   context = gtk_widget_get_style_context (GTK_WIDGET (spin_button));
681   gtk_style_context_add_class (context, GTK_STYLE_CLASS_SPINBUTTON);
682 }
683
684 static void
685 gtk_spin_button_finalize (GObject *object)
686 {
687   gtk_spin_button_set_adjustment (GTK_SPIN_BUTTON (object), NULL);
688
689   G_OBJECT_CLASS (gtk_spin_button_parent_class)->finalize (object);
690 }
691
692 static void
693 gtk_spin_button_destroy (GtkWidget *widget)
694 {
695   gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));
696
697   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->destroy (widget);
698 }
699
700 static void
701 gtk_spin_button_map (GtkWidget *widget)
702 {
703   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
704   GtkSpinButtonPrivate *priv = spin_button->priv;
705
706   if (gtk_widget_get_realized (widget) && !gtk_widget_get_mapped (widget))
707     {
708       GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->map (widget);
709       gdk_window_show (priv->down_panel);
710       gdk_window_show (priv->up_panel);
711     }
712 }
713
714 static void
715 gtk_spin_button_unmap (GtkWidget *widget)
716 {
717   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
718   GtkSpinButtonPrivate *priv = spin_button->priv;
719
720   if (gtk_widget_get_mapped (widget))
721     {
722       gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));
723
724       gdk_window_hide (priv->down_panel);
725       gdk_window_hide (priv->up_panel);
726       GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unmap (widget);
727     }
728 }
729
730 static void
731 gtk_spin_button_panel_nthchildize_context (GtkSpinButton *spin_button,
732                                            GtkStyleContext *context,
733                                            GdkWindow *panel)
734 {
735   GtkWidget *widget = GTK_WIDGET (spin_button);
736   GtkWidgetPath *path, *siblings_path;
737   GtkTextDirection direction;
738   gint up_pos, down_pos;
739
740   /* We are a subclass of GtkEntry, which is not a GtkContainer, so we
741    * have to emulate what gtk_container_get_path_for_child() would do
742    * for the button panels
743    */
744   path = gtk_widget_path_copy (gtk_widget_get_path (widget));
745   direction = gtk_widget_get_direction (widget);
746   siblings_path = gtk_widget_path_new ();
747
748   /* flip children order for RTL */
749   if (direction == GTK_TEXT_DIR_RTL)
750     {
751       up_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);  
752       down_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
753       gtk_widget_path_append_type (siblings_path, GTK_TYPE_ENTRY);
754     }
755   else
756     {
757       gtk_widget_path_append_type (siblings_path, GTK_TYPE_ENTRY);
758       down_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);  
759       up_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
760     }
761
762   gtk_widget_path_iter_add_class (siblings_path, up_pos, GTK_STYLE_CLASS_BUTTON);
763   gtk_widget_path_iter_add_class (siblings_path, down_pos, GTK_STYLE_CLASS_BUTTON);
764
765   /* this allows compatibility for themes that use .spinbutton.button */
766   gtk_widget_path_iter_add_class (siblings_path, up_pos, GTK_STYLE_CLASS_SPINBUTTON);
767   gtk_widget_path_iter_add_class (siblings_path, down_pos, GTK_STYLE_CLASS_SPINBUTTON);
768
769   if (panel == spin_button->priv->down_panel)
770     {
771       gtk_widget_path_append_with_siblings (path, siblings_path, up_pos);
772       gtk_widget_path_append_with_siblings (path, siblings_path, down_pos);
773     }
774   else
775     {
776       gtk_widget_path_append_with_siblings (path, siblings_path, down_pos);
777       gtk_widget_path_append_with_siblings (path, siblings_path, up_pos);
778     }
779
780   gtk_style_context_set_path (context, path);
781   gtk_widget_path_unref (path);
782   gtk_widget_path_unref (siblings_path);
783 }
784
785 static gboolean
786 gtk_spin_button_panel_at_limit (GtkSpinButton *spin_button,
787                                 GdkWindow     *panel)
788 {
789   GtkSpinButtonPrivate *priv = spin_button->priv;
790   GdkWindow *effective_panel;
791
792   if (priv->wrap)
793     return FALSE;
794
795   if (gtk_adjustment_get_step_increment (priv->adjustment) > 0)
796     effective_panel = panel;
797   else
798     effective_panel = panel == priv->up_panel ? priv->down_panel : priv->up_panel;
799
800   if (effective_panel == priv->up_panel &&
801       (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_value (priv->adjustment) <= EPSILON))
802     return TRUE;
803
804   if (effective_panel == priv->down_panel &&
805       (gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment) <= EPSILON))
806     return TRUE;
807
808   return FALSE;
809 }
810
811 static GtkStateFlags
812 gtk_spin_button_panel_get_state (GtkSpinButton *spin_button,
813                                  GdkWindow *panel)
814 {
815   GtkStateFlags state;
816   GtkSpinButtonPrivate *priv = spin_button->priv;
817
818   state = gtk_widget_get_state_flags (GTK_WIDGET (spin_button));
819
820   state &= ~(GTK_STATE_FLAG_ACTIVE | GTK_STATE_FLAG_PRELIGHT);
821
822   if ((state & GTK_STATE_FLAG_INSENSITIVE) ||
823       gtk_spin_button_panel_at_limit (spin_button, panel) ||
824       !gtk_editable_get_editable (GTK_EDITABLE (spin_button)))
825     {
826       state |= GTK_STATE_FLAG_INSENSITIVE;
827     }
828   else
829     {
830       if (priv->click_child == panel)
831         state |= GTK_STATE_ACTIVE;
832       else if (priv->in_child == panel &&
833                priv->click_child == NULL)
834         state |= GTK_STATE_FLAG_PRELIGHT;
835     }
836
837   return state;
838 }
839
840 static GtkStyleContext *
841 gtk_spin_button_panel_get_context (GtkSpinButton *spin_button,
842                                    GdkWindow *panel)
843 {
844   GtkStyleContext *context;
845
846   context = gtk_style_context_new ();
847   gtk_spin_button_panel_nthchildize_context (spin_button, context, panel);
848
849   return context;
850 }
851
852 static gint
853 gtk_spin_button_panel_get_width (GtkSpinButton *spin_button,
854                                  GdkWindow *panel)
855 {
856   GtkBorder button_padding, button_border;
857   GtkStyleContext *context;
858   GtkStateFlags state;
859
860   context = gtk_spin_button_panel_get_context (spin_button, panel);
861   state = gtk_spin_button_panel_get_state (spin_button, panel);
862
863   gtk_style_context_get_padding (context, state, &button_padding);
864   gtk_style_context_get_border (context, state, &button_border);
865
866   g_object_unref (context);
867
868   return get_icon_size () + button_padding.left + button_padding.right +
869     button_border.left + button_border.right;
870 }
871
872 static void
873 gtk_spin_button_panel_get_allocations (GtkSpinButton *spin_button,
874                                        GtkAllocation *down_allocation_out,
875                                        GtkAllocation *up_allocation_out)
876 {
877   GtkWidget *widget = GTK_WIDGET (spin_button);
878   GtkSpinButtonPrivate *priv = spin_button->priv;
879   GtkAllocation spin_allocation, down_allocation, up_allocation, allocation;
880   GtkRequisition requisition;
881   gint req_height;
882   gint up_panel_width, down_panel_width;
883   GtkStyleContext *context;
884   GtkBorder space;
885
886   gtk_widget_get_allocation (widget, &spin_allocation);
887   gtk_widget_get_preferred_size (widget, &requisition, NULL);
888
889   context = gtk_widget_get_style_context (GTK_WIDGET (spin_button));
890   gtk_style_context_get_border (context, GTK_STATE_NORMAL, &space);
891
892   req_height = requisition.height - gtk_widget_get_margin_top (widget) - gtk_widget_get_margin_bottom (widget);
893   down_panel_width = gtk_spin_button_panel_get_width (spin_button, priv->down_panel);
894   up_panel_width = gtk_spin_button_panel_get_width (spin_button, priv->up_panel);
895
896   /* both panels have the same size, and they're as big as the entry allocation,
897    * excluding margins
898    */
899   allocation.height = MIN (req_height, spin_allocation.height) - space.top - space.bottom;
900   allocation.y = spin_allocation.y + space.top + (spin_allocation.height - req_height) / 2;
901   down_allocation = up_allocation = allocation;
902
903   down_allocation.width = down_panel_width;
904   up_allocation.width = up_panel_width;
905
906   /* invert x axis allocation for RTL */
907   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
908     {
909       up_allocation.x = spin_allocation.x + space.left;
910       down_allocation.x = up_allocation.x + up_panel_width;
911     }
912   else
913     {
914       up_allocation.x = spin_allocation.x + spin_allocation.width - up_panel_width - space.right;
915       down_allocation.x = up_allocation.x - down_panel_width;
916     }
917
918   if (down_allocation_out)
919     *down_allocation_out = down_allocation;
920   if (up_allocation_out)
921     *up_allocation_out = up_allocation;
922 }
923
924 static void
925 gtk_spin_button_panel_draw (GtkSpinButton   *spin_button,
926                             cairo_t         *cr,
927                             GdkWindow       *panel)
928 {
929   GtkSpinButtonPrivate *priv = spin_button->priv;
930   GtkStyleContext *context;
931   GtkStateFlags state;
932   GtkWidget *widget;
933   gdouble width, height, x, y;
934   GdkPixbuf *pix;
935
936   widget = GTK_WIDGET (spin_button);
937
938   cairo_save (cr);
939   gtk_cairo_transform_to_window (cr, widget, panel);
940
941   context = gtk_spin_button_panel_get_context (spin_button, panel);
942   state = gtk_spin_button_panel_get_state (spin_button, panel);
943   gtk_style_context_set_state (context, state);
944
945   height = gdk_window_get_height (panel);
946   width = gdk_window_get_width (panel);
947
948   if (panel == priv->down_panel)
949     pix = create_one_pixbuf (context, "list-remove-symbolic");
950   else
951     pix = create_one_pixbuf (context, "list-add-symbolic");
952
953   gtk_render_background (context, cr,
954                          0, 0, width, height);
955   gtk_render_frame (context, cr,
956                     0, 0, width, height);
957
958   x = floor ((width - gdk_pixbuf_get_width (pix)) / 2.0);
959   y = floor ((height - gdk_pixbuf_get_height (pix)) / 2.0);
960
961   gtk_render_icon (context, cr, pix,
962                    x, y);
963   cairo_restore (cr);
964
965   g_object_unref (pix);
966   g_object_unref (context);
967 }
968
969 static void
970 gtk_spin_button_realize (GtkWidget *widget)
971 {
972   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
973   GtkSpinButtonPrivate *priv = spin_button->priv;
974   GtkAllocation down_allocation, up_allocation;
975   GdkWindowAttr attributes;
976   gint attributes_mask;
977   gboolean return_val;
978
979   gtk_widget_set_events (widget, gtk_widget_get_events (widget) |
980                          GDK_KEY_RELEASE_MASK);
981   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->realize (widget);
982
983   attributes.window_type = GDK_WINDOW_CHILD;
984   attributes.wclass = GDK_INPUT_ONLY;
985   attributes.visual = gtk_widget_get_visual (widget);
986   attributes.event_mask = gtk_widget_get_events (widget);
987   attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK
988     | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK
989     | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;
990
991   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
992
993   gtk_spin_button_panel_get_allocations (spin_button, &down_allocation, &up_allocation);
994
995   /* create the left panel window */
996   attributes.x = down_allocation.x;
997   attributes.y = down_allocation.y;
998   attributes.width = down_allocation.width;
999   attributes.height = down_allocation.height;
1000
1001   priv->down_panel = gdk_window_new (gtk_widget_get_window (widget),
1002                                      &attributes, attributes_mask);
1003   gdk_window_set_user_data (priv->down_panel, widget);
1004
1005   /* create the right panel window */
1006   attributes.x = up_allocation.x;
1007   attributes.y = up_allocation.y;
1008   attributes.width = up_allocation.width;
1009   attributes.height = up_allocation.height;
1010
1011   priv->up_panel = gdk_window_new (gtk_widget_get_window (widget),
1012                                       &attributes, attributes_mask);
1013   gdk_window_set_user_data (priv->up_panel, widget);
1014
1015   return_val = FALSE;
1016   g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
1017   if (return_val == FALSE)
1018     gtk_spin_button_default_output (spin_button);
1019
1020   gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1021 }
1022
1023 static void
1024 gtk_spin_button_unrealize (GtkWidget *widget)
1025 {
1026   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1027   GtkSpinButtonPrivate *priv = spin->priv;
1028
1029   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unrealize (widget);
1030
1031   if (priv->down_panel)
1032     {
1033       gdk_window_set_user_data (priv->down_panel, NULL);
1034       gdk_window_destroy (priv->down_panel);
1035       priv->down_panel = NULL;
1036     }
1037
1038   if (priv->up_panel)
1039     {
1040       gdk_window_set_user_data (priv->up_panel, NULL);
1041       gdk_window_destroy (priv->up_panel);
1042       priv->up_panel = NULL;
1043     }
1044 }
1045
1046 static int
1047 compute_double_length (double val, int digits)
1048 {
1049   int a;
1050   int extra;
1051
1052   a = 1;
1053   if (fabs (val) > 1.0)
1054     a = floor (log10 (fabs (val))) + 1;
1055
1056   extra = 0;
1057
1058   /* The dot: */
1059   if (digits > 0)
1060     extra++;
1061
1062   /* The sign: */
1063   if (val < 0)
1064     extra++;
1065
1066   return a + digits + extra;
1067 }
1068
1069 static void
1070 gtk_spin_button_get_preferred_width (GtkWidget *widget,
1071                                      gint      *minimum,
1072                                      gint      *natural)
1073 {
1074   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
1075   GtkSpinButtonPrivate *priv = spin_button->priv;
1076   GtkEntry *entry = GTK_ENTRY (widget);
1077   GtkStyleContext *style_context;
1078   gint up_panel_width, down_panel_width;
1079
1080   style_context = gtk_widget_get_style_context (widget);
1081   up_panel_width = gtk_spin_button_panel_get_width (spin_button, priv->up_panel);
1082   down_panel_width = gtk_spin_button_panel_get_width (spin_button, priv->down_panel);
1083
1084   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->get_preferred_width (widget, minimum, natural);
1085
1086   if (gtk_entry_get_width_chars (entry) < 0)
1087     {
1088       PangoContext *context;
1089       const PangoFontDescription *font_desc;
1090       PangoFontMetrics *metrics;
1091       gint width;
1092       gint w;
1093       gint string_len;
1094       gint max_string_len;
1095       gint digit_width;
1096       gboolean interior_focus;
1097       gint focus_width;
1098       GtkBorder borders;
1099
1100       gtk_style_context_get_style (style_context,
1101                                    "interior-focus", &interior_focus,
1102                                    "focus-line-width", &focus_width,
1103                                    NULL);
1104       font_desc = gtk_style_context_get_font (style_context, GTK_STATE_FLAG_NORMAL);
1105
1106       context = gtk_widget_get_pango_context (widget);
1107       metrics = pango_context_get_metrics (context, font_desc,
1108                                            pango_context_get_language (context));
1109
1110       digit_width = pango_font_metrics_get_approximate_digit_width (metrics);
1111       digit_width = PANGO_SCALE *
1112         ((digit_width + PANGO_SCALE - 1) / PANGO_SCALE);
1113
1114       pango_font_metrics_unref (metrics);
1115
1116       /* Get max of MIN_SPIN_BUTTON_WIDTH, size of upper, size of lower */
1117       width = MIN_SPIN_BUTTON_WIDTH;
1118       max_string_len = MAX (10, compute_double_length (1e9 * gtk_adjustment_get_step_increment (priv->adjustment),
1119                                                        priv->digits));
1120
1121       string_len = compute_double_length (gtk_adjustment_get_upper (priv->adjustment),
1122                                           priv->digits);
1123       w = PANGO_PIXELS (MIN (string_len, max_string_len) * digit_width);
1124       width = MAX (width, w);
1125       string_len = compute_double_length (gtk_adjustment_get_lower (priv->adjustment), priv->digits);
1126       w = PANGO_PIXELS (MIN (string_len, max_string_len) * digit_width);
1127       width = MAX (width, w);
1128
1129       _gtk_entry_get_borders (entry, &borders);
1130       width += borders.left + borders.right;
1131
1132       *minimum = width;
1133       *natural = width;
1134     }
1135
1136   *minimum += up_panel_width + down_panel_width;
1137   *natural += up_panel_width + down_panel_width;
1138 }
1139
1140 static void
1141 gtk_spin_button_size_allocate (GtkWidget     *widget,
1142                                GtkAllocation *allocation)
1143 {
1144   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1145   GtkSpinButtonPrivate *priv = spin->priv;
1146   GtkAllocation down_panel_allocation, up_panel_allocation;
1147
1148   gtk_widget_set_allocation (widget, allocation);
1149   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->size_allocate (widget, allocation);
1150
1151   gtk_spin_button_panel_get_allocations (spin, &down_panel_allocation, &up_panel_allocation);
1152
1153   if (gtk_widget_get_realized (widget))
1154     {
1155       gdk_window_move_resize (priv->down_panel,
1156                               down_panel_allocation.x,
1157                               down_panel_allocation.y,
1158                               down_panel_allocation.width,
1159                               down_panel_allocation.height);
1160
1161       gdk_window_move_resize (priv->up_panel,
1162                               up_panel_allocation.x,
1163                               up_panel_allocation.y,
1164                               up_panel_allocation.width,
1165                               up_panel_allocation.height);
1166     }
1167
1168   gtk_widget_queue_draw (GTK_WIDGET (spin));
1169 }
1170
1171 static gint
1172 gtk_spin_button_draw (GtkWidget      *widget,
1173                       cairo_t        *cr)
1174 {
1175   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1176   GtkSpinButtonPrivate *priv = spin->priv;
1177
1178   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->draw (widget, cr);
1179
1180   /* Draw the buttons */
1181   gtk_spin_button_panel_draw (spin, cr, priv->down_panel);
1182   gtk_spin_button_panel_draw (spin, cr, priv->up_panel);
1183
1184   return FALSE;
1185 }
1186
1187 static gint
1188 gtk_spin_button_enter_notify (GtkWidget        *widget,
1189                               GdkEventCrossing *event)
1190 {
1191   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1192   GtkSpinButtonPrivate *priv = spin->priv;
1193
1194   if ((event->window == priv->down_panel) ||
1195       (event->window == priv->up_panel))
1196     {
1197       priv->in_child = event->window;
1198       gtk_widget_queue_draw (GTK_WIDGET (spin));
1199     }
1200
1201   return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->enter_notify_event (widget, event);
1202 }
1203
1204 static gint
1205 gtk_spin_button_leave_notify (GtkWidget        *widget,
1206                               GdkEventCrossing *event)
1207 {
1208   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1209   GtkSpinButtonPrivate *priv = spin->priv;
1210
1211   if (priv->in_child != NULL)
1212     {
1213       priv->in_child = NULL;
1214       gtk_widget_queue_draw (GTK_WIDGET (spin));
1215     }
1216
1217   return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->leave_notify_event (widget, event);
1218 }
1219
1220 static gint
1221 gtk_spin_button_focus_out (GtkWidget     *widget,
1222                            GdkEventFocus *event)
1223 {
1224   if (gtk_editable_get_editable (GTK_EDITABLE (widget)))
1225     gtk_spin_button_update (GTK_SPIN_BUTTON (widget));
1226
1227   return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->focus_out_event (widget, event);
1228 }
1229
1230 static void
1231 gtk_spin_button_grab_notify (GtkWidget *widget,
1232                              gboolean   was_grabbed)
1233 {
1234   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1235
1236   if (!was_grabbed)
1237     {
1238       if (gtk_spin_button_stop_spinning (spin))
1239         gtk_widget_queue_draw (GTK_WIDGET (spin));
1240     }
1241 }
1242
1243 static void
1244 gtk_spin_button_state_flags_changed (GtkWidget     *widget,
1245                                      GtkStateFlags  previous_state)
1246 {
1247   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1248
1249   if (!gtk_widget_is_sensitive (widget))
1250     {
1251       if (gtk_spin_button_stop_spinning (spin))
1252         gtk_widget_queue_draw (GTK_WIDGET (spin));
1253     }
1254 }
1255
1256 static gint
1257 gtk_spin_button_scroll (GtkWidget      *widget,
1258                         GdkEventScroll *event)
1259 {
1260   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1261   GtkSpinButtonPrivate *priv = spin->priv;
1262
1263   if (event->direction == GDK_SCROLL_UP)
1264     {
1265       if (!gtk_widget_has_focus (widget))
1266         gtk_widget_grab_focus (widget);
1267       gtk_spin_button_real_spin (spin, gtk_adjustment_get_step_increment (priv->adjustment));
1268     }
1269   else if (event->direction == GDK_SCROLL_DOWN)
1270     {
1271       if (!gtk_widget_has_focus (widget))
1272         gtk_widget_grab_focus (widget);
1273       gtk_spin_button_real_spin (spin, -gtk_adjustment_get_step_increment (priv->adjustment));
1274     }
1275   else
1276     return FALSE;
1277
1278   return TRUE;
1279 }
1280
1281 static gboolean
1282 gtk_spin_button_stop_spinning (GtkSpinButton *spin)
1283 {
1284   GtkSpinButtonPrivate *priv = spin->priv;
1285   gboolean did_spin = FALSE;
1286
1287   if (priv->timer)
1288     {
1289       g_source_remove (priv->timer);
1290       priv->timer = 0;
1291       priv->need_timer = FALSE;
1292
1293       did_spin = TRUE;
1294     }
1295
1296   priv->button = 0;
1297   priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
1298   priv->timer_calls = 0;
1299
1300   priv->click_child = NULL;
1301
1302   return did_spin;
1303 }
1304
1305 static void
1306 start_spinning (GtkSpinButton *spin,
1307                 GdkWindow     *click_child,
1308                 gdouble        step)
1309 {
1310   GtkSpinButtonPrivate *priv;
1311
1312   priv = spin->priv;
1313
1314   priv->click_child = click_child;
1315
1316   if (!priv->timer)
1317     {
1318       GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (spin));
1319       guint        timeout;
1320
1321       g_object_get (settings, "gtk-timeout-initial", &timeout, NULL);
1322
1323       priv->timer_step = step;
1324       priv->need_timer = TRUE;
1325       priv->timer = gdk_threads_add_timeout (timeout,
1326                                    (GSourceFunc) gtk_spin_button_timer,
1327                                    (gpointer) spin);
1328     }
1329   gtk_spin_button_real_spin (spin, click_child == priv->up_panel ? step : -step);
1330
1331   gtk_widget_queue_draw (GTK_WIDGET (spin));
1332 }
1333
1334 static gint
1335 gtk_spin_button_button_press (GtkWidget      *widget,
1336                               GdkEventButton *event)
1337 {
1338   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1339   GtkSpinButtonPrivate *priv = spin->priv;
1340
1341   if (!priv->button)
1342     {
1343       if ((event->window == priv->down_panel) ||
1344           (event->window == priv->up_panel))
1345         {
1346           if (!gtk_widget_has_focus (widget))
1347             gtk_widget_grab_focus (widget);
1348           priv->button = event->button;
1349
1350           if (gtk_editable_get_editable (GTK_EDITABLE (widget))) {
1351             gtk_spin_button_update (spin);
1352
1353             if (event->button == GDK_BUTTON_PRIMARY)
1354               start_spinning (spin, event->window, gtk_adjustment_get_step_increment (priv->adjustment));
1355             else if (event->button == GDK_BUTTON_MIDDLE)
1356               start_spinning (spin, event->window, gtk_adjustment_get_page_increment (priv->adjustment));
1357             else
1358               priv->click_child = event->window;
1359           } else
1360             gtk_widget_error_bell (widget);
1361
1362           return TRUE;
1363         }
1364       else
1365         return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->button_press_event (widget, event);
1366     }
1367   return FALSE;
1368 }
1369
1370 static gint
1371 gtk_spin_button_button_release (GtkWidget      *widget,
1372                                 GdkEventButton *event)
1373 {
1374   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1375   GtkSpinButtonPrivate *priv = spin->priv;
1376
1377   if (event->button == priv->button)
1378     {
1379       GdkWindow *click_child = priv->click_child;
1380
1381       gtk_spin_button_stop_spinning (spin);
1382
1383       if (event->button == GDK_BUTTON_SECONDARY)
1384         {
1385           gdouble diff;
1386
1387           if (event->window == priv->down_panel &&
1388               click_child == event->window)
1389             {
1390               diff = gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment);
1391               if (diff > EPSILON)
1392                 gtk_spin_button_real_spin (spin, -diff);
1393             }
1394           else if (event->window == priv->up_panel &&
1395                    click_child == event->window)
1396             {
1397               diff = gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_value (priv->adjustment);
1398               if (diff > EPSILON)
1399                 gtk_spin_button_real_spin (spin, diff);
1400             }
1401         }
1402       gtk_widget_queue_draw (GTK_WIDGET (spin));
1403
1404       return TRUE;
1405     }
1406   else
1407     return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->button_release_event (widget, event);
1408 }
1409
1410 static gint
1411 gtk_spin_button_motion_notify (GtkWidget      *widget,
1412                                GdkEventMotion *event)
1413 {
1414   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1415   GtkSpinButtonPrivate *priv = spin->priv;
1416
1417   if (priv->button)
1418     return FALSE;
1419
1420   if (event->window == priv->down_panel ||
1421       event->window == priv->up_panel)
1422     {
1423       gdk_event_request_motions (event);
1424
1425       priv->in_child = event->window;
1426       gtk_widget_queue_draw (widget);
1427
1428       return FALSE;
1429     }
1430
1431   return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->motion_notify_event (widget, event);
1432 }
1433
1434 static gint
1435 gtk_spin_button_timer (GtkSpinButton *spin_button)
1436 {
1437   GtkSpinButtonPrivate *priv = spin_button->priv;
1438   gboolean retval = FALSE;
1439
1440   if (priv->timer)
1441     {
1442       if (priv->click_child == priv->up_panel)
1443         gtk_spin_button_real_spin (spin_button, priv->timer_step);
1444       else
1445         gtk_spin_button_real_spin (spin_button, -priv->timer_step);
1446
1447       if (priv->need_timer)
1448         {
1449           GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (spin_button));
1450           guint        timeout;
1451
1452           g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL);
1453
1454           priv->need_timer = FALSE;
1455           priv->timer = gdk_threads_add_timeout (timeout,
1456                                               (GSourceFunc) gtk_spin_button_timer,
1457                                               (gpointer) spin_button);
1458         }
1459       else
1460         {
1461           if (priv->climb_rate > 0.0 && priv->timer_step
1462               < gtk_adjustment_get_page_increment (priv->adjustment))
1463             {
1464               if (priv->timer_calls < MAX_TIMER_CALLS)
1465                 priv->timer_calls++;
1466               else
1467                 {
1468                   priv->timer_calls = 0;
1469                   priv->timer_step += priv->climb_rate;
1470                 }
1471             }
1472           retval = TRUE;
1473         }
1474     }
1475
1476   return retval;
1477 }
1478
1479 static void
1480 gtk_spin_button_value_changed (GtkAdjustment *adjustment,
1481                                GtkSpinButton *spin_button)
1482 {
1483   gboolean return_val;
1484
1485   g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
1486
1487   return_val = FALSE;
1488   g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
1489   if (return_val == FALSE)
1490     gtk_spin_button_default_output (spin_button);
1491
1492   g_signal_emit (spin_button, spinbutton_signals[VALUE_CHANGED], 0);
1493
1494   gtk_widget_queue_draw (GTK_WIDGET (spin_button));
1495
1496   g_object_notify (G_OBJECT (spin_button), "value");
1497 }
1498
1499 static void
1500 gtk_spin_button_real_change_value (GtkSpinButton *spin,
1501                                    GtkScrollType  scroll)
1502 {
1503   GtkSpinButtonPrivate *priv = spin->priv;
1504   gdouble old_value;
1505
1506   if (!gtk_editable_get_editable (GTK_EDITABLE (spin)))
1507     {
1508       gtk_widget_error_bell (GTK_WIDGET (spin));
1509       return;
1510     }
1511
1512   /* When the key binding is activated, there may be an outstanding
1513    * value, so we first have to commit what is currently written in
1514    * the spin buttons text entry. See #106574
1515    */
1516   gtk_spin_button_update (spin);
1517
1518   old_value = gtk_adjustment_get_value (priv->adjustment);
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 }