gtkspinbutton.c 79.7 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
18 19 20
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
21 22
 */

23
/*
24
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
25 26
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * 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 33 34
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
35 36
#include <string.h>
#include <locale.h>
37

38
#include "gtkbindings.h"
39
#include "gtkspinbutton.h"
40
#include "gtkentryprivate.h"
41
#include "gtkmain.h"
42
#include "gtkmarshalers.h"
43
#include "gtksettings.h"
44
#include "gtkprivate.h"
45
#include "gtkintl.h"
46
#include "gtktypebuiltins.h"
47

48 49 50
#define MIN_SPIN_BUTTON_WIDTH 30
#define MAX_TIMER_CALLS       5
#define EPSILON               1e-10
51
#define MAX_DIGITS            20
52
#define MIN_ARROW_WIDTH       6
53

54

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
/**
 * 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.
 *
 * <example>
 * <title>Using a GtkSpinButton to get an integer</title>
 * <programlisting>
 * /&ast; Provides a function to retrieve an integer value from a
 *  &ast; GtkSpinButton and creates a spin button to model percentage
 *  &ast; values.
 *  &ast;/
 *
 * 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;
 *   GtkAdjustment *adj;
 *
 *   adj = gtk_adjustment_new (50.0, 0.0, 100.0, 1.0, 5.0, 0.0);
 *
 *   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
 *   gtk_container_set_border_width (GTK_CONTAINER (window), 5);
 *
 *   /&ast; creates the spinbutton, with no decimal places &ast;/
 *   button = gtk_spin_button_new (adj, 1.0, 0);
 *   gtk_container_add (GTK_CONTAINER (window), button);
 *
 *   gtk_widget_show_all (window);
 * }
 * </programlisting>
 * </example>
 *
 * <example>
 * <title>Using a GtkSpinButton to get a floating point value</title>
 * <programlisting>
 * /&ast; Provides a function to retrieve a floating point value from a
 *  &ast; GtkSpinButton, and creates a high precision spin button.
 *  &ast;/
 *
 * 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;
 *   GtkAdjustment *adj;
 *
 *   adj = gtk_adjustment_new (2.500, 0.0, 5.0, 0.001, 0.1, 0.0);
 *
 *   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
 *   gtk_container_set_border_width (GTK_CONTAINER (window), 5);
 *
 *   /&ast; creates the spinbutton, with three decimal places &ast;/
 *   button = gtk_spin_button_new (adj, 0.001, 3);
 *   gtk_container_add (GTK_CONTAINER (window), button);
 *
 *   gtk_widget_show_all (window);
 * }
 * </programlisting>
 * </example>
 */

144
struct _GtkSpinButtonPrivate
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
{
  GtkSpinButtonUpdatePolicy update_policy;
  GtkAdjustment *adjustment;

  GdkWindow     *panel;

  guint32        timer;

  gdouble        climb_rate;
  gdouble        timer_step;

  guint          button        : 2;
  guint          click_child   : 2; /* valid: GTK_ARROW_UP=0, GTK_ARROW_DOWN=1 or 2=NONE/BOTH */
  guint          digits        : 10;
  guint          in_child      : 2;
  guint          need_timer    : 1;
  guint          numeric       : 1;
  guint          snap_to_ticks : 1;
  guint          timer_calls   : 3;
  guint          wrap          : 1;
};

167
enum {
168 169 170 171 172 173 174 175 176
  PROP_0,
  PROP_ADJUSTMENT,
  PROP_CLIMB_RATE,
  PROP_DIGITS,
  PROP_SNAP_TO_TICKS,
  PROP_NUMERIC,
  PROP_WRAP,
  PROP_UPDATE_POLICY,
  PROP_VALUE
177 178
};

179 180 181 182 183
/* Signals */
enum
{
  INPUT,
  OUTPUT,
184
  VALUE_CHANGED,
185
  CHANGE_VALUE,
186
  WRAPPED,
187 188
  LAST_SIGNAL
};
189

190
static void gtk_spin_button_editable_init  (GtkEditableInterface *iface);
191
static void gtk_spin_button_finalize       (GObject            *object);
192
static void gtk_spin_button_set_property   (GObject         *object,
193 194 195
                                            guint            prop_id,
                                            const GValue    *value,
                                            GParamSpec      *pspec);
196
static void gtk_spin_button_get_property   (GObject         *object,
197 198 199
                                            guint            prop_id,
                                            GValue          *value,
                                            GParamSpec      *pspec);
200
static void gtk_spin_button_destroy        (GtkWidget          *widget);
201 202 203 204
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);
205 206 207 208
static void gtk_spin_button_get_preferred_width  (GtkWidget          *widget,
                                                  gint               *minimum,
                                                  gint               *natural);

