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