gtkspinbutton.c 90.4 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
2 3
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
4 5 6
 * GtkSpinButton widget for GTK+
 * Copyright (C) 1998 Lars Hamann and Stefan Jeske
 *
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 10 11 12 13 14
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * 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
Javier Jardón's avatar
Javier Jardón committed
18
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 20
 */

21
/*
22
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
23 24
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
25
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
26 27
 */

28
#include "config.h"
29

30 31
#include "gtkspinbutton.h"

32 33 34
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
35 36
#include <string.h>
#include <locale.h>
37

38
#include "gtkadjustment.h"
39
#include "gtkbindings.h"
40
#include "gtkentryprivate.h"
41
#include "gtkiconhelperprivate.h"
42
#include "gtkicontheme.h"
43
#include "gtkintl.h"
44
#include "gtkmarshalers.h"
Paolo Borelli's avatar
Paolo Borelli committed
45 46
#include "gtkorientable.h"
#include "gtkorientableprivate.h"
47
#include "gtkprivate.h"
48
#include "gtksettings.h"
49
#include "gtktypebuiltins.h"
50
#include "gtkwidgetpath.h"
51
#include "gtkwidgetprivate.h"
52

53 54
#include "a11y/gtkspinbuttonaccessible.h"

55 56 57
#define MIN_SPIN_BUTTON_WIDTH 30
#define MAX_TIMER_CALLS       5
#define EPSILON               1e-10
58
#define MAX_DIGITS            20
59
#define MIN_ARROW_WIDTH       6
60 61
#define TIMEOUT_INITIAL       500
#define TIMEOUT_REPEAT        50
62

63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
/**
 * SECTION:gtkspinbutton
 * @Title: GtkSpinButton
 * @Short_description: Retrieve an integer or floating-point number from
 *     the user
 * @See_also: #GtkEntry
 *
 * A #GtkSpinButton is an ideal way to allow the user to set the value of
 * some attribute. Rather than having to directly type a number into a
 * #GtkEntry, GtkSpinButton allows the user to click on one of two arrows
 * to increment or decrement the displayed value. A value can still be
 * typed in, with the bonus that it can be checked to ensure it is in a
 * given range.
 *
 * The main properties of a GtkSpinButton are through an adjustment.
 * See the #GtkAdjustment section for more details about an adjustment's
 * properties.
 *
81 82
 * ## Using a GtkSpinButton to get an integer
 *
83
 * |[<!-- language="C" -->
84 85
 * // Provides a function to retrieve an integer value from a GtkSpinButton
 * // and creates a spin button to model percentage values.
86 87 88 89 90 91 92 93 94 95 96 97 98
 *
 * gint
 * grab_int_value (GtkSpinButton *button,
 *                 gpointer       user_data)
 * {
 *   return gtk_spin_button_get_value_as_int (button);
 * }
 *
 * void
 * create_integer_spin_button (void)
 * {
 *
 *   GtkWidget *window, *button;
99
 *   GtkAdjustment *adjustment;
100
 *
101
 *   adjustment = gtk_adjustment_new (50.0, 0.0, 100.0, 1.0, 5.0, 0.0);
102 103 104 105
 *
 *   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
 *   gtk_container_set_border_width (GTK_CONTAINER (window), 5);
 *
106
 *   // creates the spinbutton, with no decimal places
107
 *   button = gtk_spin_button_new (adjustment, 1.0, 0);
108 109 110 111
 *   gtk_container_add (GTK_CONTAINER (window), button);
 *
 *   gtk_widget_show_all (window);
 * }
112
 * ]|
113
 *
114 115
 * ## Using a GtkSpinButton to get a floating point value
 *
116
 * |[<!-- language="C" -->
117 118
 * // Provides a function to retrieve a floating point value from a
 * // GtkSpinButton, and creates a high precision spin button.
119 120 121 122 123 124 125 126 127 128 129 130
 *
 * gfloat
 * grab_float_value (GtkSpinButton *button,
 *                   gpointer       user_data)
 * {
 *   return gtk_spin_button_get_value (button);
 * }
 *
 * void
 * create_floating_spin_button (void)
 * {
 *   GtkWidget *window, *button;
131
 *   GtkAdjustment *adjustment;
132
 *
133
 *   adjustment = gtk_adjustment_new (2.500, 0.0, 5.0, 0.001, 0.1, 0.0);
134 135 136 137
 *
 *   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
 *   gtk_container_set_border_width (GTK_CONTAINER (window), 5);
 *
138
 *   // creates the spinbutton, with three decimal places
139
 *   button = gtk_spin_button_new (adjustment, 0.001, 3);
140 141 142 143
 *   gtk_container_add (GTK_CONTAINER (window), button);
 *
 *   gtk_widget_show_all (window);
 * }
144
 * ]|
145 146
 */

147
struct _GtkSpinButtonPrivate
148 149 150
{
  GtkAdjustment *adjustment;

151 152 153
  GdkWindow     *down_panel;
  GdkWindow     *up_panel;

154 155 156
  GtkStyleContext *down_panel_context;
  GtkStyleContext *up_panel_context;

157 158
  GdkWindow     *click_child;
  GdkWindow     *in_child;
159 160 161

  guint32        timer;

162 163
  GtkSpinButtonUpdatePolicy update_policy;

164 165 166
  gdouble        climb_rate;
  gdouble        timer_step;

Paolo Borelli's avatar
Paolo Borelli committed
167 168
  GtkOrientation orientation;

169 170
  GtkGesture *swipe_gesture;

171 172 173 174 175 176 177 178 179
  guint          button        : 2;
  guint          digits        : 10;
  guint          need_timer    : 1;
  guint          numeric       : 1;
  guint          snap_to_ticks : 1;
  guint          timer_calls   : 3;
  guint          wrap          : 1;
};