209
static void gtk_spin_button_size_allocate  (GtkWidget          *widget,
210
                                            GtkAllocation      *allocation);
211 212
static gint gtk_spin_button_draw           (GtkWidget          *widget,
                                            cairo_t            *cr);
213
static gint gtk_spin_button_button_press   (GtkWidget          *widget,
214
                                            GdkEventButton     *event);
215
static gint gtk_spin_button_button_release (GtkWidget          *widget,
216
                                            GdkEventButton     *event);
217
static gint gtk_spin_button_motion_notify  (GtkWidget          *widget,
218
                                            GdkEventMotion     *event);
219
static gint gtk_spin_button_enter_notify   (GtkWidget          *widget,
220
                                            GdkEventCrossing   *event);
221
static gint gtk_spin_button_leave_notify   (GtkWidget          *widget,
222
                                            GdkEventCrossing   *event);
223
static gint gtk_spin_button_focus_out      (GtkWidget          *widget,
224
                                            GdkEventFocus      *event);
225
static void gtk_spin_button_grab_notify    (GtkWidget          *widget,
226
                                            gboolean            was_grabbed);
227 228 229
static void gtk_spin_button_state_flags_changed  (GtkWidget     *widget,
                                                  GtkStateFlags  previous_state);
static void gtk_spin_button_style_updated  (GtkWidget          *widget);
230
static void gtk_spin_button_draw_arrow     (GtkSpinButton      *spin_button,
231
                                            GtkStyleContext    *context,
232 233
                                            cairo_t            *cr,
                                            GtkArrowType        arrow_type);
234
static gboolean gtk_spin_button_timer          (GtkSpinButton      *spin_button);
235
static void gtk_spin_button_stop_spinning  (GtkSpinButton      *spin);
236
static void gtk_spin_button_value_changed  (GtkAdjustment      *adjustment,
237
                                            GtkSpinButton      *spin_button);
238
static gint gtk_spin_button_key_release    (GtkWidget          *widget,
239
                                            GdkEventKey        *event);
240
static gint gtk_spin_button_scroll         (GtkWidget          *widget,
241
                                            GdkEventScroll     *event);
Owen Taylor's avatar
Owen Taylor committed
242
static void gtk_spin_button_activate       (GtkEntry           *entry);
243
static void gtk_spin_button_get_text_area_size (GtkEntry *entry,
244 245 246 247
                                                gint     *x,
                                                gint     *y,
                                                gint     *width,
                                                gint     *height);
248
static void gtk_spin_button_snap           (GtkSpinButton      *spin_button,
249
                                            gdouble             val);
250
static void gtk_spin_button_insert_text    (GtkEditable        *editable,
251 252 253
                                            const gchar        *new_text,
                                            gint                new_text_length,
                                            gint               *position);
254
static void gtk_spin_button_real_spin      (GtkSpinButton      *spin_button,
255
                                            gdouble             step);
256
static void gtk_spin_button_real_change_value (GtkSpinButton   *spin,
257
                                               GtkScrollType    scroll);
258

259
static gint gtk_spin_button_default_input  (GtkSpinButton      *spin_button,
260
                                            gdouble            *new_val);
261
static gint gtk_spin_button_default_output (GtkSpinButton      *spin_button);
262

263
static gint spin_button_get_arrow_size     (GtkSpinButton      *spin_button);
264

265
static guint spinbutton_signals[LAST_SIGNAL] = {0};
266

267
#define NO_ARROW 2
268

Matthias Clasen's avatar
Matthias Clasen committed
269
G_DEFINE_TYPE_WITH_CODE (GtkSpinButton, gtk_spin_button, GTK_TYPE_ENTRY,
270 271
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
                                                gtk_spin_button_editable_init))
272

273 274 275 276 277
#define add_spin_binding(binding_set, keyval, mask, scroll)            \
  gtk_binding_entry_add_signal (binding_set, keyval, mask,             \
                                "change_value", 1,                     \
                                GTK_TYPE_SCROLL_TYPE, scroll)

