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