180
enum {
181 182 183 184 185 186 187 188
  PROP_0,
  PROP_ADJUSTMENT,
  PROP_CLIMB_RATE,
  PROP_DIGITS,
  PROP_SNAP_TO_TICKS,
  PROP_NUMERIC,
  PROP_WRAP,
  PROP_UPDATE_POLICY,
Paolo Borelli's avatar
Paolo Borelli committed
189 190
  PROP_VALUE,
  PROP_ORIENTATION
191 192
};

193 194 195 196 197
/* Signals */
enum
{
  INPUT,
  OUTPUT,
198
  VALUE_CHANGED,
199
  CHANGE_VALUE,
200
  WRAPPED,
201 202
  LAST_SIGNAL
};
203

204
static void gtk_spin_button_editable_init  (GtkEditableInterface *iface);
205
static void gtk_spin_button_finalize       (GObject            *object);
206
static void gtk_spin_button_set_property   (GObject         *object,
207 208 209
                                            guint            prop_id,
                                            const GValue    *value,
                                            GParamSpec      *pspec);
210
static void gtk_spin_button_get_property   (GObject         *object,
211 212 213
                                            guint            prop_id,
                                            GValue          *value,
                                            GParamSpec      *pspec);
214
static void gtk_spin_button_destroy        (GtkWidget          *widget);
215 216 217 218
static void gtk_spin_button_map            (GtkWidget          *widget);
static void gtk_spin_button_unmap          (GtkWidget          *widget);
static void gtk_spin_button_realize        (GtkWidget          *widget);
static void gtk_spin_button_unrealize      (GtkWidget          *widget);
219 220 221
static void gtk_spin_button_get_preferred_width  (GtkWidget          *widget,
                                                  gint               *minimum,
                                                  gint               *natural);
Paolo Borelli's avatar
Paolo Borelli committed
222 223 224
static void gtk_spin_button_get_preferred_height (GtkWidget          *widget,
                                                  gint               *minimum,
                                                  gint               *natural);
225 226 227 228 229 230
static void gtk_spin_button_get_preferred_height_and_baseline_for_width (GtkWidget *widget,
									 gint       width,
									 gint      *minimum,
									 gint      *natural,
									 gint      *minimum_baseline,
									 gint      *natural_baseline);
231
static void gtk_spin_button_size_allocate  (GtkWidget          *widget,
232
                                            GtkAllocation      *allocation);
233 234
static gint gtk_spin_button_draw           (GtkWidget          *widget,
                                            cairo_t            *cr);
235
static gint gtk_spin_button_button_press   (GtkWidget          *widget,
236
                                            GdkEventButton     *event);
237
static gint gtk_spin_button_button_release (GtkWidget          *widget,
238
                                            GdkEventButton     *event);
239
static gint gtk_spin_button_motion_notify  (GtkWidget          *widget,
240
                                            GdkEventMotion     *event);
241
static gint gtk_spin_button_enter_notify   (GtkWidget          *widget,
242
                                            GdkEventCrossing   *event);
243
static gint gtk_spin_button_leave_notify   (GtkWidget          *widget,
244
                                            GdkEventCrossing   *event);
245
static gint gtk_spin_button_focus_out      (GtkWidget          *widget,
246
                                            GdkEventFocus      *event);
247
static void gtk_spin_button_grab_notify    (GtkWidget          *widget,
248
                                            gboolean            was_grabbed);
249 250
static void gtk_spin_button_state_flags_changed  (GtkWidget     *widget,
                                                  GtkStateFlags  previous_state);
251
static void gtk_spin_button_style_updated   (GtkWidget         *widget);
252
static gboolean gtk_spin_button_timer          (GtkSpinButton      *spin_button);
253
static gboolean gtk_spin_button_stop_spinning  (GtkSpinButton      *spin);
254
static void gtk_spin_button_value_changed  (GtkAdjustment      *adjustment,
255
                                            GtkSpinButton      *spin_button);
256
static gint gtk_spin_button_key_release    (GtkWidget          *widget,
257
                                            GdkEventKey        *event);
258
static gint gtk_spin_button_scroll         (GtkWidget          *widget,
259
                                            GdkEventScroll     *event);
Owen Taylor's avatar
Owen Taylor committed
260
static void gtk_spin_button_activate       (GtkEntry           *entry);
261
static void gtk_spin_button_get_text_area_size (GtkEntry *entry,
262 263 264 265
                                                gint     *x,
                                                gint     *y,
                                                gint     *width,
                                                gint     *height);
266
static void gtk_spin_button_unset_adjustment (GtkSpinButton *spin_button);
Paolo Borelli's avatar
Paolo Borelli committed
267 268
static void gtk_spin_button_set_orientation (GtkSpinButton     *spin_button,
                                             GtkOrientation     orientation);
269
static void gtk_spin_button_snap           (GtkSpinButton      *spin_button,
270
                                            gdouble             val);