278 279 280
static void
gtk_spin_button_class_init (GtkSpinButtonClass *class)
{
281
  GObjectClass     *gobject_class = G_OBJECT_CLASS (class);
282 283
  GtkWidgetClass   *widget_class = GTK_WIDGET_CLASS (class);
  GtkEntryClass    *entry_class = GTK_ENTRY_CLASS (class);
284
  GtkBindingSet    *binding_set;
285

286
  gobject_class->finalize = gtk_spin_button_finalize;
287 288
  gobject_class->set_property = gtk_spin_button_set_property;
  gobject_class->get_property = gtk_spin_button_get_property;
289

290
  widget_class->destroy = gtk_spin_button_destroy;
291 292 293 294
  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;
295
  widget_class->get_preferred_width = gtk_spin_button_get_preferred_width;
296
  widget_class->size_allocate = gtk_spin_button_size_allocate;
297
  widget_class->draw = gtk_spin_button_draw;
298 299 300 301 302 303 304 305
  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;
306
  widget_class->grab_notify = gtk_spin_button_grab_notify;
307 308
  widget_class->state_flags_changed = gtk_spin_button_state_flags_changed;
  widget_class->style_updated = gtk_spin_button_style_updated;
309

Owen Taylor's avatar
Owen Taylor committed
310
  entry_class->activate = gtk_spin_button_activate;
311
  entry_class->get_text_area_size = gtk_spin_button_get_text_area_size;
312 313 314

  class->input = NULL;
  class->output = NULL;
315
  class->change_value = gtk_spin_button_real_change_value;
316

317 318 319
  g_object_class_install_property (gobject_class,
                                   PROP_ADJUSTMENT,
                                   g_param_spec_object ("adjustment",
320
                                                        P_("Adjustment"),
321
                                                        P_("The adjustment that holds the value of the spin button"),
322
                                                        GTK_TYPE_ADJUSTMENT,
323
                                                        GTK_PARAM_READWRITE));
324

325 326
  g_object_class_install_property (gobject_class,
                                   PROP_CLIMB_RATE,
327
                                   g_param_spec_double ("climb-rate",
328 329 330 331 332 333 334
                                                        P_("Climb Rate"),
                                                        P_("The acceleration rate when you hold down a button"),
                                                        0.0,
                                                        G_MAXDOUBLE,
                                                        0.0,
                                                        GTK_PARAM_READWRITE));

335 336 337
  g_object_class_install_property (gobject_class,
                                   PROP_DIGITS,
                                   g_param_spec_uint ("digits",
338 339 340 341 342 343 344
                                                      P_("Digits"),
                                                      P_("The number of decimal places to display"),
                                                      0,
                                                      MAX_DIGITS,
                                                      0,
                                                      GTK_PARAM_READWRITE));

345 346
  g_object_class_install_property (gobject_class,
                                   PROP_SNAP_TO_TICKS,
347
                                   g_param_spec_boolean ("snap-to-ticks",
348 349 350 351 352
                                                         P_("Snap to Ticks"),
                                                         P_("Whether erroneous values are automatically changed to a spin button's nearest step increment"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE));

353 354 355
  g_object_class_install_property (gobject_class,
                                   PROP_NUMERIC,
                                   g_param_spec_boolean ("numeric",
356 357 358 359 360
                                                         P_("Numeric"),
                                                         P_("Whether non-numeric characters should be ignored"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE));

361 362 363
  g_object_class_install_property (gobject_class,
                                   PROP_WRAP,
                                   g_param_spec_boolean ("wrap",
364 365 366 367 368
                                                         P_("Wrap"),
                                                         P_("Whether a spin button should wrap upon reaching its limits"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE));

369 370
  g_object_class_install_property (gobject_class,
                                   PROP_UPDATE_POLICY,
371
                                   g_param_spec_enum ("update-policy",
372 373 374 375 376 377
                                                      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,
                                                      GTK_PARAM_READWRITE));

378 379 380
  g_object_class_install_property (gobject_class,
                                   PROP_VALUE,
                                   g_param_spec_double ("value",
381 382 383 384 385 386 387
                                                        P_("Value"),
                                                        P_("Reads the current value, or sets a new value"),
                                                        -G_MAXDOUBLE,
                                                        G_MAXDOUBLE,
                                                        0.0,
                                                        GTK_PARAM_READWRITE));

388
  gtk_widget_class_install_style_property_parser (widget_class,
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
                                                  g_param_spec_enum ("shadow-type",
                                                                     "Shadow Type",
                                                                     P_("Style of bevel around the spin button"),
                                                                     GTK_TYPE_SHADOW_TYPE,
                                                                     GTK_SHADOW_IN,
                                                                     GTK_PARAM_READABLE),
                                                  gtk_rc_property_parse_enum);

  /**
   * GtkSpinButton::input:
   * @spin_button: the object on which the signal was emitted
   * @new_value: return location for the new value
   *
   * 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.
   */
412
  spinbutton_signals[INPUT] =
413
    g_signal_new (I_("input"),
414 415 416 417 418 419 420
                  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);
421

422 423 424
  /**
   * GtkSpinButton::output:
   * @spin_button: the object which received the signal
425
   *
426 427 428 429 430 431 432 433 434 435
   * The ::output signal can be used to change to formatting
   * of the value that is displayed in the spin buttons entry.
   * |[
   * /&ast; show leading zeros &ast;/
   * static gboolean
   * on_output (GtkSpinButton *spin,
   *            gpointer       data)
   * {
   *    GtkAdjustment *adj;
   *    gchar *text;
436
   *    int value;
437
   *
438 439 440 441 442
   *    adj = gtk_spin_button_get_adjustment (spin);
   *    value = (int)gtk_adjustment_get_value (adj);
   *    text = g_strdup_printf ("%02d", value);
   *    gtk_entry_set_text (GTK_ENTRY (spin), text);
   *    g_free (text);
443
   *
444 445 446 447
   *    return TRUE;
   * }
   * ]|
   *
448
   * Returns: %TRUE if the value has been displayed
449
   */
450
  spinbutton_signals[OUTPUT] =
451
    g_signal_new (I_("output"),
452 453 454 455 456 457
                  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);
458 459

  spinbutton_signals[VALUE_CHANGED] =
460
    g_signal_new (I_("value-changed"),
461 462 463 464 465 466
                  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);
467

468 469 470 471 472 473 474 475 476 477 478
  /**
   * GtkSpinButton::wrapped:
   * @spinbutton: the object which received the signal
   *
   * The wrapped signal is emitted right after the spinbutton wraps
   * from its maximum to minimum value or vice-versa.
   *
   * Since: 2.10
   */
  spinbutton_signals[WRAPPED] =
    g_signal_new (I_("wrapped"),
479 480 481 482 483 484
                  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);
485

486 487
  /* Action signals */
  spinbutton_signals[CHANGE_VALUE] =
488
    g_signal_new (I_("change-value"),
Manish Singh's avatar
Manish Singh committed
489
                  G_TYPE_FROM_CLASS (gobject_class),
490 491 492 493 494 495
                  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);
496

497
  binding_set = gtk_binding_set_by_class (class);
498

499 500 501 502 503 504 505 506
  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);
507

508
  g_type_class_add_private (class, sizeof (GtkSpinButtonPrivate));
509 510
}

511
static void
512
gtk_spin_button_editable_init (GtkEditableInterface *iface)
513 514 515 516
{
  iface->insert_text = gtk_spin_button_insert_text;
}

517
static void
518
gtk_spin_button_set_property (GObject      *object,
519 520 521
                              guint         prop_id,
                              const GValue *value,
                              GParamSpec   *pspec)
522
{
523
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
524
  GtkSpinButtonPrivate *priv = spin_button->priv;
525

526
  switch (prop_id)
527 528 529
    {
      GtkAdjustment *adjustment;

530 531
    case PROP_ADJUSTMENT:
      adjustment = GTK_ADJUSTMENT (g_value_get_object (value));
532
      if (!adjustment)
533
        adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
534 535
      gtk_spin_button_set_adjustment (spin_button, adjustment);
      break;
536
    case PROP_CLIMB_RATE:
537
      gtk_spin_button_configure (spin_button,
538 539 540
                                 priv->adjustment,
                                 g_value_get_double (value),
                                 priv->digits);
541
      break;
542
    case PROP_DIGITS:
543
      gtk_spin_button_configure (spin_button,
544 545 546
                                 priv->adjustment,
                                 priv->climb_rate,
                                 g_value_get_uint (value));
547
      break;
548 549
    case PROP_SNAP_TO_TICKS:
      gtk_spin_button_set_snap_to_ticks (spin_button, g_value_get_boolean (value));
550
      break;
551 552
    case PROP_NUMERIC:
      gtk_spin_button_set_numeric (spin_button, g_value_get_boolean (value));
553
      break;
554 555
    case PROP_WRAP:
      gtk_spin_button_set_wrap (spin_button, g_value_get_boolean (value));
556
      break;
557 558
    case PROP_UPDATE_POLICY:
      gtk_spin_button_set_update_policy (spin_button, g_value_get_enum (value));
559
      break;
560 561
    case PROP_VALUE:
      gtk_spin_button_set_value (spin_button, g_value_get_double (value));
562 563
      break;
    default:
564
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
565 566 567 568 569
      break;
    }
}

static void
570
gtk_spin_button_get_property (GObject      *object,
571 572 573
                              guint         prop_id,
                              GValue       *value,
                              GParamSpec   *pspec)
574
{
575
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
576
  GtkSpinButtonPrivate *priv = spin_button->priv;
577

578
  switch (prop_id)
579
    {
580
    case PROP_ADJUSTMENT:
581
      g_value_set_object (value, priv->adjustment);
582
      break;
583
    case PROP_CLIMB_RATE:
584
      g_value_set_double (value, priv->climb_rate);
585
      break;
586
    case PROP_DIGITS:
587
      g_value_set_uint (value, priv->digits);
588
      break;
589
    case PROP_SNAP_TO_TICKS:
590
      g_value_set_boolean (value, priv->snap_to_ticks);
591
      break;
592
    case PROP_NUMERIC:
593
      g_value_set_boolean (value, priv->numeric);
594
      break;
595
    case PROP_WRAP:
596
      g_value_set_boolean (value, priv->wrap);
597
      break;
598
    case PROP_UPDATE_POLICY:
599
      g_value_set_enum (value, priv->update_policy);
600
      break;
601
     case PROP_VALUE:
602
       g_value_set_double (value, priv->adjustment->value);
603 604
      break;
    default:
605
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
606 607 608 609
      break;
    }
}

610 611 612
static void
gtk_spin_button_init (GtkSpinButton *spin_button)
{
613
  GtkSpinButtonPrivate *priv;
614
  GtkStyleContext *context;
615 616 617

  spin_button->priv = G_TYPE_INSTANCE_GET_PRIVATE (spin_button,
                                                   GTK_TYPE_SPIN_BUTTON,
618
                                                   GtkSpinButtonPrivate);
619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635
  priv = spin_button->priv;

  priv->adjustment = NULL;
  priv->panel = NULL;
  priv->timer = 0;
  priv->climb_rate = 0.0;
  priv->timer_step = 0.0;
  priv->update_policy = GTK_UPDATE_ALWAYS;
  priv->in_child = NO_ARROW;
  priv->click_child = NO_ARROW;
  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;
636

637
  gtk_spin_button_set_adjustment (spin_button,
Javier Jardón's avatar
Javier Jardón committed
638
                                  gtk_adjustment_new (0, 0, 0, 0, 0, 0));
639 640 641

  context = gtk_widget_get_style_context (GTK_WIDGET (spin_button));
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_SPINBUTTON);
642 643 644
}

static void
645
gtk_spin_button_finalize (GObject *object)
646
{
647
  gtk_spin_button_set_adjustment (GTK_SPIN_BUTTON (object), NULL);
648

Matthias Clasen's avatar
Matthias Clasen committed
649
  G_OBJECT_CLASS (gtk_spin_button_parent_class)->finalize (object);
650 651
}

652
static void
653
gtk_spin_button_destroy (GtkWidget *widget)
654
{
655
  gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));
