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