271
static void gtk_spin_button_insert_text    (GtkEditable        *editable,
272 273 274
                                            const gchar        *new_text,
                                            gint                new_text_length,
                                            gint               *position);
275
static void gtk_spin_button_real_spin      (GtkSpinButton      *spin_button,
276
                                            gdouble             step);
277
static void gtk_spin_button_real_change_value (GtkSpinButton   *spin,
278
                                               GtkScrollType    scroll);
279

280
static gint gtk_spin_button_default_input  (GtkSpinButton      *spin_button,
281
                                            gdouble            *new_val);
282
static void gtk_spin_button_default_output (GtkSpinButton      *spin_button);
283

284
static guint spinbutton_signals[LAST_SIGNAL] = {0};
285

Matthias Clasen's avatar
Matthias Clasen committed
286
G_DEFINE_TYPE_WITH_CODE (GtkSpinButton, gtk_spin_button, GTK_TYPE_ENTRY,
287
                         G_ADD_PRIVATE (GtkSpinButton)
Paolo Borelli's avatar
Paolo Borelli committed
288
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
289 290
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
                                                gtk_spin_button_editable_init))
291

292 293
#define add_spin_binding(binding_set, keyval, mask, scroll)            \
  gtk_binding_entry_add_signal (binding_set, keyval, mask,             \
294
                                "change-value", 1,                     \
295 296
                                GTK_TYPE_SCROLL_TYPE, scroll)