656

657
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->destroy (widget);
658 659
}

660 661 662
static void
gtk_spin_button_map (GtkWidget *widget)
{
663
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
664
  GtkSpinButtonPrivate *priv = spin_button->priv;
665

666
  if (gtk_widget_get_realized (widget) && !gtk_widget_get_mapped (widget))
667
    {
Matthias Clasen's avatar
Matthias Clasen committed
668
      GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->map (widget);
669
      gdk_window_show (priv->panel);
670 671 672 673 674 675
    }
}

static void
gtk_spin_button_unmap (GtkWidget *widget)
{
676
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
677
  GtkSpinButtonPrivate *priv = spin_button->priv;
678

679
  if (gtk_widget_get_mapped (widget))
680
    {
681 682
      gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));

683
      gdk_window_hide (priv->panel);
Matthias Clasen's avatar
Matthias Clasen committed
684
      GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unmap (widget);
685 686 687 688 689 690
    }
}

static void
gtk_spin_button_realize (GtkWidget *widget)
{
691
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
692
  GtkSpinButtonPrivate *priv = spin_button->priv;
693 694
  GtkStyleContext *context;
  GtkStateFlags state;
695
  GtkAllocation allocation;
696
  GtkRequisition requisition;
697
  GdkWindowAttr attributes;
698
  gint attributes_mask;
Manish Singh's avatar
Manish Singh committed
699
  gboolean return_val;
700
  gint arrow_size;
701
  GtkBorder padding;
702

703
  arrow_size = spin_button_get_arrow_size (spin_button);
704

705
  gtk_widget_get_preferred_size (widget, &requisition, NULL);
706
  gtk_widget_get_allocation (widget, &allocation);
707

708
  gtk_widget_set_events (widget, gtk_widget_get_events (widget) |
709
                         GDK_KEY_RELEASE_MASK);
Matthias Clasen's avatar
Matthias Clasen committed
710
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->realize (widget);
711

712
  attributes.window_type = GDK_WINDOW_CHILD;
713
  attributes.wclass = GDK_INPUT_ONLY;
714 715
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.event_mask = gtk_widget_get_events (widget);
716 717
  attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK
    | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK
718 719
    | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;

720
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
721

722 723 724 725 726
  state = gtk_widget_get_state_flags (widget);
  context = gtk_widget_get_style_context (widget);
  gtk_style_context_get_padding (context, state, &padding);

  attributes.x = allocation.x + allocation.width - arrow_size - (padding.left + padding.right);
727
  attributes.y = allocation.y + (allocation.height - requisition.height) / 2;
728
  attributes.width = arrow_size + padding.left + padding.right;
729
  attributes.height = requisition.height;
730

731
  priv->panel = gdk_window_new (gtk_widget_get_window (widget),
732 733 734
                                &attributes, attributes_mask);
  gdk_window_set_user_data (priv->panel, widget);

735
  return_val = FALSE;
Manish Singh's avatar
Manish Singh committed
736
  g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
737 738
  if (return_val == FALSE)
    gtk_spin_button_default_output (spin_button);
739 740

  gtk_widget_queue_resize (GTK_WIDGET (spin_button));
741 742 743 744 745
}

