gtkspinbutton.c 83 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 "gtkicontheme.h"
42
#include "gtkmarshalers.h"
43
#include "gtksettings.h"
44
#include "gtkstock.h"
45
#include "gtkprivate.h"
46
#include "gtkintl.h"
47
#include "gtktypebuiltins.h"
48

49 50
#include "a11y/gtkspinbuttonaccessible.h"

51 52 53
#define MIN_SPIN_BUTTON_WIDTH 30
#define MAX_TIMER_CALLS       5
#define EPSILON               1e-10
54
#define MAX_DIGITS            20
55
#define MIN_ARROW_WIDTH       6
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
/**
 * 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;
96
 *   GtkAdjustment *adjustment;
97
 *
98
 *   adjustment = gtk_adjustment_new (50.0, 0.0, 100.0, 1.0, 5.0, 0.0);
99 100 101 102 103
 *
 *   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;/
104
 *   button = gtk_spin_button_new (adjustment, 1.0, 0);
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
 *   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;
130
 *   GtkAdjustment *adjustment;
131
 *
132
 *   adjustment = gtk_adjustment_new (2.500, 0.0, 5.0, 0.001, 0.1, 0.0);
133 134 135 136 137
 *
 *   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;/
138
 *   button = gtk_spin_button_new (adjustment, 0.001, 3);
139 140 141 142 143 144 145 146
 *   gtk_container_add (GTK_CONTAINER (window), button);
 *
 *   gtk_widget_show_all (window);
 * }
 * </programlisting>
 * </example>
 */

147
struct _GtkSpinButtonPrivate
148 149 150
{
  GtkAdjustment *adjustment;

151 152 153 154 155
  GdkWindow     *down_panel;
  GdkWindow     *up_panel;

  GdkWindow     *click_child;
  GdkWindow     *in_child;
156 157 158

  guint32        timer;

159 160
  GtkSpinButtonUpdatePolicy update_policy;

161 162 163 164 165 166 167 168 169 170 171 172
  gdouble        climb_rate;
  gdouble        timer_step;

  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;
};

173
enum {
174 175 176 177 178 179 180 181 182
  PROP_0,
  PROP_ADJUSTMENT,
  PROP_CLIMB_RATE,
  PROP_DIGITS,
  PROP_SNAP_TO_TICKS,
  PROP_NUMERIC,
  PROP_WRAP,
  PROP_UPDATE_POLICY,
  PROP_VALUE
183 184
};

185 186 187 188 189
/* Signals */
enum
{
  INPUT,
  OUTPUT,
190
  VALUE_CHANGED,
191
  CHANGE_VALUE,
192
  WRAPPED,
193 194
  LAST_SIGNAL
};
195

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

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

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

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

Matthias Clasen's avatar
Matthias Clasen committed
266
G_DEFINE_TYPE_WITH_CODE (GtkSpinButton, gtk_spin_button, GTK_TYPE_ENTRY,
267 268
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
                                                gtk_spin_button_editable_init))
269

270 271 272 273 274
#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)

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

283
  gobject_class->finalize = gtk_spin_button_finalize;
284 285
  gobject_class->set_property = gtk_spin_button_set_property;
  gobject_class->get_property = gtk_spin_button_get_property;
286

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

Owen Taylor's avatar
Owen Taylor committed
306
  entry_class->activate = gtk_spin_button_activate;
307
  entry_class->get_text_area_size = gtk_spin_button_get_text_area_size;
308 309 310

  class->input = NULL;
  class->output = NULL;
311
  class->change_value = gtk_spin_button_real_change_value;
312

313 314 315
  g_object_class_install_property (gobject_class,
                                   PROP_ADJUSTMENT,
                                   g_param_spec_object ("adjustment",
316
                                                        P_("Adjustment"),
317
                                                        P_("The adjustment that holds the value of the spin button"),
318
                                                        GTK_TYPE_ADJUSTMENT,
319
                                                        GTK_PARAM_READWRITE));
320

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