297 298 299
static void
gtk_spin_button_class_init (GtkSpinButtonClass *class)
{
300
  GObjectClass     *gobject_class = G_OBJECT_CLASS (class);
301 302
  GtkWidgetClass   *widget_class = GTK_WIDGET_CLASS (class);
  GtkEntryClass    *entry_class = GTK_ENTRY_CLASS (class);
303
  GtkBindingSet    *binding_set;
304

305
  gobject_class->finalize = gtk_spin_button_finalize;
306 307
  gobject_class->set_property = gtk_spin_button_set_property;
  gobject_class->get_property = gtk_spin_button_get_property;
308

309
  widget_class->destroy = gtk_spin_button_destroy;
310 311 312 313
  widget_class->map = gtk_spin_button_map;
  widget_class->unmap = gtk_spin_button_unmap;
  widget_class->realize = gtk_spin_button_realize;
  widget_class->unrealize = gtk_spin_button_unrealize;
314
  widget_class->get_preferred_width = gtk_spin_button_get_preferred_width;
Paolo Borelli's avatar
Paolo Borelli committed
315
  widget_class->get_preferred_height = gtk_spin_button_get_preferred_height;
316
  widget_class->get_preferred_height_and_baseline_for_width = gtk_spin_button_get_preferred_height_and_baseline_for_width;
317
  widget_class->size_allocate = gtk_spin_button_size_allocate;
318
  widget_class->draw = gtk_spin_button_draw;
319 320 321 322 323 324 325 326
  widget_class->scroll_event = gtk_spin_button_scroll;
  widget_class->button_press_event = gtk_spin_button_button_press;
  widget_class->button_release_event = gtk_spin_button_button_release;
  widget_class->motion_notify_event = gtk_spin_button_motion_notify;
  widget_class->key_release_event = gtk_spin_button_key_release;
  widget_class->enter_notify_event = gtk_spin_button_enter_notify;
  widget_class->leave_notify_event = gtk_spin_button_leave_notify;
  widget_class->focus_out_event = gtk_spin_button_focus_out;
327
  widget_class->grab_notify = gtk_spin_button_grab_notify;
328
  widget_class->state_flags_changed = gtk_spin_button_state_flags_changed;
329
  widget_class->style_updated = gtk_spin_button_style_updated;
330

Owen Taylor's avatar
Owen Taylor committed
331
  entry_class->activate = gtk_spin_button_activate;
332
  entry_class->get_text_area_size = gtk_spin_button_get_text_area_size;
333 334 335

  class->input = NULL;
  class->output = NULL;
336
  class->change_value = gtk_spin_button_real_change_value;
337

338 339 340
  g_object_class_install_property (gobject_class,
                                   PROP_ADJUSTMENT,
                                   g_param_spec_object ("adjustment",
341
                                                        P_("Adjustment"),
342
                                                        P_("The adjustment that holds the value of the spin button"),
343
                                                        GTK_TYPE_ADJUSTMENT,
344
                                                        GTK_PARAM_READWRITE));
345

346 347
  g_object_class_install_property (gobject_class,
                                   PROP_CLIMB_RATE,
348
                                   g_param_spec_double ("climb-rate",
349 350
                                                        P_("Climb Rate"),
                                                        P_("The acceleration rate when you hold down a button"),
351 352
                                                        0.0, G_MAXDOUBLE, 0.0,
                                                        GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
353

354 355 356
  g_object_class_install_property (gobject_class,
                                   PROP_DIGITS,
                                   g_param_spec_uint ("digits",
357 358
                                                      P_("Digits"),
                                                      P_("The number of decimal places to display"),
359 360
                                                      0, MAX_DIGITS, 0,
                                                      GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
361

362 363
  g_object_class_install_property (gobject_class,
                                   PROP_SNAP_TO_TICKS,
364
                                   g_param_spec_boolean ("snap-to-ticks",
365 366 367
                                                         P_("Snap to Ticks"),
                                                         P_("Whether erroneous values are automatically changed to a spin button's nearest step increment"),
                                                         FALSE,
368
                                                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
369

370 371 372
  g_object_class_install_property (gobject_class,
                                   PROP_NUMERIC,
                                   g_param_spec_boolean ("numeric",
373 374 375
                                                         P_("Numeric"),
                                                         P_("Whether non-numeric characters should be ignored"),
                                                         FALSE,
376
                                                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
377

378 379 380
  g_object_class_install_property (gobject_class,
                                   PROP_WRAP,
                                   g_param_spec_boolean ("wrap",
381 382 383
                                                         P_("Wrap"),
                                                         P_("Whether a spin button should wrap upon reaching its limits"),
                                                         FALSE,
384
                                                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
385

386 387
  g_object_class_install_property (gobject_class,
                                   PROP_UPDATE_POLICY,
388
                                   g_param_spec_enum ("update-policy",
389 390 391 392
                                                      P_("Update Policy"),
                                                      P_("Whether the spin button should update always, or only when the value is legal"),
                                                      GTK_TYPE_SPIN_BUTTON_UPDATE_POLICY,
                                                      GTK_UPDATE_ALWAYS,
393
                                                      GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
394

395 396 397
  g_object_class_install_property (gobject_class,
                                   PROP_VALUE,
                                   g_param_spec_double ("value",
398 399
                                                        P_("Value"),
                                                        P_("Reads the current value, or sets a new value"),
400 401
                                                        -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
                                                        GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
402

Paolo Borelli's avatar
Paolo Borelli committed
403 404 405 406
  g_object_class_override_property (gobject_class,
                                    PROP_ORIENTATION,
                                    "orientation");

407 408 409 410 411 412 413
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_enum ("shadow-type",
                                                              P_("Shadow Type"),
                                                              P_("Style of bevel around the spin button"),
                                                              GTK_TYPE_SHADOW_TYPE,
                                                              GTK_SHADOW_IN,
                                                              GTK_PARAM_READABLE));
414 415 416 417

  /**
   * GtkSpinButton::input:
   * @spin_button: the object on which the signal was emitted
418
   * @new_value: (out) (type double): return location for the new value
419 420 421 422 423 424 425 426 427 428 429
   *
   * The ::input signal can be used to influence the conversion of
   * the users input into a double value. The signal handler is
   * expected to use gtk_entry_get_text() to retrieve the text of
   * the entry and set @new_value to the new value.
   *
   * The default conversion uses g_strtod().
   *
   * Returns: %TRUE for a successful conversion, %FALSE if the input
   *     was not handled, and %GTK_INPUT_ERROR if the conversion failed.
   */
430
  spinbutton_signals[INPUT] =
Matthias Clasen's avatar
Matthias Clasen committed
431
    g_signal_new (I_("input"),
432 433 434 435 436 437 438
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkSpinButtonClass, input),
                  NULL, NULL,
                  _gtk_marshal_INT__POINTER,
                  G_TYPE_INT, 1,
                  G_TYPE_POINTER);
439

440 441
  /**
   * GtkSpinButton::output:
442
   * @spin_button: the object on which the signal was emitted
443
   *
444 445
   * The ::output signal can be used to change to formatting
   * of the value that is displayed in the spin buttons entry.
446
   * |[<!-- language="C" -->
447
   * // show leading zeros
448 449 450 451
   * static gboolean
   * on_output (GtkSpinButton *spin,
   *            gpointer       data)
   * {
452
   *    GtkAdjustment *adjustment;
453
   *    gchar *text;
454
   *    int value;
455
   *
456 457
   *    adjustment = gtk_spin_button_get_adjustment (spin);
   *    value = (int)gtk_adjustment_get_value (adjustment);
458 459 460
   *    text = g_strdup_printf ("%02d", value);
   *    gtk_entry_set_text (GTK_ENTRY (spin), text);
   *    g_free (text);
461
   *
462 463 464 465
   *    return TRUE;
   * }
   * ]|
   *
466
   * Returns: %TRUE if the value has been displayed
467
   */
468
  spinbutton_signals[OUTPUT] =
Matthias Clasen's avatar
Matthias Clasen committed
469
    g_signal_new (I_("output"),
470 471 472 473 474 475
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkSpinButtonClass, output),
                  _gtk_boolean_handled_accumulator, NULL,
                  _gtk_marshal_BOOLEAN__VOID,
                  G_TYPE_BOOLEAN, 0);
476

477 478 479 480 481 482 483
  /**
   * GtkSpinButton::value-changed:
   * @spin_button: the object on which the signal was emitted
   *
   * The ::value-changed signal is emitted when the value represented by
   * @spinbutton changes. Also see the #GtkSpinButton::output signal.
   */
484
  spinbutton_signals[VALUE_CHANGED] =
485
    g_signal_new (I_("value-changed"),
486 487 488 489 490 491
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkSpinButtonClass, value_changed),
                  NULL, NULL,
                  _gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
492

493 494
  /**
   * GtkSpinButton::wrapped:
495
   * @spin_button: the object on which the signal was emitted
496
   *
497
   * The ::wrapped signal is emitted right after the spinbutton wraps
498 499 500 501 502 503
   * from its maximum to minimum value or vice-versa.
   *
   * Since: 2.10
   */
  spinbutton_signals[WRAPPED] =
    g_signal_new (I_("wrapped"),
504 505 506 507 508 509
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkSpinButtonClass, wrapped),
                  NULL, NULL,
                  _gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
510

511
  /* Action signals */
512 513 514 515 516 517 518 519 520 521 522 523 524 525
  /**
   * GtkSpinButton::change-value:
   * @spin_button: the object on which the signal was emitted
   * @scroll: a #GtkScrollType to specify the speed and amount of change
   *
   * The ::change-value signal is a [keybinding signal][GtkBindingSignal] 
   * which gets emitted when the user initiates a value change. 
   *
   * Applications should not connect to it, but may emit it with 
   * g_signal_emit_by_name() if they need to control the cursor
   * programmatically.
   *
   * The default bindings for this signal are Up/Down and PageUp and/PageDown.
   */
526
  spinbutton_signals[CHANGE_VALUE] =
527
    g_signal_new (I_("change-value"),
Manish Singh's avatar
Manish Singh committed
528
                  G_TYPE_FROM_CLASS (gobject_class),
529 530 531 532 533 534
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkSpinButtonClass, change_value),
                  NULL, NULL,
                  _gtk_marshal_VOID__ENUM,
                  G_TYPE_NONE, 1,
                  GTK_TYPE_SCROLL_TYPE);
535

536
  binding_set = gtk_binding_set_by_class (class);
537

538 539 540 541 542 543 544 545
  add_spin_binding (binding_set, GDK_KEY_Up, 0, GTK_SCROLL_STEP_UP);
  add_spin_binding (binding_set, GDK_KEY_KP_Up, 0, GTK_SCROLL_STEP_UP);
  add_spin_binding (binding_set, GDK_KEY_Down, 0, GTK_SCROLL_STEP_DOWN);
  add_spin_binding (binding_set, GDK_KEY_KP_Down, 0, GTK_SCROLL_STEP_DOWN);
  add_spin_binding (binding_set, GDK_KEY_Page_Up, 0, GTK_SCROLL_PAGE_UP);
  add_spin_binding (binding_set, GDK_KEY_Page_Down, 0, GTK_SCROLL_PAGE_DOWN);
  add_spin_binding (binding_set, GDK_KEY_Page_Up, GDK_CONTROL_MASK, GTK_SCROLL_END);
  add_spin_binding (binding_set, GDK_KEY_Page_Down, GDK_CONTROL_MASK, GTK_SCROLL_START);
546

547
  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SPIN_BUTTON_ACCESSIBLE);
548 549
}

550
static void
551
gtk_spin_button_editable_init (GtkEditableInterface *iface)
552 553 554 555
{
  iface->insert_text = gtk_spin_button_insert_text;
}

556
static void
557
gtk_spin_button_set_property (GObject      *object,
558 559 560
                              guint         prop_id,
                              const GValue *value,
                              GParamSpec   *pspec)
561
{
562
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
563
  GtkSpinButtonPrivate *priv = spin_button->priv;
564

565
  switch (prop_id)
566 567 568
    {
      GtkAdjustment *adjustment;

569 570
    case PROP_ADJUSTMENT:
      adjustment = GTK_ADJUSTMENT (g_value_get_object (value));
571 572
      gtk_spin_button_set_adjustment (spin_button, adjustment);
      break;
573
    case PROP_CLIMB_RATE:
574
      gtk_spin_button_configure (spin_button,
575 576 577
                                 priv->adjustment,
                                 g_value_get_double (value),
                                 priv->digits);
578
      break;
579
    case PROP_DIGITS:
580
      gtk_spin_button_configure (spin_button,
581 582 583
                                 priv->adjustment,
                                 priv->climb_rate,
                                 g_value_get_uint (value));
584
      break;
585 586
    case PROP_SNAP_TO_TICKS:
      gtk_spin_button_set_snap_to_ticks (spin_button, g_value_get_boolean (value));
587
      break;
588 589
    case PROP_NUMERIC:
      gtk_spin_button_set_numeric (spin_button, g_value_get_boolean (value));
590
      break;
591 592
    case PROP_WRAP:
      gtk_spin_button_set_wrap (spin_button, g_value_get_boolean (value));
593
      break;
594 595
    case PROP_UPDATE_POLICY:
      gtk_spin_button_set_update_policy (spin_button, g_value_get_enum (value));
596
      break;
597 598
    case PROP_VALUE:
      gtk_spin_button_set_value (spin_button, g_value_get_double (value));
599
      break;
Paolo Borelli's avatar
Paolo Borelli committed
600 601 602
    case PROP_ORIENTATION:
      gtk_spin_button_set_orientation (spin_button, g_value_get_enum (value));
      break;
603
    default:
604
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
605 606 607 608 609
      break;
    }
}

static void
610
gtk_spin_button_get_property (GObject      *object,
611 612 613
                              guint         prop_id,
                              GValue       *value,
                              GParamSpec   *pspec)
614
{
615
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
616
  GtkSpinButtonPrivate *priv = spin_button->priv;
617

618
  switch (prop_id)
619
    {
620
    case PROP_ADJUSTMENT:
621
      g_value_set_object (value, priv->adjustment);
622
      break;
623
    case PROP_CLIMB_RATE:
624
      g_value_set_double (value, priv->climb_rate);
625
      break;
626
    case PROP_DIGITS:
627
      g_value_set_uint (value, priv->digits);
628
      break;
629
    case PROP_SNAP_TO_TICKS:
630
      g_value_set_boolean (value, priv->snap_to_ticks);
631
      break;
632
    case PROP_NUMERIC:
633
      g_value_set_boolean (value, priv->numeric);
634
      break;
635
    case PROP_WRAP:
636
      g_value_set_boolean (value, priv->wrap);
637
      break;
638
    case PROP_UPDATE_POLICY:
639
      g_value_set_enum (value, priv->update_policy);
640
      break;
641
     case PROP_VALUE:
642
       g_value_set_double (value, gtk_adjustment_get_value (priv->adjustment));
643
      break;
Paolo Borelli's avatar
Paolo Borelli committed
644 645 646
    case PROP_ORIENTATION:
      g_value_set_enum (value, priv->orientation);
      break;
647
    default:
648
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
649 650 651 652
      break;
    }
}

653
static void
654 655 656
swipe_gesture_begin (GtkGesture       *gesture,
                     GdkEventSequence *sequence,
                     GtkSpinButton    *spin_button)
657
{
658 659
  GdkEventSequence *current;
  const GdkEvent *event;
660

661 662 663 664 665 666 667 668 669
  current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
  event = gtk_gesture_get_last_event (gesture, current);

  if (event->any.window == spin_button->priv->up_panel ||
      event->any.window == spin_button->priv->down_panel)
    gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);

  gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
  gtk_widget_grab_focus (GTK_WIDGET (spin_button));
670 671 672 673 674 675 676 677 678 679 680 681 682
}

static void
swipe_gesture_update (GtkGesture       *gesture,
                      GdkEventSequence *sequence,
                      GtkSpinButton    *spin_button)
{
  gdouble vel_y;

  gtk_gesture_swipe_get_velocity (GTK_GESTURE_SWIPE (gesture), NULL, &vel_y);
  gtk_spin_button_real_spin (spin_button, -vel_y / 20);
}

683 684 685
static void
gtk_spin_button_init (GtkSpinButton *spin_button)
{
686
  GtkSpinButtonPrivate *priv;
687
  GtkStyleContext *context;
688

689
  spin_button->priv = gtk_spin_button_get_instance_private (spin_button);
690 691 692
  priv = spin_button->priv;

  priv->adjustment = NULL;
693 694
  priv->down_panel = NULL;
  priv->up_panel = NULL;
695 696 697 698
  priv->timer = 0;
  priv->climb_rate = 0.0;
  priv->timer_step = 0.0;
  priv->update_policy = GTK_UPDATE_ALWAYS;
699 700
  priv->in_child = NULL;
  priv->click_child = NULL;
701 702 703 704 705 706 707
  priv->button = 0;
  priv->need_timer = FALSE;
  priv->timer_calls = 0;
  priv->digits = 0;
  priv->numeric = FALSE;
  priv->wrap = FALSE;
  priv->snap_to_ticks = FALSE;
708

Paolo Borelli's avatar
Paolo Borelli committed
709 710
  priv->orientation = GTK_ORIENTATION_HORIZONTAL;

711
  gtk_spin_button_set_adjustment (spin_button, NULL);
712 713 714

  context = gtk_widget_get_style_context (GTK_WIDGET (spin_button));
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_SPINBUTTON);
715
  _gtk_orientable_set_style_classes (GTK_ORIENTABLE (spin_button));
716 717

  gtk_widget_add_events (GTK_WIDGET (spin_button), GDK_SCROLL_MASK);
718 719

  priv->swipe_gesture = gtk_gesture_swipe_new (GTK_WIDGET (spin_button));
720
  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->swipe_gesture), TRUE);
721 722
  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->swipe_gesture),
                                              GTK_PHASE_CAPTURE);
723 724
  g_signal_connect (priv->swipe_gesture, "begin",
                    G_CALLBACK (swipe_gesture_begin), spin_button);
725 726
  g_signal_connect (priv->swipe_gesture, "update",
                    G_CALLBACK (swipe_gesture_update), spin_button);
727 728 729
}

static void
730
gtk_spin_button_finalize (GObject *object)
731
{
732 733 734
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
  GtkSpinButtonPrivate *priv = spin_button->priv;

735
  gtk_spin_button_unset_adjustment (spin_button);
736 737 738 739 740 741

  if (priv->down_panel_context)
    g_object_unref (priv->down_panel_context);

  if (priv->up_panel_context)
    g_object_unref (priv->up_panel_context);
742

743 744
  g_object_unref (priv->swipe_gesture);

Matthias Clasen's avatar
Matthias Clasen committed
745
  G_OBJECT_CLASS (gtk_spin_button_parent_class)->finalize (object);
746 747
}

748
static void
749
gtk_spin_button_destroy (GtkWidget *widget)
750
{
751
  gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));
752

753
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->destroy (widget);
754 755
}

756 757 758
static void
gtk_spin_button_map (GtkWidget *widget)
{
759
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
760
  GtkSpinButtonPrivate *priv = spin_button->priv;
761

762
  if (gtk_widget_get_realized (widget) && !gtk_widget_get_mapped (widget))
763
    {
Matthias Clasen's avatar
Matthias Clasen committed
764
      GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->map (widget);
765 766
      gdk_window_show (priv->down_panel);
      gdk_window_show (priv->up_panel);
767 768 769 770 771 772
    }
}

static void
gtk_spin_button_unmap (GtkWidget *widget)
{
773
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
774
  GtkSpinButtonPrivate *priv = spin_button->priv;
775

776
  if (gtk_widget_get_mapped (widget))
777
    {
778 779
      gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));

780 781
      gdk_window_hide (priv->down_panel);
      gdk_window_hide (priv->up_panel);
Matthias Clasen's avatar
Matthias Clasen committed
782
      GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unmap (widget);
783 784 785 786
    }
}