static void
gtk_spin_button_unrealize (GtkWidget *widget)
{
746
  GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
747
  GtkSpinButtonPrivate *priv = spin->priv;
748

Matthias Clasen's avatar
Matthias Clasen committed
749
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unrealize (widget);
750

751
  if (priv->panel)
752
    {
753 754 755
      gdk_window_set_user_data (priv->panel, NULL);
      gdk_window_destroy (priv->panel);
      priv->panel = NULL;
756 757 758
    }
}

759
static int
760
compute_double_length (double val, int digits)
761
{
762
  int a;
763 764 765 766
  int extra;

  a = 1;
  if (fabs (val) > 1.0)
767
    a = floor (log10 (fabs (val))) + 1;
768 769

  extra = 0;
770

771
  /* The dot: */
772
  if (digits > 0)
773 774 775 776 777 778
    extra++;

  /* The sign: */
  if (val < 0)
    extra++;

779
  return a + digits + extra;
780 781
}

782
static void
783 784 785
gtk_spin_button_get_preferred_width (GtkWidget *widget,
                                     gint      *minimum,
                                     gint      *natural)
786
{
787
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
788
  GtkSpinButtonPrivate *priv = spin_button->priv;
789
  GtkEntry *entry = GTK_ENTRY (widget);
790 791
  GtkStyleContext *style_context;
  GtkBorder padding;
792
  gint arrow_size;
793

794
  style_context = gtk_widget_get_style_context (widget);
795

796
  arrow_size = spin_button_get_arrow_size (spin_button);
797

798
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->get_preferred_width (widget, minimum, natural);
799

800
  if (gtk_entry_get_width_chars (entry) < 0)
801
    {
802
      PangoContext *context;
803
      const PangoFontDescription *font_desc;
804
      PangoFontMetrics *metrics;
805 806
      gint width;
      gint w;
807
      gint string_len;
808
      gint max_string_len;
809
      gint digit_width;
810 811
      gboolean interior_focus;
      gint focus_width;
812
      gint xborder, yborder;
813
      GtkBorder inner_border;
814 815

      gtk_widget_style_get (widget,
816 817 818
                            "interior-focus", &interior_focus,
                            "focus-line-width", &focus_width,
                            NULL);
819

820
      font_desc = gtk_style_context_get_font (style_context, 0);
821

822
      context = gtk_widget_get_pango_context (widget);
823
      metrics = pango_context_get_metrics (context, font_desc,
824
                                           pango_context_get_language (context));
825 826

      digit_width = pango_font_metrics_get_approximate_digit_width (metrics);
Kristian Rietveld's avatar
Kristian Rietveld committed
827 828
      digit_width = PANGO_SCALE *
        ((digit_width + PANGO_SCALE - 1) / PANGO_SCALE);
829 830

      pango_font_metrics_unref (metrics);
831

832 833
      /* Get max of MIN_SPIN_BUTTON_WIDTH, size of upper, size of lower */
      width = MIN_SPIN_BUTTON_WIDTH;
834 835
      max_string_len = MAX (10, compute_double_length (1e9 * priv->adjustment->step_increment,
                                                       priv->digits));
836

837 838
      string_len = compute_double_length (priv->adjustment->upper,
                                          priv->digits);
Kristian Rietveld's avatar
Kristian Rietveld committed
839
      w = PANGO_PIXELS (MIN (string_len, max_string_len) * digit_width);
840
      width = MAX (width, w);
841
      string_len = compute_double_length (priv->adjustment->lower, priv->digits);
Kristian Rietveld's avatar
Kristian Rietveld committed
842
      w = PANGO_PIXELS (MIN (string_len, max_string_len) * digit_width);
843
      width = MAX (width, w);
844

845
      _gtk_entry_get_borders (entry, &xborder, &yborder);
846
      _gtk_entry_effective_inner_border (entry, &inner_border);
847

848 849 850 851
      width += xborder * 2 + inner_border.left + inner_border.right;

      *minimum = width;
      *natural = width;
852
    }
853

854 855 856 857 858 859
  gtk_style_context_get_padding (style_context,
                                 gtk_widget_get_state_flags (widget),
                                 &padding);

  *minimum += arrow_size + padding.left + padding.right;
  *natural += arrow_size + padding.left + padding.right;
860 861 862 863
}

