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