]> Pileus Git - ~andy/gtk/blob - gtk/gtkspinbutton.c
spinbutton: Get rid of gdk_drawable_get_size() usage
[~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_draw           (GtkWidget          *widget,
120                                             cairo_t            *cr);
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                                             cairo_t            *cr,
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->draw = gtk_spin_button_draw;
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.event_mask = gtk_widget_get_events (widget);
608   attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK 
609     | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK 
610     | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;
611
612   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
613
614   attributes.x = allocation.width - arrow_size - 2 * style->xthickness;
615   attributes.y = (allocation.height - requisition.height) / 2;
616   attributes.width = arrow_size + 2 * style->xthickness;
617   attributes.height = requisition.height;
618
619   priv->panel = gdk_window_new (gtk_widget_get_window (widget),
620                                 &attributes, attributes_mask);
621   gdk_window_set_user_data (priv->panel, widget);
622
623   gtk_style_set_background (style,
624                             priv->panel, GTK_STATE_NORMAL);
625
626   return_val = FALSE;
627   g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
628   if (return_val == FALSE)
629     gtk_spin_button_default_output (spin_button);
630
631   gtk_widget_queue_resize (GTK_WIDGET (spin_button));
632 }
633
634 static void
635 gtk_spin_button_unrealize (GtkWidget *widget)
636 {
637   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
638   GtkSpinButtonPrivate *priv = spin->priv;
639
640   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unrealize (widget);
641
642   if (priv->panel)
643     {
644       gdk_window_set_user_data (priv->panel, NULL);
645       gdk_window_destroy (priv->panel);
646       priv->panel = NULL;
647     }
648 }
649
650 static int
651 compute_double_length (double val, int digits)
652 {
653   int a;
654   int extra;
655
656   a = 1;
657   if (fabs (val) > 1.0)
658     a = floor (log10 (fabs (val))) + 1;  
659
660   extra = 0;
661   
662   /* The dot: */
663   if (digits > 0)
664     extra++;
665
666   /* The sign: */
667   if (val < 0)
668     extra++;
669
670   return a + digits + extra;
671 }
672
673 static void
674 gtk_spin_button_size_request (GtkWidget      *widget,
675                               GtkRequisition *requisition)
676 {
677   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
678   GtkSpinButtonPrivate *priv = spin_button->priv;
679   GtkEntry *entry = GTK_ENTRY (widget);
680   GtkStyle *style;
681   gint arrow_size;
682
683   style = gtk_widget_get_style (widget);
684
685   arrow_size = spin_button_get_arrow_size (spin_button);
686
687   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->size_request (widget, requisition);
688
689   if (entry->width_chars < 0)
690     {
691       PangoContext *context;
692       PangoFontMetrics *metrics;
693       gint width;
694       gint w;
695       gint string_len;
696       gint max_string_len;
697       gint digit_width;
698       gboolean interior_focus;
699       gint focus_width;
700       gint xborder, yborder;
701       GtkBorder inner_border;
702
703       gtk_widget_style_get (widget,
704                             "interior-focus", &interior_focus,
705                             "focus-line-width", &focus_width,
706                             NULL);
707
708       context = gtk_widget_get_pango_context (widget);
709       metrics = pango_context_get_metrics (context,
710                                            style->font_desc,
711                                            pango_context_get_language (context));
712
713       digit_width = pango_font_metrics_get_approximate_digit_width (metrics);
714       digit_width = PANGO_SCALE *
715         ((digit_width + PANGO_SCALE - 1) / PANGO_SCALE);
716
717       pango_font_metrics_unref (metrics);
718       
719       /* Get max of MIN_SPIN_BUTTON_WIDTH, size of upper, size of lower */
720       
721       width = MIN_SPIN_BUTTON_WIDTH;
722       max_string_len = MAX (10, compute_double_length (1e9 * priv->adjustment->step_increment,
723                                                        priv->digits));
724
725       string_len = compute_double_length (priv->adjustment->upper,
726                                           priv->digits);
727       w = PANGO_PIXELS (MIN (string_len, max_string_len) * digit_width);
728       width = MAX (width, w);
729       string_len = compute_double_length (priv->adjustment->lower,
730                                           priv->digits);
731       w = PANGO_PIXELS (MIN (string_len, max_string_len) * digit_width);
732       width = MAX (width, w);
733       
734       _gtk_entry_get_borders (entry, &xborder, &yborder);
735       _gtk_entry_effective_inner_border (entry, &inner_border);
736
737       requisition->width = width + xborder * 2 + inner_border.left + inner_border.right;
738     }
739
740   requisition->width += arrow_size + 2 * style->xthickness;
741 }
742
743 static void
744 gtk_spin_button_size_allocate (GtkWidget     *widget,
745                                GtkAllocation *allocation)
746 {
747   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
748   GtkSpinButtonPrivate *priv = spin->priv;
749   GtkAllocation panel_allocation;
750   GtkRequisition requisition;
751   gint arrow_size;
752   gint panel_width;
753
754   arrow_size = spin_button_get_arrow_size (spin);
755   panel_width = arrow_size + 2 * gtk_widget_get_style (widget)->xthickness;
756
757   gtk_size_request_get_size (GTK_SIZE_REQUEST (widget), &requisition, NULL);
758
759   gtk_widget_set_allocation (widget, allocation);
760
761   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
762     panel_allocation.x = 0;
763   else
764     panel_allocation.x = allocation->width - panel_width;
765
766   panel_allocation.width = panel_width;
767   panel_allocation.height = MIN (requisition.height, allocation->height);
768
769   panel_allocation.y = 0;
770
771   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->size_allocate (widget, allocation);
772
773   if (gtk_widget_get_realized (widget))
774     {
775       gdk_window_move_resize (priv->panel,
776                               panel_allocation.x,
777                               panel_allocation.y,
778                               panel_allocation.width,
779                               panel_allocation.height); 
780     }
781
782   gtk_widget_queue_draw (GTK_WIDGET (spin));
783 }
784
785 static gint
786 gtk_spin_button_draw (GtkWidget      *widget,
787                       cairo_t        *cr)
788 {
789   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
790   GtkSpinButtonPrivate *priv = spin->priv;
791
792   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->draw (widget, cr);
793
794   if (gtk_cairo_should_draw_window (cr, priv->panel))
795     {
796       GtkShadowType shadow_type;
797       int x, y;
798
799       shadow_type = spin_button_get_shadow_type (spin);
800
801       gdk_window_get_position (priv->panel, &x, &y);
802       cairo_translate (cr, x, y);
803
804       if (shadow_type != GTK_SHADOW_NONE)
805         {
806           GtkStateType state;
807
808           state = gtk_widget_has_focus (widget) ?
809             GTK_STATE_ACTIVE : gtk_widget_get_state (widget);
810
811           gtk_paint_box (gtk_widget_get_style (widget), cr,
812                          state, shadow_type,
813                          widget, "spinbutton",
814                          0, 0,
815                          gdk_window_get_width (priv->panel),
816                          gdk_window_get_height (priv->panel));
817         }
818
819       gtk_spin_button_draw_arrow (spin, cr, GTK_ARROW_UP);
820       gtk_spin_button_draw_arrow (spin, cr, GTK_ARROW_DOWN);
821     }
822   
823   return FALSE;
824 }
825
826 static gboolean
827 spin_button_at_limit (GtkSpinButton *spin_button,
828                      GtkArrowType   arrow)
829 {
830   GtkSpinButtonPrivate *priv = spin_button->priv;
831   GtkArrowType effective_arrow;
832
833   if (priv->wrap)
834     return FALSE;
835
836   if (priv->adjustment->step_increment > 0)
837     effective_arrow = arrow;
838   else
839     effective_arrow = arrow == GTK_ARROW_UP ? GTK_ARROW_DOWN : GTK_ARROW_UP; 
840   
841   if (effective_arrow == GTK_ARROW_UP &&
842       (priv->adjustment->upper - priv->adjustment->value <= EPSILON))
843     return TRUE;
844   
845   if (effective_arrow == GTK_ARROW_DOWN &&
846       (priv->adjustment->value - priv->adjustment->lower <= EPSILON))
847     return TRUE;
848   
849   return FALSE;
850 }
851
852 static void
853 gtk_spin_button_draw_arrow (GtkSpinButton *spin_button, 
854                             cairo_t       *cr,
855                             GtkArrowType   arrow_type)
856 {
857   GtkSpinButtonPrivate *priv;
858   GtkRequisition requisition;
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   style = gtk_widget_get_style (widget);
875   gtk_size_request_get_size (GTK_SIZE_REQUEST (widget), &requisition, NULL);
876
877   width = spin_button_get_arrow_size (spin_button) + 2 * style->xthickness;
878
879   if (arrow_type == GTK_ARROW_UP)
880     {
881       x = 0;
882       y = 0;
883
884       height = requisition.height / 2;
885     }
886   else
887     {
888       x = 0;
889       y = requisition.height / 2;
890
891       height = (requisition.height + 1) / 2;
892     }
893
894   if (spin_button_at_limit (spin_button, arrow_type))
895     {
896       shadow_type = GTK_SHADOW_OUT;
897       state_type = GTK_STATE_INSENSITIVE;
898     }
899   else
900     {
901       if (priv->click_child == arrow_type)
902         {
903           state_type = GTK_STATE_ACTIVE;
904           shadow_type = GTK_SHADOW_IN;
905         }
906       else
907         {
908           if (priv->in_child == arrow_type &&
909               priv->click_child == NO_ARROW)
910             {
911               state_type = GTK_STATE_PRELIGHT;
912             }
913           else
914             {
915               state_type = gtk_widget_get_state (widget);
916             }
917           
918           shadow_type = GTK_SHADOW_OUT;
919         }
920     }
921   
922   gtk_paint_box (style, cr,
923                  state_type, shadow_type,
924                  widget,
925                  (arrow_type == GTK_ARROW_UP)? "spinbutton_up" : "spinbutton_down",
926                  x, y, width, height);
927
928   height = requisition.height;
929
930   if (arrow_type == GTK_ARROW_DOWN)
931     {
932       y = height / 2;
933       height = height - y - 2;
934     }
935   else
936     {
937       y = 2;
938       height = height / 2 - 2;
939     }
940
941   width -= 3;
942
943   if (widget && gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
944     x = 2;
945   else
946     x = 1;
947
948   w = width / 2;
949   w -= w % 2 - 1; /* force odd */
950   h = (w + 1) / 2;
951   
952   x += (width - w) / 2;
953   y += (height - h) / 2;
954   
955   height = h;
956   width = w;
957
958   gtk_paint_arrow (style, cr,
959                    state_type, shadow_type, 
960                    widget, "spinbutton",
961                    arrow_type, TRUE, 
962                    x, y, width, height);
963 }
964
965 static gint
966 gtk_spin_button_enter_notify (GtkWidget        *widget,
967                               GdkEventCrossing *event)
968 {
969   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
970   GtkSpinButtonPrivate *priv = spin->priv;
971   GtkRequisition requisition;
972
973   if (event->window == priv->panel)
974     {
975       GdkDevice *device;
976       gint x;
977       gint y;
978
979       device = gdk_event_get_device ((GdkEvent *) event);
980       gdk_window_get_device_position (priv->panel, device, &x, &y, NULL);
981
982       gtk_size_request_get_size (GTK_SIZE_REQUEST (widget), &requisition, NULL);
983
984       if (y <= requisition.height / 2)
985         priv->in_child = GTK_ARROW_UP;
986       else
987         priv->in_child = GTK_ARROW_DOWN;
988
989       gtk_widget_queue_draw (GTK_WIDGET (spin));
990     }
991
992   if (GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->enter_notify_event)
993     return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->enter_notify_event (widget, event);
994
995   return FALSE;
996 }
997
998 static gint
999 gtk_spin_button_leave_notify (GtkWidget        *widget,
1000                               GdkEventCrossing *event)
1001 {
1002   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1003   GtkSpinButtonPrivate *priv = spin->priv;
1004
1005   priv->in_child = NO_ARROW;
1006   gtk_widget_queue_draw (GTK_WIDGET (spin));
1007  
1008   if (GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->leave_notify_event)
1009     return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->leave_notify_event (widget, event);
1010
1011   return FALSE;
1012 }
1013
1014 static gint
1015 gtk_spin_button_focus_out (GtkWidget     *widget,
1016                            GdkEventFocus *event)
1017 {
1018   if (GTK_ENTRY (widget)->editable)
1019     gtk_spin_button_update (GTK_SPIN_BUTTON (widget));
1020
1021   return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->focus_out_event (widget, event);
1022 }
1023
1024 static void
1025 gtk_spin_button_grab_notify (GtkWidget *widget,
1026                              gboolean   was_grabbed)
1027 {
1028   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1029
1030   if (!was_grabbed)
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_state_changed (GtkWidget    *widget,
1039                                GtkStateType  previous_state)
1040 {
1041   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1042
1043   if (!gtk_widget_is_sensitive (widget))
1044     {
1045       gtk_spin_button_stop_spinning (spin);    
1046       gtk_widget_queue_draw (GTK_WIDGET (spin));
1047     }
1048 }
1049
1050 static void
1051 gtk_spin_button_style_set (GtkWidget *widget,
1052                            GtkStyle  *previous_style)
1053 {
1054   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1055   GtkSpinButtonPrivate *priv = spin->priv;
1056
1057   if (previous_style && gtk_widget_get_realized (widget))
1058     gtk_style_set_background (gtk_widget_get_style (widget),
1059                               priv->panel, GTK_STATE_NORMAL);
1060
1061   GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->style_set (widget, previous_style);
1062 }
1063
1064
1065 static gint
1066 gtk_spin_button_scroll (GtkWidget      *widget,
1067                         GdkEventScroll *event)
1068 {
1069   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1070   GtkSpinButtonPrivate *priv = spin->priv;
1071
1072   if (event->direction == GDK_SCROLL_UP)
1073     {
1074       if (!gtk_widget_has_focus (widget))
1075         gtk_widget_grab_focus (widget);
1076       gtk_spin_button_real_spin (spin, priv->adjustment->step_increment);
1077     }
1078   else if (event->direction == GDK_SCROLL_DOWN)
1079     {
1080       if (!gtk_widget_has_focus (widget))
1081         gtk_widget_grab_focus (widget);
1082       gtk_spin_button_real_spin (spin, -priv->adjustment->step_increment); 
1083     }
1084   else
1085     return FALSE;
1086
1087   return TRUE;
1088 }
1089
1090 static void
1091 gtk_spin_button_stop_spinning (GtkSpinButton *spin)
1092 {
1093   GtkSpinButtonPrivate *priv = spin->priv;
1094
1095   if (priv->timer)
1096     {
1097       g_source_remove (priv->timer);
1098       priv->timer = 0;
1099       priv->timer_calls = 0;
1100       priv->need_timer = FALSE;
1101     }
1102
1103   priv->button = 0;
1104   priv->timer = 0;
1105   priv->timer_step = priv->adjustment->step_increment;
1106   priv->timer_calls = 0;
1107
1108   priv->click_child = NO_ARROW;
1109   priv->button = 0;
1110 }
1111
1112 static void
1113 start_spinning (GtkSpinButton *spin,
1114                 GtkArrowType   click_child,
1115                 gdouble        step)
1116 {
1117   GtkSpinButtonPrivate *priv;
1118
1119   g_return_if_fail (click_child == GTK_ARROW_UP || click_child == GTK_ARROW_DOWN);
1120
1121   priv = spin->priv;
1122
1123   priv->click_child = click_child;
1124
1125   if (!priv->timer)
1126     {
1127       GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (spin));
1128       guint        timeout;
1129
1130       g_object_get (settings, "gtk-timeout-initial", &timeout, NULL);
1131
1132       priv->timer_step = step;
1133       priv->need_timer = TRUE;
1134       priv->timer = gdk_threads_add_timeout (timeout,
1135                                    (GSourceFunc) gtk_spin_button_timer,
1136                                    (gpointer) spin);
1137     }
1138   gtk_spin_button_real_spin (spin, click_child == GTK_ARROW_UP ? step : -step);
1139
1140   gtk_widget_queue_draw (GTK_WIDGET (spin));
1141 }
1142
1143 static gint
1144 gtk_spin_button_button_press (GtkWidget      *widget,
1145                               GdkEventButton *event)
1146 {
1147   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1148   GtkSpinButtonPrivate *priv = spin->priv;
1149
1150   if (!priv->button)
1151     {
1152       if (event->window == priv->panel)
1153         {
1154           GtkRequisition requisition;
1155
1156           if (!gtk_widget_has_focus (widget))
1157             gtk_widget_grab_focus (widget);
1158           priv->button = event->button;
1159           
1160           if (GTK_ENTRY (widget)->editable)
1161             gtk_spin_button_update (spin);
1162           
1163           gtk_size_request_get_size (GTK_SIZE_REQUEST (widget), &requisition, NULL);
1164
1165           if (event->y <= requisition.height / 2)
1166             {
1167               if (event->button == 1)
1168                 start_spinning (spin, GTK_ARROW_UP, priv->adjustment->step_increment);
1169               else if (event->button == 2)
1170                 start_spinning (spin, GTK_ARROW_UP, priv->adjustment->page_increment);
1171               else
1172                 priv->click_child = GTK_ARROW_UP;
1173             }
1174           else 
1175             {
1176               if (event->button == 1)
1177                 start_spinning (spin, GTK_ARROW_DOWN, priv->adjustment->step_increment);
1178               else if (event->button == 2)
1179                 start_spinning (spin, GTK_ARROW_DOWN, priv->adjustment->page_increment);
1180               else
1181                 priv->click_child = GTK_ARROW_DOWN;
1182             }
1183           return TRUE;
1184         }
1185       else
1186         return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->button_press_event (widget, event);
1187     }
1188   return FALSE;
1189 }
1190
1191 static gint
1192 gtk_spin_button_button_release (GtkWidget      *widget,
1193                                 GdkEventButton *event)
1194 {
1195   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1196   GtkSpinButtonPrivate *priv = spin->priv;
1197   gint arrow_size;
1198
1199   arrow_size = spin_button_get_arrow_size (spin);
1200
1201   if (event->button == priv->button)
1202     {
1203       int click_child = priv->click_child;
1204
1205       gtk_spin_button_stop_spinning (spin);
1206
1207       if (event->button == 3)
1208         {
1209           GtkRequisition requisition;
1210
1211           gtk_size_request_get_size (GTK_SIZE_REQUEST (widget), &requisition, NULL);
1212
1213           if (event->y >= 0 && event->x >= 0 && 
1214               event->y <= requisition.height &&
1215               event->x <= arrow_size + 2 * gtk_widget_get_style (widget)->xthickness)
1216             {
1217               if (click_child == GTK_ARROW_UP &&
1218                   event->y <= requisition.height / 2)
1219                 {
1220                   gdouble diff;
1221
1222                   diff = priv->adjustment->upper - priv->adjustment->value;
1223                   if (diff > EPSILON)
1224                     gtk_spin_button_real_spin (spin, diff);
1225                 }
1226               else if (click_child == GTK_ARROW_DOWN &&
1227                        event->y > requisition.height / 2)
1228                 {
1229                   gdouble diff;
1230
1231                   diff = priv->adjustment->value - priv->adjustment->lower;
1232                   if (diff > EPSILON)
1233                     gtk_spin_button_real_spin (spin, -diff);
1234                 }
1235             }
1236         }                 
1237       gtk_widget_queue_draw (GTK_WIDGET (spin));
1238
1239       return TRUE;
1240     }
1241   else
1242     return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->button_release_event (widget, event);
1243 }
1244
1245 static gint
1246 gtk_spin_button_motion_notify (GtkWidget      *widget,
1247                                GdkEventMotion *event)
1248 {
1249   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1250   GtkSpinButtonPrivate *priv = spin->priv;
1251
1252   if (priv->button)
1253     return FALSE;
1254
1255   if (event->window == priv->panel)
1256     {
1257       GtkRequisition requisition;
1258       gint y = event->y;
1259
1260       gdk_event_request_motions (event);
1261
1262       gtk_size_request_get_size (GTK_SIZE_REQUEST (widget), &requisition, NULL);
1263
1264       if (y <= requisition.height / 2 &&
1265           priv->in_child == GTK_ARROW_DOWN)
1266         {
1267           priv->in_child = GTK_ARROW_UP;
1268           gtk_widget_queue_draw (GTK_WIDGET (spin));
1269         }
1270       else if (y > requisition.height / 2 &&
1271           priv->in_child == GTK_ARROW_UP)
1272         {
1273           priv->in_child = GTK_ARROW_DOWN;
1274           gtk_widget_queue_draw (GTK_WIDGET (spin));
1275         }
1276       
1277       return FALSE;
1278     }
1279           
1280   return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->motion_notify_event (widget, event);
1281 }
1282
1283 static gint
1284 gtk_spin_button_timer (GtkSpinButton *spin_button)
1285 {
1286   GtkSpinButtonPrivate *priv = spin_button->priv;
1287   gboolean retval = FALSE;
1288
1289   if (priv->timer)
1290     {
1291       if (priv->click_child == GTK_ARROW_UP)
1292         gtk_spin_button_real_spin (spin_button, priv->timer_step);
1293       else
1294         gtk_spin_button_real_spin (spin_button, -priv->timer_step);
1295
1296       if (priv->need_timer)
1297         {
1298           GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (spin_button));
1299           guint        timeout;
1300
1301           g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL);
1302
1303           priv->need_timer = FALSE;
1304           priv->timer = gdk_threads_add_timeout (timeout,
1305                                               (GSourceFunc) gtk_spin_button_timer, 
1306                                               (gpointer) spin_button);
1307         }
1308       else 
1309         {
1310           if (priv->climb_rate > 0.0 && priv->timer_step 
1311               < priv->adjustment->page_increment)
1312             {
1313               if (priv->timer_calls < MAX_TIMER_CALLS)
1314                 priv->timer_calls++;
1315               else 
1316                 {
1317                   priv->timer_calls = 0;
1318                   priv->timer_step += priv->climb_rate;
1319                 }
1320             }
1321           retval = TRUE;
1322         }
1323     }
1324
1325   return retval;
1326 }
1327
1328 static void
1329 gtk_spin_button_value_changed (GtkAdjustment *adjustment,
1330                                GtkSpinButton *spin_button)
1331 {
1332   gboolean return_val;
1333
1334   g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
1335
1336   return_val = FALSE;
1337   g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
1338   if (return_val == FALSE)
1339     gtk_spin_button_default_output (spin_button);
1340
1341   g_signal_emit (spin_button, spinbutton_signals[VALUE_CHANGED], 0);
1342
1343   gtk_widget_queue_draw (GTK_WIDGET (spin_button));
1344   
1345   g_object_notify (G_OBJECT (spin_button), "value");
1346 }
1347
1348 static void
1349 gtk_spin_button_real_change_value (GtkSpinButton *spin,
1350                                    GtkScrollType  scroll)
1351 {
1352   GtkSpinButtonPrivate *priv = spin->priv;
1353   gdouble old_value;
1354
1355   /* When the key binding is activated, there may be an outstanding
1356    * value, so we first have to commit what is currently written in
1357    * the spin buttons text entry. See #106574
1358    */
1359   gtk_spin_button_update (spin);
1360
1361   old_value = priv->adjustment->value;
1362
1363   /* We don't test whether the entry is editable, since
1364    * this key binding conceptually corresponds to changing
1365    * the value with the buttons using the mouse, which
1366    * we allow for non-editable spin buttons.
1367    */
1368   switch (scroll)
1369     {
1370     case GTK_SCROLL_STEP_BACKWARD:
1371     case GTK_SCROLL_STEP_DOWN:
1372     case GTK_SCROLL_STEP_LEFT:
1373       gtk_spin_button_real_spin (spin, -priv->timer_step);
1374
1375       if (priv->climb_rate > 0.0 && priv->timer_step
1376           < priv->adjustment->page_increment)
1377         {
1378           if (priv->timer_calls < MAX_TIMER_CALLS)
1379             priv->timer_calls++;
1380           else 
1381             {
1382               priv->timer_calls = 0;
1383               priv->timer_step += priv->climb_rate;
1384             }
1385         }
1386       break;
1387       
1388     case GTK_SCROLL_STEP_FORWARD:
1389     case GTK_SCROLL_STEP_UP:
1390     case GTK_SCROLL_STEP_RIGHT:
1391       gtk_spin_button_real_spin (spin, priv->timer_step);
1392
1393       if (priv->climb_rate > 0.0 && priv->timer_step
1394           < priv->adjustment->page_increment)
1395         {
1396           if (priv->timer_calls < MAX_TIMER_CALLS)
1397             priv->timer_calls++;
1398           else 
1399             {
1400               priv->timer_calls = 0;
1401               priv->timer_step += priv->climb_rate;
1402             }
1403         }
1404       break;
1405       
1406     case GTK_SCROLL_PAGE_BACKWARD:
1407     case GTK_SCROLL_PAGE_DOWN:
1408     case GTK_SCROLL_PAGE_LEFT:
1409       gtk_spin_button_real_spin (spin, -priv->adjustment->page_increment);
1410       break;
1411       
1412     case GTK_SCROLL_PAGE_FORWARD:
1413     case GTK_SCROLL_PAGE_UP:
1414     case GTK_SCROLL_PAGE_RIGHT:
1415       gtk_spin_button_real_spin (spin, priv->adjustment->page_increment);
1416       break;
1417       
1418     case GTK_SCROLL_START:
1419       {
1420         gdouble diff = priv->adjustment->value - priv->adjustment->lower;
1421         if (diff > EPSILON)
1422           gtk_spin_button_real_spin (spin, -diff);
1423         break;
1424       }
1425       
1426     case GTK_SCROLL_END:
1427       {
1428         gdouble diff = priv->adjustment->upper - priv->adjustment->value;
1429         if (diff > EPSILON)
1430           gtk_spin_button_real_spin (spin, diff);
1431         break;
1432       }
1433       
1434     default:
1435       g_warning ("Invalid scroll type %d for GtkSpinButton::change-value", scroll);
1436       break;
1437     }
1438   
1439   gtk_spin_button_update (spin);
1440
1441   if (priv->adjustment->value == old_value)
1442     gtk_widget_error_bell (GTK_WIDGET (spin));
1443 }
1444
1445 static gint
1446 gtk_spin_button_key_release (GtkWidget   *widget,
1447                              GdkEventKey *event)
1448 {
1449   GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1450   GtkSpinButtonPrivate *priv = spin->priv;
1451
1452   /* We only get a release at the end of a key repeat run, so reset the timer_step */
1453   priv->timer_step = priv->adjustment->step_increment;
1454   priv->timer_calls = 0;
1455
1456   return TRUE;
1457 }
1458
1459 static void
1460 gtk_spin_button_snap (GtkSpinButton *spin_button,
1461                       gdouble        val)
1462 {
1463   GtkSpinButtonPrivate *priv = spin_button->priv;
1464   gdouble inc;
1465   gdouble tmp;
1466
1467   inc = priv->adjustment->step_increment;
1468   if (inc == 0)
1469     return;
1470
1471   tmp = (val - priv->adjustment->lower) / inc;
1472   if (tmp - floor (tmp) < ceil (tmp) - tmp)
1473     val = priv->adjustment->lower + floor (tmp) * inc;
1474   else
1475     val = priv->adjustment->lower + ceil (tmp) * inc;
1476
1477   gtk_spin_button_set_value (spin_button, val);
1478 }
1479
1480 static void
1481 gtk_spin_button_activate (GtkEntry *entry)
1482 {
1483   if (entry->editable)
1484     gtk_spin_button_update (GTK_SPIN_BUTTON (entry));
1485
1486   /* Chain up so that entry->activates_default is honored */
1487   GTK_ENTRY_CLASS (gtk_spin_button_parent_class)->activate (entry);
1488 }
1489
1490 static void
1491 gtk_spin_button_get_text_area_size (GtkEntry *entry,
1492                                     gint     *x,
1493                                     gint     *y,
1494                                     gint     *width,
1495                                     gint     *height)
1496 {
1497   gint arrow_size;
1498   gint panel_width;
1499
1500   GTK_ENTRY_CLASS (gtk_spin_button_parent_class)->get_text_area_size (entry, x, y, width, height);
1501
1502   arrow_size = spin_button_get_arrow_size (GTK_SPIN_BUTTON (entry));
1503   panel_width = arrow_size + 2 * gtk_widget_get_style (GTK_WIDGET (entry))->xthickness;
1504
1505   if (width)
1506     *width -= panel_width;
1507
1508   if (gtk_widget_get_direction (GTK_WIDGET (entry)) == GTK_TEXT_DIR_RTL && x)
1509     *x += panel_width;
1510 }
1511
1512 static void
1513 gtk_spin_button_insert_text (GtkEditable *editable,
1514                              const gchar *new_text,
1515                              gint         new_text_length,
1516                              gint        *position)
1517 {
1518   GtkEntry *entry = GTK_ENTRY (editable);
1519   GtkSpinButton *spin = GTK_SPIN_BUTTON (editable);
1520   GtkSpinButtonPrivate *priv = spin->priv;
1521   GtkEditableInterface *parent_editable_iface;
1522
1523   parent_editable_iface = g_type_interface_peek (gtk_spin_button_parent_class,
1524                                                  GTK_TYPE_EDITABLE);
1525
1526   if (priv->numeric)
1527     {
1528       struct lconv *lc;
1529       gboolean sign;
1530       gint dotpos = -1;
1531       gint i;
1532       guint32 pos_sign;
1533       guint32 neg_sign;
1534       gint entry_length;
1535       const gchar *entry_text;
1536
1537       entry_length = gtk_entry_get_text_length (entry);
1538       entry_text = gtk_entry_get_text (entry);
1539
1540       lc = localeconv ();
1541
1542       if (*(lc->negative_sign))
1543         neg_sign = *(lc->negative_sign);
1544       else 
1545         neg_sign = '-';
1546
1547       if (*(lc->positive_sign))
1548         pos_sign = *(lc->positive_sign);
1549       else 
1550         pos_sign = '+';
1551
1552 #ifdef G_OS_WIN32
1553       /* Workaround for bug caused by some Windows application messing
1554        * up the positive sign of the current locale, more specifically
1555        * HKEY_CURRENT_USER\Control Panel\International\sPositiveSign.
1556        * See bug #330743 and for instance
1557        * http://www.msnewsgroups.net/group/microsoft.public.dotnet.languages.csharp/topic36024.aspx
1558        *
1559        * I don't know if the positive sign always gets bogusly set to
1560        * a digit when the above Registry value is corrupted as
1561        * described. (In my test case, it got set to "8", and in the
1562        * bug report above it presumably was set ot "0".) Probably it
1563        * might get set to almost anything? So how to distinguish a
1564        * bogus value from some correct one for some locale? That is
1565        * probably hard, but at least we should filter out the
1566        * digits...
1567        */
1568       if (pos_sign >= '0' && pos_sign <= '9')
1569         pos_sign = '+';
1570 #endif
1571
1572       for (sign=0, i=0; i<entry_length; i++)
1573         if ((entry_text[i] == neg_sign) ||
1574             (entry_text[i] == pos_sign))
1575           {
1576             sign = 1;
1577             break;
1578           }
1579
1580       if (sign && !(*position))
1581         return;
1582
1583       for (dotpos=-1, i=0; i<entry_length; i++)
1584         if (entry_text[i] == *(lc->decimal_point))
1585           {
1586             dotpos = i;
1587             break;
1588           }
1589
1590       if (dotpos > -1 && *position > dotpos &&
1591           (gint)priv->digits - entry_length
1592             + dotpos - new_text_length + 1 < 0)
1593         return;
1594
1595       for (i = 0; i < new_text_length; i++)
1596         {
1597           if (new_text[i] == neg_sign || new_text[i] == pos_sign)
1598             {
1599               if (sign || (*position) || i)
1600                 return;
1601               sign = TRUE;
1602             }
1603           else if (new_text[i] == *(lc->decimal_point))
1604             {
1605               if (!priv->digits || dotpos > -1 ||
1606                   (new_text_length - 1 - i + entry_length
1607                     - *position > (gint)priv->digits))
1608                 return;
1609               dotpos = *position + i;
1610             }
1611           else if (new_text[i] < 0x30 || new_text[i] > 0x39)
1612             return;
1613         }
1614     }
1615
1616   parent_editable_iface->insert_text (editable, new_text,
1617                                       new_text_length, position);
1618 }
1619
1620 static void
1621 gtk_spin_button_real_spin (GtkSpinButton *spin_button,
1622                            gdouble        increment)
1623 {
1624   GtkSpinButtonPrivate *priv = spin_button->priv;
1625   GtkAdjustment *adj;
1626   gdouble new_value = 0.0;
1627   gboolean wrapped = FALSE;
1628
1629   adj = priv->adjustment;
1630
1631   new_value = adj->value + increment;
1632
1633   if (increment > 0)
1634     {
1635       if (priv->wrap)
1636         {
1637           if (fabs (adj->value - adj->upper) < EPSILON)
1638             {
1639               new_value = adj->lower;
1640               wrapped = TRUE;
1641             }
1642           else if (new_value > adj->upper)
1643             new_value = adj->upper;
1644         }
1645       else
1646         new_value = MIN (new_value, adj->upper);
1647     }
1648   else if (increment < 0) 
1649     {
1650       if (priv->wrap)
1651         {
1652           if (fabs (adj->value - adj->lower) < EPSILON)
1653             {
1654               new_value = adj->upper;
1655               wrapped = TRUE;
1656             }
1657           else if (new_value < adj->lower)
1658             new_value = adj->lower;
1659         }
1660       else
1661         new_value = MAX (new_value, adj->lower);
1662     }
1663
1664   if (fabs (new_value - adj->value) > EPSILON)
1665     gtk_adjustment_set_value (adj, new_value);
1666
1667   if (wrapped)
1668     g_signal_emit (spin_button, spinbutton_signals[WRAPPED], 0);
1669
1670   gtk_widget_queue_draw (GTK_WIDGET (spin_button));
1671 }
1672
1673 static gint
1674 gtk_spin_button_default_input (GtkSpinButton *spin_button,
1675                                gdouble       *new_val)
1676 {
1677   gchar *err = NULL;
1678
1679   *new_val = g_strtod (gtk_entry_get_text (GTK_ENTRY (spin_button)), &err);
1680   if (*err)
1681     return GTK_INPUT_ERROR;
1682   else
1683     return FALSE;
1684 }
1685
1686 static gint
1687 gtk_spin_button_default_output (GtkSpinButton *spin_button)
1688 {
1689   GtkSpinButtonPrivate *priv = spin_button->priv;
1690
1691   gchar *buf = g_strdup_printf ("%0.*f", priv->digits, priv->adjustment->value);
1692
1693   if (strcmp (buf, gtk_entry_get_text (GTK_ENTRY (spin_button))))
1694     gtk_entry_set_text (GTK_ENTRY (spin_button), buf);
1695   g_free (buf);
1696   return FALSE;
1697 }
1698
1699
1700 /***********************************************************
1701  ***********************************************************
1702  ***                  Public interface                   ***
1703  ***********************************************************
1704  ***********************************************************/
1705
1706
1707 /**
1708  * gtk_spin_button_configure:
1709  * @spin_button: a #GtkSpinButton
1710  * @adjustment: (allow-none):  a #GtkAdjustment.
1711  * @climb_rate: the new climb rate.
1712  * @digits: the number of decimal places to display in the spin button.
1713  *
1714  * Changes the properties of an existing spin button. The adjustment, climb rate,
1715  * and number of decimal places are all changed accordingly, after this function call.
1716  */
1717 void
1718 gtk_spin_button_configure (GtkSpinButton  *spin_button,
1719                            GtkAdjustment  *adjustment,
1720                            gdouble         climb_rate,
1721                            guint           digits)
1722 {
1723   GtkSpinButtonPrivate *priv;
1724
1725   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
1726
1727   priv = spin_button->priv;
1728
1729   if (adjustment)
1730     gtk_spin_button_set_adjustment (spin_button, adjustment);
1731   else
1732     adjustment = priv->adjustment;
1733
1734   g_object_freeze_notify (G_OBJECT (spin_button));
1735   if (priv->digits != digits) 
1736     {
1737       priv->digits = digits;
1738       g_object_notify (G_OBJECT (spin_button), "digits");
1739     }
1740
1741   if (priv->climb_rate != climb_rate)
1742     {
1743       priv->climb_rate = climb_rate;
1744       g_object_notify (G_OBJECT (spin_button), "climb-rate");
1745     }
1746   g_object_thaw_notify (G_OBJECT (spin_button));
1747
1748   gtk_adjustment_value_changed (adjustment);
1749 }
1750
1751 GtkWidget *
1752 gtk_spin_button_new (GtkAdjustment *adjustment,
1753                      gdouble        climb_rate,
1754                      guint          digits)
1755 {
1756   GtkSpinButton *spin;
1757
1758   if (adjustment)
1759     g_return_val_if_fail (GTK_IS_ADJUSTMENT (adjustment), NULL);
1760
1761   spin = g_object_new (GTK_TYPE_SPIN_BUTTON, NULL);
1762
1763   gtk_spin_button_configure (spin, adjustment, climb_rate, digits);
1764
1765   return GTK_WIDGET (spin);
1766 }
1767
1768 /**
1769  * gtk_spin_button_new_with_range:
1770  * @min: Minimum allowable value
1771  * @max: Maximum allowable value
1772  * @step: Increment added or subtracted by spinning the widget
1773  * 
1774  * This is a convenience constructor that allows creation of a numeric 
1775  * #GtkSpinButton without manually creating an adjustment. The value is 
1776  * initially set to the minimum value and a page increment of 10 * @step
1777  * is the default. The precision of the spin button is equivalent to the 
1778  * precision of @step. 
1779  * 
1780  * Note that the way in which the precision is derived works best if @step 
1781  * is a power of ten. If the resulting precision is not suitable for your 
1782  * needs, use gtk_spin_button_set_digits() to correct it.
1783  * 
1784  * Return value: The new spin button as a #GtkWidget.
1785  **/
1786 GtkWidget *
1787 gtk_spin_button_new_with_range (gdouble min,
1788                                 gdouble max,
1789                                 gdouble step)
1790 {
1791   GtkObject *adj;
1792   GtkSpinButton *spin;
1793   gint digits;
1794
1795   g_return_val_if_fail (min <= max, NULL);
1796   g_return_val_if_fail (step != 0.0, NULL);
1797
1798   spin = g_object_new (GTK_TYPE_SPIN_BUTTON, NULL);
1799
1800   adj = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
1801
1802   if (fabs (step) >= 1.0 || step == 0.0)
1803     digits = 0;
1804   else {
1805     digits = abs ((gint) floor (log10 (fabs (step))));
1806     if (digits > MAX_DIGITS)
1807       digits = MAX_DIGITS;
1808   }
1809
1810   gtk_spin_button_configure (spin, GTK_ADJUSTMENT (adj), step, digits);
1811
1812   gtk_spin_button_set_numeric (spin, TRUE);
1813
1814   return GTK_WIDGET (spin);
1815 }
1816
1817 /* Callback used when the spin button's adjustment changes.  We need to redraw
1818  * the arrows when the adjustment's range changes, and reevaluate our size request.
1819  */
1820 static void
1821 adjustment_changed_cb (GtkAdjustment *adjustment, gpointer data)
1822 {
1823   GtkSpinButton *spin_button = GTK_SPIN_BUTTON (data);
1824   GtkSpinButtonPrivate *priv = spin_button->priv;
1825
1826   priv->timer_step = priv->adjustment->step_increment;
1827   gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1828 }
1829
1830 /**
1831  * gtk_spin_button_set_adjustment:
1832  * @spin_button: a #GtkSpinButton
1833  * @adjustment: a #GtkAdjustment to replace the existing adjustment
1834  * 
1835  * Replaces the #GtkAdjustment associated with @spin_button.
1836  **/
1837 void
1838 gtk_spin_button_set_adjustment (GtkSpinButton *spin_button,
1839                                 GtkAdjustment *adjustment)
1840 {
1841   GtkSpinButtonPrivate *priv;
1842
1843   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
1844
1845   priv = spin_button->priv;
1846
1847   if (priv->adjustment != adjustment)
1848     {
1849       if (priv->adjustment)
1850         {
1851           g_signal_handlers_disconnect_by_func (priv->adjustment,
1852                                                 gtk_spin_button_value_changed,
1853                                                 spin_button);
1854           g_signal_handlers_disconnect_by_func (priv->adjustment,
1855                                                 adjustment_changed_cb,
1856                                                 spin_button);
1857           g_object_unref (priv->adjustment);
1858         }
1859       priv->adjustment = adjustment;
1860       if (adjustment)
1861         {
1862           g_object_ref_sink (adjustment);
1863           g_signal_connect (adjustment, "value-changed",
1864                             G_CALLBACK (gtk_spin_button_value_changed),
1865                             spin_button);
1866           g_signal_connect (adjustment, "changed",
1867                             G_CALLBACK (adjustment_changed_cb),
1868                             spin_button);
1869           priv->timer_step = priv->adjustment->step_increment;
1870         }
1871
1872       gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1873     }
1874
1875   g_object_notify (G_OBJECT (spin_button), "adjustment");
1876 }
1877
1878 /**
1879  * gtk_spin_button_get_adjustment:
1880  * @spin_button: a #GtkSpinButton
1881  * 
1882  * Get the adjustment associated with a #GtkSpinButton
1883  * 
1884  * Return value: (transfer none): the #GtkAdjustment of @spin_button
1885  **/
1886 GtkAdjustment *
1887 gtk_spin_button_get_adjustment (GtkSpinButton *spin_button)
1888 {
1889   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), NULL);
1890
1891   return spin_button->priv->adjustment;
1892 }
1893
1894 /**
1895  * gtk_spin_button_set_digits:
1896  * @spin_button: a #GtkSpinButton
1897  * @digits: the number of digits after the decimal point to be displayed for the spin button's value
1898  * 
1899  * Set the precision to be displayed by @spin_button. Up to 20 digit precision
1900  * is allowed.
1901  **/
1902 void
1903 gtk_spin_button_set_digits (GtkSpinButton *spin_button,
1904                             guint          digits)
1905 {
1906   GtkSpinButtonPrivate *priv;
1907
1908   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
1909
1910   priv = spin_button->priv;
1911
1912   if (priv->digits != digits)
1913     {
1914       priv->digits = digits;
1915       gtk_spin_button_value_changed (priv->adjustment, spin_button);
1916       g_object_notify (G_OBJECT (spin_button), "digits");
1917       
1918       /* since lower/upper may have changed */
1919       gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1920     }
1921 }
1922
1923 /**
1924  * gtk_spin_button_get_digits:
1925  * @spin_button: a #GtkSpinButton
1926  *
1927  * Fetches the precision of @spin_button. See gtk_spin_button_set_digits().
1928  *
1929  * Returns: the current precision
1930  **/
1931 guint
1932 gtk_spin_button_get_digits (GtkSpinButton *spin_button)
1933 {
1934   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0);
1935
1936   return spin_button->priv->digits;
1937 }
1938
1939 /**
1940  * gtk_spin_button_set_increments:
1941  * @spin_button: a #GtkSpinButton
1942  * @step: increment applied for a button 1 press.
1943  * @page: increment applied for a button 2 press.
1944  * 
1945  * Sets the step and page increments for spin_button.  This affects how 
1946  * quickly the value changes when the spin button's arrows are activated.
1947  **/
1948 void
1949 gtk_spin_button_set_increments (GtkSpinButton *spin_button,
1950                                 gdouble        step,
1951                                 gdouble        page)
1952 {
1953   GtkSpinButtonPrivate *priv;
1954
1955   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
1956
1957   priv = spin_button->priv;
1958
1959   priv->adjustment->step_increment = step;
1960   priv->adjustment->page_increment = page;
1961 }
1962
1963 /**
1964  * gtk_spin_button_get_increments:
1965  * @spin_button: a #GtkSpinButton
1966  * @step: (allow-none): location to store step increment, or %NULL
1967  * @page: (allow-none): location to store page increment, or %NULL
1968  *
1969  * Gets the current step and page the increments used by @spin_button. See
1970  * gtk_spin_button_set_increments().
1971  **/
1972 void
1973 gtk_spin_button_get_increments (GtkSpinButton *spin_button,
1974                                 gdouble       *step,
1975                                 gdouble       *page)
1976 {
1977   GtkSpinButtonPrivate *priv;
1978
1979   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
1980
1981   priv = spin_button->priv;
1982
1983   if (step)
1984     *step = priv->adjustment->step_increment;
1985   if (page)
1986     *page = priv->adjustment->page_increment;
1987 }
1988
1989 /**
1990  * gtk_spin_button_set_range:
1991  * @spin_button: a #GtkSpinButton
1992  * @min: minimum allowable value
1993  * @max: maximum allowable value
1994  * 
1995  * Sets the minimum and maximum allowable values for @spin_button
1996  **/
1997 void
1998 gtk_spin_button_set_range (GtkSpinButton *spin_button,
1999                            gdouble        min,
2000                            gdouble        max)
2001 {
2002   GtkSpinButtonPrivate *priv;
2003   gdouble value;
2004
2005   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2006
2007   priv = spin_button->priv;
2008
2009   priv->adjustment->lower = min;
2010   priv->adjustment->upper = max;
2011
2012   value = CLAMP (priv->adjustment->value,
2013                  priv->adjustment->lower,
2014                  (priv->adjustment->upper - priv->adjustment->page_size));
2015
2016   if (value != priv->adjustment->value)
2017     gtk_spin_button_set_value (spin_button, value);
2018
2019   gtk_adjustment_changed (priv->adjustment);
2020 }
2021
2022 /**
2023  * gtk_spin_button_get_range:
2024  * @spin_button: a #GtkSpinButton
2025  * @min: (allow-none): location to store minimum allowed value, or %NULL
2026  * @max: (allow-none): location to store maximum allowed value, or %NULL
2027  *
2028  * Gets the range allowed for @spin_button. See
2029  * gtk_spin_button_set_range().
2030  **/
2031 void
2032 gtk_spin_button_get_range (GtkSpinButton *spin_button,
2033                            gdouble       *min,
2034                            gdouble       *max)
2035 {
2036   GtkSpinButtonPrivate *priv;
2037
2038   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2039
2040   priv = spin_button->priv;
2041
2042   if (min)
2043     *min = priv->adjustment->lower;
2044   if (max)
2045     *max = priv->adjustment->upper;
2046 }
2047
2048 /**
2049  * gtk_spin_button_get_value:
2050  * @spin_button: a #GtkSpinButton
2051  * 
2052  * Get the value in the @spin_button.
2053  * 
2054  * Return value: the value of @spin_button
2055  **/
2056 gdouble
2057 gtk_spin_button_get_value (GtkSpinButton *spin_button)
2058 {
2059   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0.0);
2060
2061   return spin_button->priv->adjustment->value;
2062 }
2063
2064 /**
2065  * gtk_spin_button_get_value_as_int:
2066  * @spin_button: a #GtkSpinButton
2067  * 
2068  * Get the value @spin_button represented as an integer.
2069  * 
2070  * Return value: the value of @spin_button
2071  **/
2072 gint
2073 gtk_spin_button_get_value_as_int (GtkSpinButton *spin_button)
2074 {
2075   GtkSpinButtonPrivate *priv;
2076   gdouble val;
2077
2078   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0);
2079
2080   priv = spin_button->priv;
2081
2082   val = priv->adjustment->value;
2083   if (val - floor (val) < ceil (val) - val)
2084     return floor (val);
2085   else
2086     return ceil (val);
2087 }
2088
2089 /**
2090  * gtk_spin_button_set_value:
2091  * @spin_button: a #GtkSpinButton
2092  * @value: the new value
2093  * 
2094  * Set the value of @spin_button.
2095  **/
2096 void 
2097 gtk_spin_button_set_value (GtkSpinButton *spin_button, 
2098                            gdouble        value)
2099 {
2100   GtkSpinButtonPrivate *priv;
2101
2102   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2103
2104   priv = spin_button->priv;
2105
2106   if (fabs (value - priv->adjustment->value) > EPSILON)
2107     gtk_adjustment_set_value (priv->adjustment, value);
2108   else
2109     {
2110       gint return_val = FALSE;
2111       g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
2112       if (return_val == FALSE)
2113         gtk_spin_button_default_output (spin_button);
2114     }
2115 }
2116
2117 /**
2118  * gtk_spin_button_set_update_policy:
2119  * @spin_button: a #GtkSpinButton 
2120  * @policy: a #GtkSpinButtonUpdatePolicy value
2121  * 
2122  * Sets the update behavior of a spin button. This determines whether the
2123  * spin button is always updated or only when a valid value is set.
2124  **/
2125 void
2126 gtk_spin_button_set_update_policy (GtkSpinButton             *spin_button,
2127                                    GtkSpinButtonUpdatePolicy  policy)
2128 {
2129   GtkSpinButtonPrivate *priv;
2130
2131   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2132
2133   priv = spin_button->priv;
2134
2135   if (priv->update_policy != policy)
2136     {
2137       priv->update_policy = policy;
2138       g_object_notify (G_OBJECT (spin_button), "update-policy");
2139     }
2140 }
2141
2142 /**
2143  * gtk_spin_button_get_update_policy:
2144  * @spin_button: a #GtkSpinButton
2145  *
2146  * Gets the update behavior of a spin button. See
2147  * gtk_spin_button_set_update_policy().
2148  *
2149  * Return value: the current update policy
2150  **/
2151 GtkSpinButtonUpdatePolicy
2152 gtk_spin_button_get_update_policy (GtkSpinButton *spin_button)
2153 {
2154   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), GTK_UPDATE_ALWAYS);
2155
2156   return spin_button->priv->update_policy;
2157 }
2158
2159 /**
2160  * gtk_spin_button_set_numeric:
2161  * @spin_button: a #GtkSpinButton 
2162  * @numeric: flag indicating if only numeric entry is allowed. 
2163  * 
2164  * Sets the flag that determines if non-numeric text can be typed into
2165  * the spin button.
2166  **/
2167 void
2168 gtk_spin_button_set_numeric (GtkSpinButton  *spin_button,
2169                              gboolean        numeric)
2170 {
2171   GtkSpinButtonPrivate *priv;
2172
2173   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2174
2175   priv = spin_button->priv;
2176
2177   numeric = numeric != FALSE;
2178
2179   if (priv->numeric != numeric)
2180     {
2181        priv->numeric = numeric;
2182        g_object_notify (G_OBJECT (spin_button), "numeric");
2183     }
2184 }
2185
2186 /**
2187  * gtk_spin_button_get_numeric:
2188  * @spin_button: a #GtkSpinButton
2189  *
2190  * Returns whether non-numeric text can be typed into the spin button.
2191  * See gtk_spin_button_set_numeric().
2192  *
2193  * Return value: %TRUE if only numeric text can be entered
2194  **/
2195 gboolean
2196 gtk_spin_button_get_numeric (GtkSpinButton *spin_button)
2197 {
2198   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2199
2200   return spin_button->priv->numeric;
2201 }
2202
2203 /**
2204  * gtk_spin_button_set_wrap:
2205  * @spin_button: a #GtkSpinButton 
2206  * @wrap: a flag indicating if wrapping behavior is performed.
2207  * 
2208  * Sets the flag that determines if a spin button value wraps around to the
2209  * opposite limit when the upper or lower limit of the range is exceeded.
2210  **/
2211 void
2212 gtk_spin_button_set_wrap (GtkSpinButton  *spin_button,
2213                           gboolean        wrap)
2214 {
2215   GtkSpinButtonPrivate *priv;
2216
2217   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2218
2219   priv = spin_button->priv;
2220
2221   wrap = wrap != FALSE; 
2222
2223   if (priv->wrap != wrap)
2224     {
2225        priv->wrap = (wrap != 0);
2226   
2227        g_object_notify (G_OBJECT (spin_button), "wrap");
2228     }
2229 }
2230
2231 /**
2232  * gtk_spin_button_get_wrap:
2233  * @spin_button: a #GtkSpinButton
2234  *
2235  * Returns whether the spin button's value wraps around to the
2236  * opposite limit when the upper or lower limit of the range is
2237  * exceeded. See gtk_spin_button_set_wrap().
2238  *
2239  * Return value: %TRUE if the spin button wraps around
2240  **/
2241 gboolean
2242 gtk_spin_button_get_wrap (GtkSpinButton *spin_button)
2243 {
2244   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2245
2246   return spin_button->priv->wrap;
2247 }
2248
2249 static gint
2250 spin_button_get_arrow_size (GtkSpinButton *spin_button)
2251 {
2252   GtkStyle *style;
2253   gint size;
2254   gint arrow_size;
2255
2256   style = gtk_widget_get_style (GTK_WIDGET (spin_button));
2257   size = pango_font_description_get_size (style->font_desc);
2258   arrow_size = MAX (PANGO_PIXELS (size), MIN_ARROW_WIDTH);
2259
2260   return arrow_size - arrow_size % 2; /* force even */
2261 }
2262
2263 /**
2264  * spin_button_get_shadow_type:
2265  * @spin_button: a #GtkSpinButton 
2266  * 
2267  * Convenience function to Get the shadow type from the underlying widget's
2268  * style.
2269  * 
2270  * Return value: the #GtkShadowType
2271  **/
2272 static gint
2273 spin_button_get_shadow_type (GtkSpinButton *spin_button)
2274 {
2275   GtkShadowType rc_shadow_type;
2276
2277   gtk_widget_style_get (GTK_WIDGET (spin_button), "shadow-type", &rc_shadow_type, NULL);
2278
2279   return rc_shadow_type;
2280 }
2281
2282 /**
2283  * gtk_spin_button_set_snap_to_ticks:
2284  * @spin_button: a #GtkSpinButton 
2285  * @snap_to_ticks: a flag indicating if invalid values should be corrected.
2286  * 
2287  * Sets the policy as to whether values are corrected to the nearest step 
2288  * increment when a spin button is activated after providing an invalid value.
2289  **/
2290 void
2291 gtk_spin_button_set_snap_to_ticks (GtkSpinButton *spin_button,
2292                                    gboolean       snap_to_ticks)
2293 {
2294   GtkSpinButtonPrivate *priv;
2295   guint new_val;
2296
2297   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2298
2299   priv = spin_button->priv;
2300
2301   new_val = (snap_to_ticks != 0);
2302
2303   if (new_val != priv->snap_to_ticks)
2304     {
2305       priv->snap_to_ticks = new_val;
2306       if (new_val && GTK_ENTRY (spin_button)->editable)
2307         gtk_spin_button_update (spin_button);
2308       
2309       g_object_notify (G_OBJECT (spin_button), "snap-to-ticks");
2310     }
2311 }
2312
2313 /**
2314  * gtk_spin_button_get_snap_to_ticks:
2315  * @spin_button: a #GtkSpinButton
2316  *
2317  * Returns whether the values are corrected to the nearest step. See
2318  * gtk_spin_button_set_snap_to_ticks().
2319  *
2320  * Return value: %TRUE if values are snapped to the nearest step.
2321  **/
2322 gboolean
2323 gtk_spin_button_get_snap_to_ticks (GtkSpinButton *spin_button)
2324 {
2325   g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2326
2327   return spin_button->priv->snap_to_ticks;
2328 }
2329
2330 /**
2331  * gtk_spin_button_spin:
2332  * @spin_button: a #GtkSpinButton 
2333  * @direction: a #GtkSpinType indicating the direction to spin.
2334  * @increment: step increment to apply in the specified direction.
2335  * 
2336  * Increment or decrement a spin button's value in a specified direction
2337  * by a specified amount. 
2338  **/
2339 void
2340 gtk_spin_button_spin (GtkSpinButton *spin_button,
2341                       GtkSpinType    direction,
2342                       gdouble        increment)
2343 {
2344   GtkSpinButtonPrivate *priv;
2345   GtkAdjustment *adj;
2346   gdouble diff;
2347
2348   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2349
2350   priv = spin_button->priv;
2351
2352   adj = priv->adjustment;
2353
2354   /* for compatibility with the 1.0.x version of this function */
2355   if (increment != 0 && increment != adj->step_increment &&
2356       (direction == GTK_SPIN_STEP_FORWARD ||
2357        direction == GTK_SPIN_STEP_BACKWARD))
2358     {
2359       if (direction == GTK_SPIN_STEP_BACKWARD && increment > 0)
2360         increment = -increment;
2361       direction = GTK_SPIN_USER_DEFINED;
2362     }
2363
2364   switch (direction)
2365     {
2366     case GTK_SPIN_STEP_FORWARD:
2367
2368       gtk_spin_button_real_spin (spin_button, adj->step_increment);
2369       break;
2370
2371     case GTK_SPIN_STEP_BACKWARD:
2372
2373       gtk_spin_button_real_spin (spin_button, -adj->step_increment);
2374       break;
2375
2376     case GTK_SPIN_PAGE_FORWARD:
2377
2378       gtk_spin_button_real_spin (spin_button, adj->page_increment);
2379       break;
2380
2381     case GTK_SPIN_PAGE_BACKWARD:
2382
2383       gtk_spin_button_real_spin (spin_button, -adj->page_increment);
2384       break;
2385
2386     case GTK_SPIN_HOME:
2387
2388       diff = adj->value - adj->lower;
2389       if (diff > EPSILON)
2390         gtk_spin_button_real_spin (spin_button, -diff);
2391       break;
2392
2393     case GTK_SPIN_END:
2394
2395       diff = adj->upper - adj->value;
2396       if (diff > EPSILON)
2397         gtk_spin_button_real_spin (spin_button, diff);
2398       break;
2399
2400     case GTK_SPIN_USER_DEFINED:
2401
2402       if (increment != 0)
2403         gtk_spin_button_real_spin (spin_button, increment);
2404       break;
2405
2406     default:
2407       break;
2408     }
2409 }
2410
2411 /**
2412  * gtk_spin_button_update:
2413  * @spin_button: a #GtkSpinButton 
2414  * 
2415  * Manually force an update of the spin button.
2416  **/
2417 void 
2418 gtk_spin_button_update (GtkSpinButton *spin_button)
2419 {
2420   GtkSpinButtonPrivate *priv;
2421   gdouble val;
2422   gint error = 0;
2423   gint return_val;
2424
2425   g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2426
2427   priv = spin_button->priv;
2428
2429   return_val = FALSE;
2430   g_signal_emit (spin_button, spinbutton_signals[INPUT], 0, &val, &return_val);
2431   if (return_val == FALSE)
2432     {
2433       return_val = gtk_spin_button_default_input (spin_button, &val);
2434       error = (return_val == GTK_INPUT_ERROR);
2435     }
2436   else if (return_val == GTK_INPUT_ERROR)
2437     error = 1;
2438
2439   gtk_widget_queue_draw (GTK_WIDGET (spin_button));
2440
2441   if (priv->update_policy == GTK_UPDATE_ALWAYS)
2442     {
2443       if (val < priv->adjustment->lower)
2444         val = priv->adjustment->lower;
2445       else if (val > priv->adjustment->upper)
2446         val = priv->adjustment->upper;
2447     }
2448   else if ((priv->update_policy == GTK_UPDATE_IF_VALID) && 
2449            (error ||
2450            val < priv->adjustment->lower ||
2451            val > priv->adjustment->upper))
2452     {
2453       gtk_spin_button_value_changed (priv->adjustment, spin_button);
2454       return;
2455     }
2456
2457   if (priv->snap_to_ticks)
2458     gtk_spin_button_snap (spin_button, val);
2459   else
2460     gtk_spin_button_set_value (spin_button, val);
2461 }
2462
2463 GdkWindow *
2464 _gtk_spin_button_get_panel (GtkSpinButton *spin_button)
2465 {
2466   return spin_button->priv->panel;
2467 }