static void
gtk_spin_button_size_allocate (GtkWidget     *widget,
864
                               GtkAllocation *allocation)
865
{
866
  GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
867
  GtkSpinButtonPrivate *priv = spin->priv;
868
  GtkAllocation panel_allocation;
869
  GtkRequisition requisition;
870 871 872
  GtkStyleContext *context;
  GtkStateFlags state;
  GtkBorder padding;
873
  gint arrow_size;
874 875
  gint panel_width;

876
  arrow_size = spin_button_get_arrow_size (spin);
877 878 879 880 881
  context = gtk_widget_get_style_context (widget);
  state = gtk_widget_get_state_flags (widget);

  gtk_style_context_get_padding (context, state, &padding);
  panel_width = arrow_size + padding.left + padding.right;
882

883
  gtk_widget_get_preferred_size (widget, &requisition, NULL);
884

885 886
  gtk_widget_set_allocation (widget, allocation);

887
  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
888
    panel_allocation.x = allocation->x;
889
  else
890
    panel_allocation.x = allocation->x + allocation->width - panel_width;
891

892
  panel_allocation.width = panel_width;
893
  panel_allocation.height = MIN (requisition.height, allocation->height);
894

895 896
  panel_allocation.y = allocation->y +
                       (allocation->height - requisition.height) / 2;
897

898
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->size_allocate (widget, allocation);
Tim Janik's avatar
Tim Janik committed
899

900
  if (gtk_widget_get_realized (widget))
Tim Janik's avatar
Tim Janik committed
901
    {
902
      gdk_window_move_resize (priv->panel,
903 904 905 906
                              panel_allocation.x,
                              panel_allocation.y,
                              panel_allocation.width,
                              panel_allocation.height);
Tim Janik's avatar
Tim Janik committed
907
    }
908

909
  gtk_widget_queue_draw (GTK_WIDGET (spin));
910 911
}

