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