static void
787 788
gtk_spin_button_panel_nthchildize_context (GtkSpinButton *spin_button,
                                           GtkStyleContext *context,
789
                                           gboolean         is_down_panel)
790
{
Paolo Borelli's avatar
Paolo Borelli committed
791
  GtkSpinButtonPrivate *priv = spin_button->priv;
792 793 794 795 796 797 798 799 800
  GtkWidget *widget = GTK_WIDGET (spin_button);
  GtkWidgetPath *path, *siblings_path;
  GtkTextDirection direction;
  gint up_pos, down_pos;

  /* We are a subclass of GtkEntry, which is not a GtkContainer, so we
   * have to emulate what gtk_container_get_path_for_child() would do
   * for the button panels
   */
801
  path = _gtk_widget_create_path (widget);
802 803
  siblings_path = gtk_widget_path_new ();

Paolo Borelli's avatar
Paolo Borelli committed
804
  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
805
    {
Paolo Borelli's avatar
Paolo Borelli committed
806 807 808 809 810 811 812 813 814 815 816 817 818 819 820
      direction = gtk_widget_get_direction (widget);

      /* flip children order for RTL */
      if (direction == GTK_TEXT_DIR_RTL)
        {
          up_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
          down_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
          gtk_widget_path_append_type (siblings_path, GTK_TYPE_ENTRY);
        }
      else
        {
          gtk_widget_path_append_type (siblings_path, GTK_TYPE_ENTRY);
          down_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
          up_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
        }
821 822 823 824
    }
  else
    {
      up_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
Paolo Borelli's avatar
Paolo Borelli committed
825 826
      gtk_widget_path_append_type (siblings_path, GTK_TYPE_ENTRY);
      down_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
827 828 829 830 831 832 833 834 835
    }

  gtk_widget_path_iter_add_class (siblings_path, up_pos, GTK_STYLE_CLASS_BUTTON);
  gtk_widget_path_iter_add_class (siblings_path, down_pos, GTK_STYLE_CLASS_BUTTON);

  /* this allows compatibility for themes that use .spinbutton.button */
  gtk_widget_path_iter_add_class (siblings_path, up_pos, GTK_STYLE_CLASS_SPINBUTTON);
  gtk_widget_path_iter_add_class (siblings_path, down_pos, GTK_STYLE_CLASS_SPINBUTTON);

836
  if (is_down_panel)
837
    gtk_widget_path_append_with_siblings (path, siblings_path, down_pos);
838
  else
839
    gtk_widget_path_append_with_siblings (path, siblings_path, up_pos);
840 841 842 843 844 845 846 847 848

  gtk_style_context_set_path (context, path);
  gtk_widget_path_unref (path);
  gtk_widget_path_unref (siblings_path);
}