912
static gint
913 914
gtk_spin_button_draw (GtkWidget      *widget,
                      cairo_t        *cr)
915
{
916
  GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
917
  GtkSpinButtonPrivate *priv = spin->priv;
918 919 920 921 922 923 924 925 926 927 928 929
  GtkStyleContext *context;
  GtkStateFlags state = 0;
  gboolean is_rtl;

  is_rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
  context = gtk_widget_get_style_context (widget);
  gtk_style_context_save (context);

  if (is_rtl)
    gtk_style_context_set_junction_sides (context, GTK_JUNCTION_LEFT);
  else
    gtk_style_context_set_junction_sides (context, GTK_JUNCTION_RIGHT);
930

931 932
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->draw (widget, cr);

933
  gtk_style_context_restore (context);
934
  cairo_save (cr);
935

936
  state = gtk_widget_get_state_flags (widget);
937

938 939 940 941 942
  if (gtk_widget_has_focus (widget))
    state |= GTK_STATE_FLAG_FOCUSED;

  gtk_style_context_save (context);
  gtk_style_context_set_state (context, state);
943
  gtk_style_context_remove_class (context, GTK_STYLE_CLASS_ENTRY);
944 945 946 947 948

  if (is_rtl)
    gtk_style_context_set_junction_sides (context, GTK_JUNCTION_RIGHT);
  else
    gtk_style_context_set_junction_sides (context, GTK_JUNCTION_LEFT);
949

950
  gtk_cairo_transform_to_window (cr, widget, priv->panel);
951

952
  if (gtk_entry_get_has_frame (GTK_ENTRY (widget)))
953 954 955
    gtk_render_background (context, cr, 0, 0,
                           gdk_window_get_width (priv->panel),
                           gdk_window_get_height (priv->panel));
956

957 958
  gtk_spin_button_draw_arrow (spin, context, cr, GTK_ARROW_UP);
  gtk_spin_button_draw_arrow (spin, context, cr, GTK_ARROW_DOWN);
959

960
  gtk_style_context_restore (context);
961
  cairo_restore (cr);
962

963 964 965 966 967 968 969
  return FALSE;
}

static gboolean
spin_button_at_limit (GtkSpinButton *spin_button,
                     GtkArrowType   arrow)
{
970
  GtkSpinButtonPrivate *priv = spin_button->priv;
971 972
  GtkArrowType effective_arrow;

973
  if (priv->wrap)
974 975
    return FALSE;

976
  if (priv->adjustment->step_increment > 0)
977 978
    effective_arrow = arrow;
  else
979 980
    effective_arrow = arrow == GTK_ARROW_UP ? GTK_ARROW_DOWN : GTK_ARROW_UP;

981
  if (effective_arrow == GTK_ARROW_UP &&
982
      (priv->adjustment->upper - priv->adjustment->value <= EPSILON))
983
    return TRUE;
984

985
  if (effective_arrow == GTK_ARROW_DOWN &&
986
      (priv->adjustment->value - priv->adjustment->lower <= EPSILON))
987
    return TRUE;
988

989 990 991 992
  return FALSE;
}

