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