static gboolean
gtk_spin_button_panel_at_limit (GtkSpinButton *spin_button,
                                GdkWindow     *panel)
849
{
850
  GtkSpinButtonPrivate *priv = spin_button->priv;
851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878
  GdkWindow *effective_panel;

  if (priv->wrap)
    return FALSE;

  if (gtk_adjustment_get_step_increment (priv->adjustment) > 0)
    effective_panel = panel;
  else
    effective_panel = panel == priv->up_panel ? priv->down_panel : priv->up_panel;

  if (effective_panel == priv->up_panel &&
      (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_value (priv->adjustment) <= EPSILON))
    return TRUE;

  if (effective_panel == priv->down_panel &&
      (gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment) <= EPSILON))
    return TRUE;

  return FALSE;
}

static GtkStateFlags
gtk_spin_button_panel_get_state (GtkSpinButton *spin_button,
                                 GdkWindow *panel)
{
  GtkStateFlags state;
  GtkSpinButtonPrivate *priv = spin_button->priv;

879 880 881 882 883 884
  state = gtk_widget_get_state_flags (GTK_WIDGET (spin_button));

  state &= ~(GTK_STATE_FLAG_ACTIVE | GTK_STATE_FLAG_PRELIGHT);

  if ((state & GTK_STATE_FLAG_INSENSITIVE) ||
      gtk_spin_button_panel_at_limit (spin_button, panel) ||
885
      !gtk_editable_get_editable (GTK_EDITABLE (spin_button)))
886 887 888
    {
      state |= GTK_STATE_FLAG_INSENSITIVE;
    }
889 890 891
  else
    {
      if (priv->click_child == panel)
Cosimo Cecchi's avatar
Cosimo Cecchi committed
892
        state |= GTK_STATE_FLAG_ACTIVE;
893 894 895
      else if (priv->in_child == panel &&
               priv->click_child == NULL)
        state |= GTK_STATE_FLAG_PRELIGHT;
896 897 898 899 900 901 902
    }

  return state;
}

