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