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