static GtkStyleContext *
gtk_spin_button_panel_get_context (GtkSpinButton *spin_button,
903
                                   GdkWindow     *panel)
904
{
905 906
  GtkSpinButtonPrivate *priv = spin_button->priv;
  GtkStyleContext **contextp;
907

908 909 910 911 912 913 914 915 916
  contextp = (panel == priv->down_panel) ?
    &priv->down_panel_context : &priv->up_panel_context;

  if (*contextp == NULL)
    {
      *contextp = gtk_style_context_new ();
      gtk_spin_button_panel_nthchildize_context (spin_button, *contextp,
                                                 panel == priv->down_panel);
    }
917

918
  gtk_style_context_set_screen (*contextp, gtk_widget_get_screen (GTK_WIDGET (spin_button)));
919 920
  gtk_style_context_set_state (*contextp, gtk_spin_button_panel_get_state (spin_button, panel));

921
  return *contextp;
922 923
}

924 925
#include "gtkcsssectionprivate.h"

Paolo Borelli's avatar
Paolo Borelli committed
926 927 928 929 930
static void
gtk_spin_button_panel_get_size (GtkSpinButton *spin_button,
                                GdkWindow *panel,
                                gint *width,
                                gint *height)
931 932
{
  GtkBorder button_padding, button_border;
933 934
  GtkStyleContext *context;
  GtkStateFlags state;
Paolo Borelli's avatar
Paolo Borelli committed
935
  gint icon_size, w, h;
936

Paolo Borelli's avatar
Paolo Borelli committed
937 938
  gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
  icon_size = MAX (w, h);
939 940

  context = gtk_spin_button_panel_get_context (spin_button, panel);
941
  state = gtk_style_context_get_state (context);
942 943 944 945

  gtk_style_context_get_padding (context, state, &button_padding);
  gtk_style_context_get_border (context, state, &button_border);

Paolo Borelli's avatar
Paolo Borelli committed
946 947 948 949 950 951 952
  if (width)
    *width = icon_size + button_padding.left + button_padding.right +
             button_border.left + button_border.right;

  if (height)
    *height = icon_size + button_padding.top + button_padding.bottom +
              button_border.top + button_border.bottom;
953 954 955 956 957 958 959 960 961 962
}

