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