static void
993 994
gtk_spin_button_draw_arrow (GtkSpinButton   *spin_button,
                            GtkStyleContext *context,
995 996
                            cairo_t         *cr,
                            GtkArrowType     arrow_type)
997
{
998
  GtkSpinButtonPrivate *priv;
999 1000
  GtkJunctionSides junction;
  GtkStateFlags state;
1001
  GtkWidget *widget;
1002 1003
  GtkBorder padding;
  gdouble angle;
1004 1005
  gint x;
  gint y;
1006
  gint panel_height;
1007 1008
  gint height;
  gint width;
1009
  gint h, w;
1010

1011
  g_return_if_fail (arrow_type == GTK_ARROW_UP || arrow_type == GTK_ARROW_DOWN);
1012

1013 1014 1015
  gtk_style_context_save (context);
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON);

1016
  priv = spin_button->priv;
1017
  widget = GTK_WIDGET (spin_button);
1018
  junction = gtk_style_context_get_junction_sides (context);
1019

1020
  panel_height = gdk_window_get_height (priv->panel);
1021

1022 1023 1024 1025
  if (arrow_type == GTK_ARROW_UP)
    {
      x = 0;
      y = 0;
1026

1027
      height = panel_height / 2;
1028 1029
      angle = 0;
      junction |= GTK_JUNCTION_BOTTOM;
1030 1031 1032 1033
    }
  else
    {
      x = 0;
1034
      y = panel_height / 2;
1035

1036
      height = (panel_height + 1) / 2;
1037 1038
      angle = G_PI;
      junction |= GTK_JUNCTION_TOP;
1039
    }
1040

1041
  if (spin_button_at_limit (spin_button, arrow_type))
1042
    state = GTK_STATE_FLAG_INSENSITIVE;
1043 1044 1045
  else
    {
      if (priv->click_child == arrow_type)
1046
        state = GTK_STATE_ACTIVE;
1047
      else
1048 1049 1050
        {
          if (priv->in_child == arrow_type &&
              priv->click_child == NO_ARROW)
1051
            state = GTK_STATE_FLAG_PRELIGHT;
1052
          else
1053
            state = gtk_widget_get_state_flags (widget);
1054 1055
        }
    }
1056 1057 1058 1059 1060 1061 1062 1063 1064

  gtk_style_context_get_padding (context, state, &padding);
  gtk_style_context_set_junction_sides (context, junction);
  gtk_style_context_set_state (context, state);

  width = spin_button_get_arrow_size (spin_button) + padding.left + padding.right;

  gtk_render_background (context, cr, x, y, width, height);
  gtk_render_frame (context, cr, x, y, width, height);
1065

1066
  height = panel_height;
1067 1068 1069 1070 1071

  if (arrow_type == GTK_ARROW_DOWN)
    {
      y = height / 2;
      height = height - y - 2;
1072
    }
1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088
  else
    {
      y = 2;
      height = height / 2 - 2;
    }

  width -= 3;

  if (widget && gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
    x = 2;
  else
    x = 1;

  w = width / 2;
  w -= w % 2 - 1; /* force odd */
  h = (w + 1) / 2;
1089

1090 1091
  x += (width - w) / 2;
  y += (height - h) / 2;
1092

1093 1094 1095
  height = h;
  width = w;

1096 1097 1098 1099
  gtk_render_arrow (context, cr,
                    angle, x, y, width);

  gtk_style_context_restore (context);
1100 1101 1102 1103
}

static gint
gtk_spin_button_enter_notify (GtkWidget        *widget,
1104
                              GdkEventCrossing *event)
1105
{
1106
  GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1107
  GtkSpinButtonPrivate *priv = spin->priv;
1108
  GtkRequisition requisition;
1109

1110
  if (event->window == priv->panel)
1111
    {
1112
      GdkDevice *device;
1113 1114 1115
      gint x;
      gint y;

1116
      device = gdk_event_get_device ((GdkEvent *) event);
1117
      gdk_window_get_device_position (priv->panel, device, &x, &y, NULL);
1118

1119
      gtk_widget_get_preferred_size (widget, &requisition, NULL);
1120 1121

      if (y <= requisition.height / 2)
1122
        priv->in_child = GTK_ARROW_UP;
1123
      else
1124
        priv->in_child = GTK_ARROW_DOWN;
1125

1126
      gtk_widget_queue_draw (GTK_WIDGET (spin));
1127
    }
1128

1129 1130 1131
  if (GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->enter_notify_event)
    return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->enter_notify_event (