static void
gtk_spin_button_panel_get_allocations (GtkSpinButton *spin_button,
                                       GtkAllocation *down_allocation_out,
                                       GtkAllocation *up_allocation_out)
{
  GtkWidget *widget = GTK_WIDGET (spin_button);
  GtkSpinButtonPrivate *priv = spin_button->priv;
  GtkAllocation spin_allocation, down_allocation, up_allocation, allocation;
963
  GtkRequisition requisition;
964
  gint req_height;
Paolo Borelli's avatar
Paolo Borelli committed
965 966
  gint down_panel_width, down_panel_height;
  gint up_panel_width, up_panel_height;
967
  GtkStyleContext *context;
Paolo Borelli's avatar
Paolo Borelli committed
968
  GtkBorder border;
969
  GtkStateFlags state;
970

971
  gtk_widget_get_allocation (widget, &spin_allocation);
972
  gtk_widget_get_preferred_size (widget, &requisition, NULL);
973 974

  context = gtk_widget_get_style_context (GTK_WIDGET (spin_button));
975 976
  state = gtk_style_context_get_state (context);
  gtk_style_context_get_border (context, state, &border);
977

Paolo Borelli's avatar
Paolo Borelli committed
978 979
  gtk_spin_button_panel_get_size (spin_button, priv->down_panel, &down_panel_width, &down_panel_height);
  gtk_spin_button_panel_get_size (spin_button, priv->up_panel, &up_panel_width, &up_panel_height);
980

Paolo Borelli's avatar
Paolo Borelli committed
981 982 983
  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
    {
      req_height = requisition.height - gtk_widget_get_margin_top (widget) - gtk_widget_get_margin_bottom (widget);
984

Paolo Borelli's avatar
Paolo Borelli committed
985 986 987 988 989 990
      /* both panels have the same size, and they're as tall as the entry allocation,
       * excluding margins
       */
      allocation.height = MIN (req_height, spin_allocation.height) - border.top - border.bottom;
      allocation.y = spin_allocation.y + border.top + (spin_allocation.height - req_height) / 2;
      down_allocation = up_allocation = allocation;
991

Paolo Borelli's avatar
Paolo Borelli committed
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005
      down_allocation.width = down_panel_width;
      up_allocation.width = up_panel_width;

      /* invert x axis allocation for RTL */
      if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
        {
          up_allocation.x = spin_allocation.x + border.left;
          down_allocation.x = up_allocation.x + up_panel_width;
        }
      else
        {
          up_allocation.x = spin_allocation.x + spin_allocation.width - up_panel_width - border.right;
          down_allocation.x = up_allocation.x - down_panel_width;
        }
1006 1007 1008
    }
  else
    {
Paolo Borelli's avatar
Paolo Borelli committed
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018
      /* both panels have the same size, and they're as wide as the entry allocation */
      allocation.width = spin_allocation.width;
      allocation.x = spin_allocation.x;
      down_allocation = up_allocation = allocation;

      down_allocation.height = down_panel_height;
      up_allocation.height = up_panel_height;

      up_allocation.y = spin_allocation.y;
      down_allocation.y = spin_allocation.y + spin_allocation.height - down_allocation.height;
1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031
    }

  if (down_allocation_out)
    *down_allocation_out = down_allocation;
  if (up_allocation_out)
    *up_allocation_out = up_allocation;
}

static void
gtk_spin_button_panel_draw (GtkSpinButton   *spin_button,
                            cairo_t         *cr,
                            GdkWindow       *panel)
{