331 332 333
  g_object_class_install_property (gobject_class,
                                   PROP_DIGITS,
                                   g_param_spec_uint ("digits",
334 335 336 337 338 339 340
                                                      P_("Digits"),
                                                      P_("The number of decimal places to display"),
                                                      0,
                                                      MAX_DIGITS,
                                                      0,
                                                      GTK_PARAM_READWRITE));

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

349 350 351
  g_object_class_install_property (gobject_class,
                                   PROP_NUMERIC,
                                   g_param_spec_boolean ("numeric",
352 353 354 355 356
                                                         P_("Numeric"),
                                                         P_("Whether non-numeric characters should be ignored"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE));

357 358 359
  g_object_class_install_property (gobject_class,
                                   PROP_WRAP,
                                   g_param_spec_boolean ("wrap",
360 361 362 363 364
                                                         P_("Wrap"),
                                                         P_("Whether a spin button should wrap upon reaching its limits"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE));

365 366
  g_object_class_install_property (gobject_class,
                                   PROP_UPDATE_POLICY,
367
                                   g_param_spec_enum ("update-policy",
368 369 370 371 372 373
                                                      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));

374 375 376
  g_object_class_install_property (gobject_class,
                                   PROP_VALUE,
                                   g_param_spec_double ("value",
377 378 379 380 381 382 383
                                                        P_("Value"),
                                                        P_("Reads the current value, or sets a new value"),
                                                        -G_MAXDOUBLE,
                                                        G_MAXDOUBLE,
                                                        0.0,
                                                        GTK_PARAM_READWRITE));

384
  gtk_widget_class_install_style_property_parser (widget_class,
385 386 387 388 389 390 391 392 393 394 395
                                                  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
396
   * @new_value: (out) (type double): return location for the new value
397 398 399 400 401 402 403 404 405 406 407
   *
   * 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.
   */
408
  spinbutton_signals[INPUT] =
409
    g_signal_new (I_("input"),
410 411 412 413 414 415 416
                  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);
417

418 419 420
  /**
   * GtkSpinButton::output:
   * @spin_button: the object which received the signal
421
   *
422 423 424 425 426 427 428 429
   * 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)
   * {
430
   *    GtkAdjustment *adjustment;
431
   *    gchar *text;
432
   *    int value;
433
   *
434 435
   *    adjustment = gtk_spin_button_get_adjustment (spin);
   *    value = (int)gtk_adjustment_get_value (adjustment);
436 437 438
   *    text = g_strdup_printf ("%02d", value);
   *    gtk_entry_set_text (GTK_ENTRY (spin), text);
   *    g_free (text);
439
   *
440 441 442 443
   *    return TRUE;
   * }
   * ]|
   *
444
   * Returns: %TRUE if the value has been displayed
445
   */
446
  spinbutton_signals[OUTPUT] =
447
    g_signal_new (I_("output"),
448 449 450 451 452 453
                  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);
454 455

  spinbutton_signals[VALUE_CHANGED] =
456
    g_signal_new (I_("value-changed"),
457 458 459 460 461 462
                  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);
463

464 465 466 467 468 469 470 471 472 473 474
  /**
   * 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"),
475 476 477 478 479 480
                  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);
481

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

493
  binding_set = gtk_binding_set_by_class (class);
494

495 496 497 498 499 500 501 502
  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);
503

504
  g_type_class_add_private (class, sizeof (GtkSpinButtonPrivate));
505 506

  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SPIN_BUTTON_ACCESSIBLE);
507 508
}

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

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

524
  switch (prop_id)
525 526 527
    {
      GtkAdjustment *adjustment;

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

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

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

608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
static gint
get_icon_size (void)
{
  gint width, height, icon_size;

  gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height);
  icon_size = MAX (width, height);

  return icon_size;
}

static GdkPixbuf *
create_one_pixbuf (GtkStyleContext *context,
                   const gchar *icon_name)
{
  GtkIconInfo *icon_info;
  GdkPixbuf *pix;
  gint size = get_icon_size ();

  icon_info = gtk_icon_theme_lookup_icon (gtk_icon_theme_get_default (),
                                          icon_name, size,
629 630
                                          GTK_ICON_LOOKUP_GENERIC_FALLBACK |
                                          GTK_ICON_LOOKUP_USE_BUILTIN);
631 632 633 634 635 636 637 638 639 640 641 642

  if (icon_info != NULL)
    {
      pix = gtk_icon_info_load_symbolic_for_context (icon_info, context, 
                                                     NULL, NULL);
      gtk_icon_info_free (icon_info);
    }
  else
    {
      GtkIconSet *icon_set;

      icon_set = gtk_style_context_lookup_icon_set (context, GTK_STOCK_MISSING_IMAGE);
643
      pix = gtk_icon_set_render_icon_pixbuf (icon_set, context, GTK_ICON_SIZE_MENU);
644 645 646 647 648 649 650

      g_warning ("Unable to fetch icon %s from the icon theme", icon_name);
    }

  return pix;
}

651 652 653
static void
gtk_spin_button_init (GtkSpinButton *spin_button)
{
654
  GtkSpinButtonPrivate *priv;
655
  GtkStyleContext *context;
656 657 658

  spin_button->priv = G_TYPE_INSTANCE_GET_PRIVATE (spin_button,
                                                   GTK_TYPE_SPIN_BUTTON,
659
                                                   GtkSpinButtonPrivate);
660 661 662
  priv = spin_button->priv;

  priv->adjustment = NULL;
663 664
  priv->down_panel = NULL;
  priv->up_panel = NULL;
665 666 667 668
  priv->timer = 0;
  priv->climb_rate = 0.0;
  priv->timer_step = 0.0;
  priv->update_policy = GTK_UPDATE_ALWAYS;
669 670
  priv->in_child = NULL;
  priv->click_child = NULL;
671 672 673 674 675 676 677
  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;
678

679
  gtk_spin_button_set_adjustment (spin_button,
Javier Jardón's avatar
Javier Jardón committed
680
                                  gtk_adjustment_new (0, 0, 0, 0, 0, 0));
681 682 683

  context = gtk_widget_get_style_context (GTK_WIDGET (spin_button));
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_SPINBUTTON);
684 685 686
}

static void
687
gtk_spin_button_finalize (GObject *object)
688
{
689
  gtk_spin_button_set_adjustment (GTK_SPIN_BUTTON (object), NULL);
690

Matthias Clasen's avatar
Matthias Clasen committed
691
  G_OBJECT_CLASS (gtk_spin_button_parent_class)->finalize (object);
692 693
}

694
static void
695
gtk_spin_button_destroy (GtkWidget *widget)
696
{
697
  gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));
698

699
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->destroy (widget);
700 701
}

702 703 704
static void
gtk_spin_button_map (GtkWidget *widget)
{
705
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
706
  GtkSpinButtonPrivate *priv = spin_button->priv;
707

708
  if (gtk_widget_get_realized (widget) && !gtk_widget_get_mapped (widget))
709
    {
Matthias Clasen's avatar
Matthias Clasen committed
710
      GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->map (widget);
711 712
      gdk_window_show (priv->down_panel);
      gdk_window_show (priv->up_panel);
713 714 715 716 717 718
    }
}

static void
gtk_spin_button_unmap (GtkWidget *widget)
{
719
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
720
  GtkSpinButtonPrivate *priv = spin_button->priv;
721

722
  if (gtk_widget_get_mapped (widget))
723
    {
724 725
      gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));

726 727
      gdk_window_hide (priv->down_panel);
      gdk_window_hide (priv->up_panel);
Matthias Clasen's avatar
Matthias Clasen committed
728
      GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unmap (widget);
729 730 731 732
    }
}

static void
733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
gtk_spin_button_panel_nthchildize_context (GtkSpinButton *spin_button,
                                           GtkStyleContext *context,
                                           GdkWindow *panel)
{
  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
   */
  path = gtk_widget_path_copy (gtk_widget_get_path (widget));
  direction = gtk_widget_get_direction (widget);
  siblings_path = gtk_widget_path_new ();

  /* 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);
    }

  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);

  if (panel == spin_button->priv->down_panel)
    {
      gtk_widget_path_append_with_siblings (path, siblings_path, up_pos);
      gtk_widget_path_append_with_siblings (path, siblings_path, down_pos);
    }
  else
    {
      gtk_widget_path_append_with_siblings (path, siblings_path, down_pos);
      gtk_widget_path_append_with_siblings (path, siblings_path, up_pos);
    }

  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)
790
{
791
  GtkSpinButtonPrivate *priv = spin_button->priv;
792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819
  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;

820 821 822 823 824 825
  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) ||
826
      !gtk_editable_get_editable (GTK_EDITABLE (spin_button)))
827 828 829
    {
      state |= GTK_STATE_FLAG_INSENSITIVE;
    }
830 831 832
  else
    {
      if (priv->click_child == panel)
833 834 835 836
        state |= GTK_STATE_ACTIVE;
      else if (priv->in_child == panel &&
               priv->click_child == NULL)
        state |= GTK_STATE_FLAG_PRELIGHT;
837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858
    }

  return state;
}

static GtkStyleContext *
gtk_spin_button_panel_get_context (GtkSpinButton *spin_button,
                                   GdkWindow *panel)
{
  GtkStyleContext *context;

  context = gtk_style_context_new ();
  gtk_spin_button_panel_nthchildize_context (spin_button, context, panel);

  return context;
}

static gint
gtk_spin_button_panel_get_width (GtkSpinButton *spin_button,
                                 GdkWindow *panel)
{
  GtkBorder button_padding, button_border;
859 860
  GtkStyleContext *context;
  GtkStateFlags state;
861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881

  context = gtk_spin_button_panel_get_context (spin_button, panel);
  state = gtk_spin_button_panel_get_state (spin_button, panel);

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

  g_object_unref (context);

  return get_icon_size () + button_padding.left + button_padding.right +
    button_border.left + button_border.right;
}

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;
882
  GtkRequisition requisition;
883
  gint req_height;
884 885 886
  gint up_panel_width, down_panel_width;
  GtkStyleContext *context;
  GtkBorder space;
887

888
  gtk_widget_get_allocation (widget, &spin_allocation);
889
  gtk_widget_get_preferred_size (widget, &requisition, NULL);
890 891 892 893

  context = gtk_widget_get_style_context (GTK_WIDGET (spin_button));
  gtk_style_context_get_border (context, GTK_STATE_NORMAL, &space);

894
  req_height = requisition.height - gtk_widget_get_margin_top (widget) - gtk_widget_get_margin_bottom (widget);
895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930
  down_panel_width = gtk_spin_button_panel_get_width (spin_button, priv->down_panel);
  up_panel_width = gtk_spin_button_panel_get_width (spin_button, priv->up_panel);

  /* both panels have the same size, and they're as big as the entry allocation,
   * excluding margins
   */
  allocation.height = MIN (req_height, spin_allocation.height) - space.top - space.bottom;
  allocation.y = spin_allocation.y + space.top + (spin_allocation.height - req_height) / 2;
  down_allocation = up_allocation = allocation;

  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 + space.left;
      down_allocation.x = up_allocation.x + up_panel_width;
    }
  else
    {
      up_allocation.x = spin_allocation.x + spin_allocation.width - up_panel_width - space.right;
      down_allocation.x = up_allocation.x - down_panel_width;
    }

  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)
{
931
  GtkSpinButtonPrivate *priv = spin_button->priv;
932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979
  GtkStyleContext *context;
  GtkStateFlags state;
  GtkWidget *widget;
  gdouble width, height, x, y;
  GdkPixbuf *pix;

  widget = GTK_WIDGET (spin_button);

  cairo_save (cr);
  gtk_cairo_transform_to_window (cr, widget, panel);

  context = gtk_spin_button_panel_get_context (spin_button, panel);
  state = gtk_spin_button_panel_get_state (spin_button, panel);
  gtk_style_context_set_state (context, state);

  height = gdk_window_get_height (panel);
  width = gdk_window_get_width (panel);

  if (panel == priv->down_panel)
    pix = create_one_pixbuf (context, "list-remove-symbolic");
  else
    pix = create_one_pixbuf (context, "list-add-symbolic");

  gtk_render_background (context, cr,
                         0, 0, width, height);
  gtk_render_frame (context, cr,
                    0, 0, width, height);

  x = floor ((width - gdk_pixbuf_get_width (pix)) / 2.0);
  y = floor ((height - gdk_pixbuf_get_height (pix)) / 2.0);

  gtk_render_icon (context, cr, pix,
                   x, y);
  cairo_restore (cr);

  g_object_unref (pix);
  g_object_unref (context);
}

static void
gtk_spin_button_realize (GtkWidget *widget)
{
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
  GtkSpinButtonPrivate *priv = spin_button->priv;
  GtkAllocation down_allocation, up_allocation;
  GdkWindowAttr attributes;
  gint attributes_mask;
  gboolean return_val;
980

981
  gtk_widget_set_events (widget, gtk_widget_get_events (widget) |
982
                         GDK_KEY_RELEASE_MASK);
Matthias Clasen's avatar
Matthias Clasen committed
983
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->realize (widget);
984

985
  attributes.window_type = GDK_WINDOW_CHILD;
986
  attributes.wclass = GDK_INPUT_ONLY;
987 988
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.event_mask = gtk_widget_get_events (widget);
989 990
  attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK
    | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK
991 992
    | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;

993
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
994

995 996 997 998 999 1000 1001
  gtk_spin_button_panel_get_allocations (spin_button, &down_allocation, &up_allocation);

  /* create the left panel window */
  attributes.x = down_allocation.x;
  attributes.y = down_allocation.y;
  attributes.width = down_allocation.width;
  attributes.height = down_allocation.height;
1002

1003 1004 1005
  priv->down_panel = gdk_window_new (gtk_widget_get_window (widget),
                                     &attributes, attributes_mask);
  gdk_window_set_user_data (priv->down_panel, widget);
1006

1007 1008 1009 1010 1011 1012 1013 1014 1015
  /* create the right panel window */
  attributes.x = up_allocation.x;
  attributes.y = up_allocation.y;
  attributes.width = up_allocation.width;
  attributes.height = up_allocation.height;

  priv->up_panel = gdk_window_new (gtk_widget_get_window (widget),
                                      &attributes, attributes_mask);
  gdk_window_set_user_data (priv->up_panel, widget);
1016

1017
  return_val = FALSE;
Manish Singh's avatar
Manish Singh committed
1018
  g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
1019 1020
  if (return_val == FALSE)
    gtk_spin_button_default_output (spin_button);
1021 1022

  gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1023 1024 1025 1026 1027
}

static void
gtk_spin_button_unrealize (GtkWidget *widget)
{
1028
  GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1029
  GtkSpinButtonPrivate *priv = spin->priv;
1030

Matthias Clasen's avatar
Matthias Clasen committed
1031
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unrealize (widget);
1032

1033
  if (priv->down_panel)
1034
    {
1035 1036 1037 1038 1039 1040 1041 1042 1043 1044
      gdk_window_set_user_data (priv->down_panel, NULL);
      gdk_window_destroy (priv->down_panel);
      priv->down_panel = NULL;
    }

  if (priv->up_panel)
    {
      gdk_window_set_user_data (priv->up_panel, NULL);
      gdk_window_destroy (priv->up_panel);
      priv->up_panel = NULL;
1045 1046 1047
    }
}

1048
static int
1049
compute_double_length (double val, int digits)
1050
{
1051
  int a;
1052 1053 1054 1055
  int extra;

  a = 1;
  if (fabs (val) > 1.0)
1056
    a = floor (log10 (fabs (val))) + 1;
1057 1058

  extra = 0;
1059

1060
  /* The dot: */
1061
  if (digits > 0)
1062 1063 1064 1065 1066 1067
    extra++;

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

1068
  return a + digits + extra;
1069 1070
}

1071
static void
1072 1073 1074
gtk_spin_button_get_preferred_width (GtkWidget *widget,
                                     gint      *minimum,
                                     gint      *natural)
1075
{
1076
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
1077
  GtkSpinButtonPrivate *priv = spin_button->priv;
1078
  GtkEntry *entry = GTK_ENTRY (widget);
1079
  GtkStyleContext *style_context;
1080
  gint up_panel_width, down_panel_width;
1081

1082
  style_context = gtk_widget_get_style_context (widget);
1083 1084
  up_panel_width = gtk_spin_button_panel_get_width (spin_button, priv->up_panel);
  down_panel_width = gtk_spin_button_panel_get_width (spin_button, priv->down_panel);
1085

1086
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->get_preferred_width (widget, minimum, natural);
1087

1088
  if (gtk_entry_get_width_chars (entry) < 0)
1089
    {
1090
      PangoContext *context;
1091
      const PangoFontDescription *font_desc;
1092
      PangoFontMetrics *metrics;
1093 1094
      gint width;
      gint w;
1095
      gint string_len;
1096
      gint max_string_len;
1097
      gint digit_width;
1098 1099
      gboolean interior_focus;
      gint focus_width;
1100
      GtkBorder borders;
1101

1102 1103 1104 1105 1106
      gtk_style_context_get_style (style_context,
                                   "interior-focus", &interior_focus,
                                   "focus-line-width", &focus_width,
                                   NULL);
      font_desc = gtk_style_context_get_font (style_context, GTK_STATE_FLAG_NORMAL);
1107

1108
      context = gtk_widget_get_pango_context (widget);
1109
      metrics = pango_context_get_metrics (context, font_desc,
1110
                                           pango_context_get_language (context));
1111 1112

      digit_width = pango_font_metrics_get_approximate_digit_width (metrics);
Kristian Rietveld's avatar
Kristian Rietveld committed
1113 1114
      digit_width = PANGO_SCALE *
        ((digit_width + PANGO_SCALE - 1) / PANGO_SCALE);
1115 1116

      pango_font_metrics_unref (metrics);
1117

1118 1119
      /* Get max of MIN_SPIN_BUTTON_WIDTH, size of upper, size of lower */
      width = MIN_SPIN_BUTTON_WIDTH;
1120
      max_string_len = MAX (10, compute_double_length (1e9 * gtk_adjustment_get_step_increment (priv->adjustment),
1121
                                                       priv->digits));
1122

1123
      string_len = compute_double_length (gtk_adjustment_get_upper (priv->adjustment),
1124
                                          priv->digits);
Kristian Rietveld's avatar
Kristian Rietveld committed
1125
      w = PANGO_PIXELS (MIN (string_len, max_string_len) * digit_width);
1126
      width = MAX (width, w);
1127
      string_len = compute_double_length (gtk_adjustment_get_lower (priv->adjustment), priv->digits);
Kristian Rietveld's avatar
Kristian Rietveld committed
1128
      w = PANGO_PIXELS (MIN (string_len, max_string_len) * digit_width);
1129
      width = MAX (width, w);
1130

1131
      _gtk_entry_get_borders (entry, &borders);
1132
      width += borders.left + borders.right;
1133 1134 1135

      *minimum = width;
      *natural = width;
1136
    }
1137

1138 1139
  *minimum += up_panel_width + down_panel_width;
  *natural += up_panel_width + down_panel_width;
1140 1141 1142 1143
}

static void
gtk_spin_button_size_allocate (GtkWidget     *widget,
1144
                               GtkAllocation *allocation)
1145
{
1146
  GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1147
  GtkSpinButtonPrivate *priv = spin->priv;
1148
  GtkAllocation down_panel_allocation, up_panel_allocation;
1149

1150
  gtk_widget_set_allocation (widget, allocation);
1151
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->size_allocate (widget, allocation);
Tim Janik's avatar
Tim Janik committed
1152

1153 1154
  gtk_spin_button_panel_get_allocations (spin, &down_panel_allocation, &up_panel_allocation);

1155
  if (gtk_widget_get_realized (widget))
Tim Janik's avatar
Tim Janik committed
1156
    {
1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167
      gdk_window_move_resize (priv->down_panel,
                              down_panel_allocation.x,
                              down_panel_allocation.y,
                              down_panel_allocation.width,
                              down_panel_allocation.height);

      gdk_window_move_resize (priv->up_panel,
                              up_panel_allocation.x,
                              up_panel_allocation.y,
                              up_panel_allocation.width,
                              up_panel_allocation.height);
Tim Janik's avatar
Tim Janik committed
1168
    }
1169

1170
  gtk_widget_queue_draw (GTK_WIDGET (spin));
1171 1172
}

1173
static gint
1174 1175
gtk_spin_button_draw (GtkWidget      *widget,
                      cairo_t        *cr)
1176
{
1177
  GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1178
  GtkSpinButtonPrivate *priv = spin->priv;
1179

1180
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->draw (widget, cr);
1181

1182 1183 1184
  /* Draw the buttons */
  gtk_spin_button_panel_draw (spin, cr, priv->down_panel);
  gtk_spin_button_panel_draw (spin, cr, priv->up_panel);
1185

1186 1187 1188 1189 1190
  return FALSE;
}

static gint
gtk_spin_button_enter_notify (GtkWidget        *widget,
1191
                              GdkEventCrossing *event)
1192
{
1193
  GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1194
  GtkSpinButtonPrivate *priv = spin->priv;
1195

1196 1197
  if ((event->window == priv->down_panel) ||
      (event->window == priv->up_panel))
1198
    {
1199
      priv->in_child = event->window;
1200
      gtk_widget_queue_draw (GTK_WIDGET (spin));
1201
    }
1202

1203
  return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->enter_notify_event (widget, event);
1204 1205 1206 1207
}

static gint
gtk_spin_button_leave_notify (GtkWidget        *widget,
1208
                              GdkEventCrossing *event)
1209
{
1210
  GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1211
  GtkSpinButtonPrivate *priv = spin->priv;
1212

1213
  if (priv->in_child != NULL)
1214
    {
1215
      priv->in_child = NULL;
1216 1217
      gtk_widget_queue_draw (GTK_WIDGET (spin));
    }
1218

1219
  return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->leave_notify_event (widget, event);
1220 1221 1222 1223
}

static gint
gtk_spin_button_focus_out (GtkWidget     *widget,
1224
                           GdkEventFocus *event)
1225
{
1226
  if (gtk_editable_get_editable (GTK_EDITABLE (widget)))
1227
    gtk_spin_button_update (GTK_SPIN_BUTTON (widget));
1228

Matthias Clasen's avatar
Matthias Clasen committed
1229
  return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->focus_out_event (widget, event);
1230 1231
}

1232 1233
static void
gtk_spin_button_grab_notify (GtkWidget *widget,
1234
                             gboolean   was_grabbed)
1235
{
Matthias Clasen's avatar
Matthias Clasen committed
1236 1237
  GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);

1238
  if (!was_grabbed)
Matthias Clasen's avatar
Matthias Clasen committed
1239
    {
1240 1241
      if (gtk_spin_button_stop_spinning (spin))
        gtk_widget_queue_draw (GTK_WIDGET (spin));
Matthias Clasen's avatar
Matthias Clasen committed
1242
    }
1243 1244
}

1245
static void
1246 1247
gtk_spin_button_state_flags_changed (GtkWidget     *widget,
                                     GtkStateFlags  previous_state)
1248
{
Matthias Clasen's avatar
Matthias Clasen committed
1249 1250
  GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);

1251
  if (!gtk_widget_is_sensitive (widget))
Matthias Clasen's avatar
Matthias Clasen committed
1252
    {
1253 1254
      if (gtk_spin_button_stop_spinning (spin))
        gtk_widget_queue_draw (GTK_WIDGET (spin));
Matthias Clasen's avatar
Matthias Clasen committed
1255
    }
1256 1257
}

1258 1259
static gint
gtk_spin_button_scroll (GtkWidget      *widget,
1260
                        GdkEventScroll *event)
1261
{
1262
  GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1263
  GtkSpinButtonPrivate *priv = spin->priv;
1264 1265 1266

  if (event->direction == GDK_SCROLL_UP)
    {
1267
      if (!gtk_widget_has_focus (widget))
1268
        gtk_widget_grab_focus (widget);
1269
      gtk_spin_button_real_spin (spin, gtk_adjustment_get_step_increment (priv->adjustment));
1270 1271