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