gtkspinbutton.c 89.6 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
2 3
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
4 5 6
 * GtkSpinButton widget for GTK+
 * Copyright (C) 1998 Lars Hamann and Stefan Jeske
 *
7
 * This library is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Lesser General Public
9 10 11 12 13 14
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
18
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 20
 */

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

28
#include "config.h"
29

30 31
#include "gtkspinbutton.h"

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

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

53 54
#include "a11y/gtkspinbuttonaccessible.h"

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

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

147
struct _GtkSpinButtonPrivate
148 149 150
{
  GtkAdjustment *adjustment;

151 152 153
  GdkWindow     *down_panel;
  GdkWindow     *up_panel;

154 155 156
  GtkStyleContext *down_panel_context;
  GtkStyleContext *up_panel_context;

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

  guint32        timer;

162 163
  GtkSpinButtonUpdatePolicy update_policy;

164 165 166
  gdouble        climb_rate;
  gdouble        timer_step;

167 168
  GtkOrientation orientation;

169 170 171 172 173 174 175 176 177
  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;
};

178
enum {
179 180 181 182 183 184 185 186
  PROP_0,
  PROP_ADJUSTMENT,
  PROP_CLIMB_RATE,
  PROP_DIGITS,
  PROP_SNAP_TO_TICKS,
  PROP_NUMERIC,
  PROP_WRAP,
  PROP_UPDATE_POLICY,
187 188
  PROP_VALUE,
  PROP_ORIENTATION
189 190
};

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

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

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

286
static guint spinbutton_signals[LAST_SIGNAL] = {0};
287

Matthias Clasen's avatar
Matthias Clasen committed
288
G_DEFINE_TYPE_WITH_CODE (GtkSpinButton, gtk_spin_button, GTK_TYPE_ENTRY,
289
                         G_ADD_PRIVATE (GtkSpinButton)
290
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
291 292
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
                                                gtk_spin_button_editable_init))
293

294 295 296 297 298
#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)

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

307
  gobject_class->finalize = gtk_spin_button_finalize;
308 309
  gobject_class->set_property = gtk_spin_button_set_property;
  gobject_class->get_property = gtk_spin_button_get_property;
310

311
  widget_class->destroy = gtk_spin_button_destroy;
312 313 314 315
  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;
316
  widget_class->get_preferred_width = gtk_spin_button_get_preferred_width;
317
  widget_class->get_preferred_height = gtk_spin_button_get_preferred_height;
318
  widget_class->get_preferred_height_and_baseline_for_width = gtk_spin_button_get_preferred_height_and_baseline_for_width;
319
  widget_class->size_allocate = gtk_spin_button_size_allocate;
320
  widget_class->draw = gtk_spin_button_draw;
321 322 323 324 325 326 327 328
  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;
329
  widget_class->grab_notify = gtk_spin_button_grab_notify;
330
  widget_class->state_flags_changed = gtk_spin_button_state_flags_changed;
331
  widget_class->style_updated = gtk_spin_button_style_updated;
332

Owen Taylor's avatar
Owen Taylor committed
333
  entry_class->activate = gtk_spin_button_activate;
334
  entry_class->get_text_area_size = gtk_spin_button_get_text_area_size;
335
  entry_class->get_frame_size = gtk_spin_button_get_frame_size;
336 337 338

  class->input = NULL;
  class->output = NULL;
339
  class->change_value = gtk_spin_button_real_change_value;
340

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

349 350
  g_object_class_install_property (gobject_class,
                                   PROP_CLIMB_RATE,
351
                                   g_param_spec_double ("climb-rate",
352 353 354 355 356 357 358
                                                        P_("Climb Rate"),
                                                        P_("The acceleration rate when you hold down a button"),
                                                        0.0,
                                                        G_MAXDOUBLE,
                                                        0.0,
                                                        GTK_PARAM_READWRITE));

359 360 361
  g_object_class_install_property (gobject_class,
                                   PROP_DIGITS,
                                   g_param_spec_uint ("digits",
362 363 364 365 366 367 368
                                                      P_("Digits"),
                                                      P_("The number of decimal places to display"),
                                                      0,
                                                      MAX_DIGITS,
                                                      0,
                                                      GTK_PARAM_READWRITE));

369 370
  g_object_class_install_property (gobject_class,
                                   PROP_SNAP_TO_TICKS,
371
                                   g_param_spec_boolean ("snap-to-ticks",
372 373 374 375 376
                                                         P_("Snap to Ticks"),
                                                         P_("Whether erroneous values are automatically changed to a spin button's nearest step increment"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE));

377 378 379
  g_object_class_install_property (gobject_class,
                                   PROP_NUMERIC,
                                   g_param_spec_boolean ("numeric",
380 381 382 383 384
                                                         P_("Numeric"),
                                                         P_("Whether non-numeric characters should be ignored"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE));

385 386 387
  g_object_class_install_property (gobject_class,
                                   PROP_WRAP,
                                   g_param_spec_boolean ("wrap",
388 389 390 391 392
                                                         P_("Wrap"),
                                                         P_("Whether a spin button should wrap upon reaching its limits"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE));

393 394
  g_object_class_install_property (gobject_class,
                                   PROP_UPDATE_POLICY,
395
                                   g_param_spec_enum ("update-policy",
396 397 398 399 400 401
                                                      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));

402 403 404
  g_object_class_install_property (gobject_class,
                                   PROP_VALUE,
                                   g_param_spec_double ("value",
405 406 407 408 409 410 411
                                                        P_("Value"),
                                                        P_("Reads the current value, or sets a new value"),
                                                        -G_MAXDOUBLE,
                                                        G_MAXDOUBLE,
                                                        0.0,
                                                        GTK_PARAM_READWRITE));

412 413 414 415
  g_object_class_override_property (gobject_class,
                                    PROP_ORIENTATION,
                                    "orientation");

416
  gtk_widget_class_install_style_property_parser (widget_class,
417 418 419 420 421 422 423 424 425 426 427
                                                  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
428
   * @new_value: (out) (type double): return location for the new value
429 430 431 432 433 434 435 436 437 438 439
   *
   * 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.
   */
440
  spinbutton_signals[INPUT] =
441
    g_signal_new (I_("input"),
442 443 444 445 446 447 448
                  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);
449

450 451 452
  /**
   * GtkSpinButton::output:
   * @spin_button: the object which received the signal
453
   *
454 455
   * The ::output signal can be used to change to formatting
   * of the value that is displayed in the spin buttons entry.
456
   * |[<!-- language="C" -->
457
   * // show leading zeros
458 459 460 461
   * static gboolean
   * on_output (GtkSpinButton *spin,
   *            gpointer       data)
   * {
462
   *    GtkAdjustment *adjustment;
463
   *    gchar *text;
464
   *    int value;
465
   *
466 467
   *    adjustment = gtk_spin_button_get_adjustment (spin);
   *    value = (int)gtk_adjustment_get_value (adjustment);
468 469 470
   *    text = g_strdup_printf ("%02d", value);
   *    gtk_entry_set_text (GTK_ENTRY (spin), text);
   *    g_free (text);
471
   *
472 473 474 475
   *    return TRUE;
   * }
   * ]|
   *
476
   * Returns: %TRUE if the value has been displayed
477
   */
478
  spinbutton_signals[OUTPUT] =
479
    g_signal_new (I_("output"),
480 481 482 483 484 485
                  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);
486 487

  spinbutton_signals[VALUE_CHANGED] =
488
    g_signal_new (I_("value-changed"),
489 490 491 492 493 494
                  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);
495

496 497 498 499 500 501 502 503 504 505 506
  /**
   * 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"),
507 508 509 510 511 512
                  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);
513

514 515
  /* Action signals */
  spinbutton_signals[CHANGE_VALUE] =
516
    g_signal_new (I_("change-value"),
Manish Singh's avatar
Manish Singh committed
517
                  G_TYPE_FROM_CLASS (gobject_class),
518 519 520 521 522 523
                  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);
524

525
  binding_set = gtk_binding_set_by_class (class);
526

527 528 529 530 531 532 533 534
  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);
535

536
  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SPIN_BUTTON_ACCESSIBLE);
537 538
}

539
static void
540
gtk_spin_button_editable_init (GtkEditableInterface *iface)
541 542 543 544
{
  iface->insert_text = gtk_spin_button_insert_text;
}

545
static void
546
gtk_spin_button_set_property (GObject      *object,
547 548 549
                              guint         prop_id,
                              const GValue *value,
                              GParamSpec   *pspec)
550
{
551
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
552
  GtkSpinButtonPrivate *priv = spin_button->priv;
553

554
  switch (prop_id)
555 556 557
    {
      GtkAdjustment *adjustment;

558 559
    case PROP_ADJUSTMENT:
      adjustment = GTK_ADJUSTMENT (g_value_get_object (value));
560
      if (!adjustment)
561
        adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
562 563
      gtk_spin_button_set_adjustment (spin_button, adjustment);
      break;
564
    case PROP_CLIMB_RATE:
565
      gtk_spin_button_configure (spin_button,
566 567 568
                                 priv->adjustment,
                                 g_value_get_double (value),
                                 priv->digits);
569
      break;
570
    case PROP_DIGITS:
571
      gtk_spin_button_configure (spin_button,
572 573 574
                                 priv->adjustment,
                                 priv->climb_rate,
                                 g_value_get_uint (value));
575
      break;
576 577
    case PROP_SNAP_TO_TICKS:
      gtk_spin_button_set_snap_to_ticks (spin_button, g_value_get_boolean (value));
578
      break;
579 580
    case PROP_NUMERIC:
      gtk_spin_button_set_numeric (spin_button, g_value_get_boolean (value));
581
      break;
582 583
    case PROP_WRAP:
      gtk_spin_button_set_wrap (spin_button, g_value_get_boolean (value));
584
      break;
585 586
    case PROP_UPDATE_POLICY:
      gtk_spin_button_set_update_policy (spin_button, g_value_get_enum (value));
587
      break;
588 589
    case PROP_VALUE:
      gtk_spin_button_set_value (spin_button, g_value_get_double (value));
590
      break;
591 592 593
    case PROP_ORIENTATION:
      gtk_spin_button_set_orientation (spin_button, g_value_get_enum (value));
      break;
594
    default:
595
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
596 597 598 599 600
      break;
    }
}

static void
601
gtk_spin_button_get_property (GObject      *object,
602 603 604
                              guint         prop_id,
                              GValue       *value,
                              GParamSpec   *pspec)
605
{
606
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
607
  GtkSpinButtonPrivate *priv = spin_button->priv;
608

609
  switch (prop_id)
610
    {
611
    case PROP_ADJUSTMENT:
612
      g_value_set_object (value, priv->adjustment);
613
      break;
614
    case PROP_CLIMB_RATE:
615
      g_value_set_double (value, priv->climb_rate);
616
      break;
617
    case PROP_DIGITS:
618
      g_value_set_uint (value, priv->digits);
619
      break;
620
    case PROP_SNAP_TO_TICKS:
621
      g_value_set_boolean (value, priv->snap_to_ticks);
622
      break;
623
    case PROP_NUMERIC:
624
      g_value_set_boolean (value, priv->numeric);
625
      break;
626
    case PROP_WRAP:
627
      g_value_set_boolean (value, priv->wrap);
628
      break;
629
    case PROP_UPDATE_POLICY:
630
      g_value_set_enum (value, priv->update_policy);
631
      break;
632
     case PROP_VALUE:
633
       g_value_set_double (value, gtk_adjustment_get_value (priv->adjustment));
634
      break;
635 636 637
    case PROP_ORIENTATION:
      g_value_set_enum (value, priv->orientation);
      break;
638
    default:
639
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
640 641 642 643
      break;
    }
}

644 645 646
static void
gtk_spin_button_init (GtkSpinButton *spin_button)
{
647
  GtkSpinButtonPrivate *priv;
648
  GtkStyleContext *context;
649

650
  spin_button->priv = gtk_spin_button_get_instance_private (spin_button);
651 652 653
  priv = spin_button->priv;

  priv->adjustment = NULL;
654 655
  priv->down_panel = NULL;
  priv->up_panel = NULL;
656 657 658 659
  priv->timer = 0;
  priv->climb_rate = 0.0;
  priv->timer_step = 0.0;
  priv->update_policy = GTK_UPDATE_ALWAYS;
660 661
  priv->in_child = NULL;
  priv->click_child = NULL;
662 663 664 665 666 667 668
  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;
669

670 671
  priv->orientation = GTK_ORIENTATION_HORIZONTAL;

672
  gtk_spin_button_set_adjustment (spin_button,
Javier Jardón's avatar
Javier Jardón committed
673
                                  gtk_adjustment_new (0, 0, 0, 0, 0, 0));
674 675 676

  context = gtk_widget_get_style_context (GTK_WIDGET (spin_button));
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_SPINBUTTON);
677 678

  gtk_widget_add_events (GTK_WIDGET (spin_button), GDK_SCROLL_MASK);
679 680 681
}

static void
682
gtk_spin_button_finalize (GObject *object)
683
{
684 685 686 687 688 689 690 691 692 693
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
  GtkSpinButtonPrivate *priv = spin_button->priv;

  gtk_spin_button_set_adjustment (spin_button, NULL);

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

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

Matthias Clasen's avatar
Matthias Clasen committed
695
  G_OBJECT_CLASS (gtk_spin_button_parent_class)->finalize (object);
696 697
}

698
static void
699
gtk_spin_button_destroy (GtkWidget *widget)
700
{
701
  gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));
702

703
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->destroy (widget);
704 705
}

706 707 708
static void
gtk_spin_button_map (GtkWidget *widget)
{
709
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
710
  GtkSpinButtonPrivate *priv = spin_button->priv;
711

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

static void
gtk_spin_button_unmap (GtkWidget *widget)
{
723
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
724
  GtkSpinButtonPrivate *priv = spin_button->priv;
725

726
  if (gtk_widget_get_mapped (widget))
727
    {
728 729
      gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));

730 731
      gdk_window_hide (priv->down_panel);
      gdk_window_hide (priv->up_panel);
Matthias Clasen's avatar
Matthias Clasen committed
732
      GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unmap (widget);
733 734 735 736
    }
}

static void
737 738
gtk_spin_button_panel_nthchildize_context (GtkSpinButton *spin_button,
                                           GtkStyleContext *context,
739
                                           gboolean         is_down_panel)
740
{
741
  GtkSpinButtonPrivate *priv = spin_button->priv;
742 743 744 745 746 747 748 749 750
  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
   */
751
  path = _gtk_widget_create_path (widget);
752 753
  siblings_path = gtk_widget_path_new ();

754
  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
755
    {
756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
      direction = gtk_widget_get_direction (widget);

      /* flip children order for RTL */
      if (direction == GTK_TEXT_DIR_RTL)
        {
          up_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
          down_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
          gtk_widget_path_append_type (siblings_path, GTK_TYPE_ENTRY);
        }
      else
        {
          gtk_widget_path_append_type (siblings_path, GTK_TYPE_ENTRY);
          down_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
          up_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
        }
771 772 773 774
    }
  else
    {
      up_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
775 776
      gtk_widget_path_append_type (siblings_path, GTK_TYPE_ENTRY);
      down_pos = gtk_widget_path_append_type (siblings_path, GTK_TYPE_SPIN_BUTTON);
777 778 779 780 781 782 783 784 785
    }

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

786
  if (is_down_panel)
787
    gtk_widget_path_append_with_siblings (path, siblings_path, down_pos);
788
  else
789
    gtk_widget_path_append_with_siblings (path, siblings_path, up_pos);
790 791 792 793 794 795 796 797 798

  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)
799
{
800
  GtkSpinButtonPrivate *priv = spin_button->priv;
801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828
  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;

829 830 831 832 833 834
  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) ||
835
      !gtk_editable_get_editable (GTK_EDITABLE (spin_button)))
836 837 838
    {
      state |= GTK_STATE_FLAG_INSENSITIVE;
    }
839 840 841
  else
    {
      if (priv->click_child == panel)
Cosimo Cecchi's avatar
Cosimo Cecchi committed
842
        state |= GTK_STATE_FLAG_ACTIVE;
843 844 845
      else if (priv->in_child == panel &&
               priv->click_child == NULL)
        state |= GTK_STATE_FLAG_PRELIGHT;
846 847 848 849 850 851 852 853 854
    }

  return state;
}

static GtkStyleContext *
gtk_spin_button_panel_get_context (GtkSpinButton *spin_button,
                                   GdkWindow *panel)
{
855 856
  GtkSpinButtonPrivate *priv = spin_button->priv;
  GtkStyleContext **contextp;
857

858 859 860 861 862 863 864 865 866
  contextp = (panel == priv->down_panel) ?
    &priv->down_panel_context : &priv->up_panel_context;

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

868
  return *contextp;
869 870
}

871 872 873 874 875
static void
gtk_spin_button_panel_get_size (GtkSpinButton *spin_button,
                                GdkWindow *panel,
                                gint *width,
                                gint *height)
876 877
{
  GtkBorder button_padding, button_border;
878 879
  GtkStyleContext *context;
  GtkStateFlags state;
880
  gint icon_size, w, h;
881

882 883
  gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
  icon_size = MAX (w, h);
884 885 886 887 888 889 890

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

891 892 893 894 895 896 897
  if (width)
    *width = icon_size + button_padding.left + button_padding.right +
             button_border.left + button_border.right;

  if (height)
    *height = icon_size + button_padding.top + button_padding.bottom +
              button_border.top + button_border.bottom;
898 899 900 901 902 903 904 905 906 907
}

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;
908
  GtkRequisition requisition;
909
  gint req_height;
910 911
  gint down_panel_width, down_panel_height;
  gint up_panel_width, up_panel_height;
912
  GtkStyleContext *context;
913
  GtkBorder border;
914

915
  gtk_widget_get_allocation (widget, &spin_allocation);
916
  gtk_widget_get_preferred_size (widget, &requisition, NULL);
917 918

  context = gtk_widget_get_style_context (GTK_WIDGET (spin_button));
919
  gtk_style_context_get_border (context, GTK_STATE_FLAG_NORMAL, &border);
920

921 922
  gtk_spin_button_panel_get_size (spin_button, priv->down_panel, &down_panel_width, &down_panel_height);
  gtk_spin_button_panel_get_size (spin_button, priv->up_panel, &up_panel_width, &up_panel_height);
923

924 925 926
  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
    {
      req_height = requisition.height - gtk_widget_get_margin_top (widget) - gtk_widget_get_margin_bottom (widget);
927

928 929 930 931 932 933
      /* both panels have the same size, and they're as tall as the entry allocation,
       * excluding margins
       */
      allocation.height = MIN (req_height, spin_allocation.height) - border.top - border.bottom;
      allocation.y = spin_allocation.y + border.top + (spin_allocation.height - req_height) / 2;
      down_allocation = up_allocation = allocation;
934

935 936 937 938 939 940 941 942 943 944 945 946 947 948
      down_allocation.width = down_panel_width;
      up_allocation.width = up_panel_width;

      /* invert x axis allocation for RTL */
      if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
        {
          up_allocation.x = spin_allocation.x + border.left;
          down_allocation.x = up_allocation.x + up_panel_width;
        }
      else
        {
          up_allocation.x = spin_allocation.x + spin_allocation.width - up_panel_width - border.right;
          down_allocation.x = up_allocation.x - down_panel_width;
        }
949 950 951
    }
  else
    {
952 953 954 955 956 957 958 959 960 961
      /* both panels have the same size, and they're as wide as the entry allocation */
      allocation.width = spin_allocation.width;
      allocation.x = spin_allocation.x;
      down_allocation = up_allocation = allocation;

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

      up_allocation.y = spin_allocation.y;
      down_allocation.y = spin_allocation.y + spin_allocation.height - down_allocation.height;
962 963 964 965 966 967 968 969 970 971 972 973 974
    }

  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)
{
975
  GtkSpinButtonPrivate *priv = spin_button->priv;
976 977 978 979
  GtkStyleContext *context;
  GtkStateFlags state;
  GtkWidget *widget;
  gdouble width, height, x, y;
980 981
  gint icon_width, icon_height;
  GtkIconHelper *icon_helper;
982 983 984 985 986 987 988 989 990 991 992 993 994

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

995
  icon_helper = _gtk_icon_helper_new ();
996
  _gtk_icon_helper_set_window (icon_helper, panel);
997 998
  _gtk_icon_helper_set_use_fallback (icon_helper, TRUE);

999
  if (panel == priv->down_panel)
1000
    _gtk_icon_helper_set_icon_name (icon_helper, "list-remove-symbolic", GTK_ICON_SIZE_MENU);
1001
  else
1002 1003 1004 1005
    _gtk_icon_helper_set_icon_name (icon_helper, "list-add-symbolic", GTK_ICON_SIZE_MENU);

  _gtk_icon_helper_get_size (icon_helper, context,
                             &icon_width, &icon_height);
1006 1007 1008 1009 1010 1011

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

1012 1013
  x = floor ((width - icon_width) / 2.0);
  y = floor ((height - icon_height) / 2.0);
1014

1015 1016
  _gtk_icon_helper_draw (icon_helper, context, cr,
                         x, y);
1017 1018
  cairo_restore (cr);

1019
  g_object_unref (icon_helper);
1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
}

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

1032
  gtk_widget_set_events (widget, gtk_widget_get_events (widget) |
1033
                         GDK_KEY_RELEASE_MASK);
Matthias Clasen's avatar
Matthias Clasen committed
1034
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->realize (widget);
1035

1036
  attributes.window_type = GDK_WINDOW_CHILD;
1037
  attributes.wclass = GDK_INPUT_ONLY;
1038 1039
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.event_mask = gtk_widget_get_events (widget);
1040 1041
  attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK
    | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK
1042 1043
    | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;

1044
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
1045

1046 1047 1048 1049 1050 1051 1052
  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;
1053

1054 1055
  priv->down_panel = gdk_window_new (gtk_widget_get_window (widget),
                                     &attributes, attributes_mask);
1056
  gtk_widget_register_window (widget, priv->down_panel);
1057

1058 1059 1060 1061 1062 1063 1064 1065
  /* 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);
1066
  gtk_widget_register_window (widget, priv->up_panel);
1067

1068
  return_val = FALSE;
Manish Singh's avatar
Manish Singh committed
1069
  g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
1070 1071 1072 1073 1074

  /* If output wasn't processed explicitly by the method connected to the
   * 'output' signal; and if we don't have any explicit 'text' set initially,
   * fallback to the default output. */
  if (!return_val &&
Matthias Clasen's avatar
Matthias Clasen committed
1075
      (spin_button->priv->numeric || gtk_entry_get_text (GTK_ENTRY (spin_button)) == NULL))
1076
    gtk_spin_button_default_output (spin_button);
1077 1078

  gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1079 1080 1081 1082 1083
}

static void
gtk_spin_button_unrealize (GtkWidget *widget)
{
1084
  GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1085
  GtkSpinButtonPrivate *priv = spin->priv;
1086

Matthias Clasen's avatar
Matthias Clasen committed
1087
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unrealize (widget);
1088

1089
  if (priv->down_panel)
1090
    {
1091
      gtk_widget_unregister_window (widget, priv->down_panel);
1092 1093 1094 1095 1096 1097
      gdk_window_destroy (priv->down_panel);
      priv->down_panel = NULL;
    }

  if (priv->up_panel)
    {
1098
      gtk_widget_unregister_window (widget, priv->up_panel);
1099 1100
      gdk_window_destroy (priv->up_panel);
      priv->up_panel = NULL;
1101 1102 1103
    }
}

1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128
static void
gtk_spin_button_set_orientation (GtkSpinButton *spin,
                                 GtkOrientation orientation)
{
  GtkEntry *entry = GTK_ENTRY (spin);
  GtkSpinButtonPrivate *priv = spin->priv;

  if (priv->orientation == orientation)
    return;

  priv->orientation = orientation;
  _gtk_orientable_set_style_classes (GTK_ORIENTABLE (spin));

  /* change alignment if it's the default */
  if (priv->orientation == GTK_ORIENTATION_VERTICAL &&
      gtk_entry_get_alignment (entry) == 0.0)
    gtk_entry_set_alignment (entry, 0.5);
  else if (priv->orientation == GTK_ORIENTATION_HORIZONTAL &&
           gtk_entry_get_alignment (entry) == 0.5)
    gtk_entry_set_alignment (entry, 0.0);

  g_object_notify (G_OBJECT (spin), "orientation");
  gtk_widget_queue_resize (GTK_WIDGET (spin));
}

1129 1130 1131
static gint
measure_string_width (PangoLayout *layout,
                      const gchar *string)
1132
{
1133
  gint width;
1134

1135 1136
  pango_layout_set_text (layout, string, -1);
  pango_layout_get_pixel_size (layout, &width, NULL);
1137

1138 1139
  return width;
}
1140

1141 1142 1143 1144 1145 1146
static gchar *
gtk_spin_button_format_for_value (GtkSpinButton *spin_button,
                                  gdouble        value)
{
  GtkSpinButtonPrivate *priv = spin_button->priv;
  gchar *buf = g_strdup_printf ("%0.*f", priv->digits, value);
1147

1148
  return buf;
1149 1150
}

1151
static void
1152 1153 1154
gtk_spin_button_get_preferred_width (GtkWidget *widget,
                                     gint      *minimum,
                                     gint      *natural)
1155
{
1156
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
1157
  GtkSpinButtonPrivate *priv = spin_button->priv;
1158
  GtkEntry *entry = GTK_ENTRY (widget);
1159
  GtkStyleContext *style_context;
1160

1161
  style_context = gtk_widget_get_style_context (widget);
1162

1163
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->get_preferred_width (widget, minimum, natural);
1164

1165
  if (gtk_entry_get_width_chars (entry) < 0)
1166
    {
1167
      gint width, w;
1168 1169
      gboolean interior_focus;
      gint focus_width;
1170
      GtkBorder borders;
1171 1172
      PangoLayout *layout;
      gchar *str;
1173

1174 1175 1176 1177
      gtk_style_context_get_style (style_context,
                                   "interior-focus", &interior_focus,
                                   "focus-line-width", &focus_width,
                                   NULL);
1178

1179
      layout = pango_layout_copy (gtk_entry_get_layout (entry));
1180

1181 1182
      /* Get max of MIN_SPIN_BUTTON_WIDTH, size of upper, size of lower */
      width = MIN_SPIN_BUTTON_WIDTH;
1183

1184 1185 1186
      str = gtk_spin_button_format_for_value (spin_button,
                                              gtk_adjustment_get_upper (priv->adjustment));
      w = measure_string_width (layout, str);
1187
      width = MAX (width, w);
1188 1189 1190 1191 1192
      g_free (str);

      str = gtk_spin_button_format_for_value (spin_button,
                                              gtk_adjustment_get_lower (priv->adjustment));
      w = measure_string_width (layout, str);
1193
      width = MAX (width, w);
1194
      g_free (str);
1195

1196
      _gtk_entry_get_borders (entry, &borders);
1197
      width += borders.left + borders.right;
1198 1199 1200

      *minimum = width;
      *natural = width;
1201 1202

      g_object_unref (layout);
1203
    }
1204

1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218
  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
    {
      gint down_panel_width;
      gint up_panel_width;

      gtk_spin_button_panel_get_size (spin_button, priv->down_panel, &down_panel_width, NULL);
      gtk_spin_button_panel_get_size (spin_button, priv->up_panel, &up_panel_width, NULL);

      *minimum += up_panel_width + down_panel_width;
      *natural += up_panel_width + down_panel_width;
    }
}

static void
1219 1220 1221 1222 1223 1224
gtk_spin_button_get_preferred_height_and_baseline_for_width (GtkWidget *widget,
							     gint       width,
							     gint      *minimum,
							     gint      *natural,
							     gint      *minimum_baseline,
							     gint      *natural_baseline)
1225 1226 1227 1228
{
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
  GtkSpinButtonPrivate *priv = spin_button->priv;

1229 1230 1231 1232
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->get_preferred_height_and_baseline_for_width (widget, width,
												minimum, natural,
												minimum_baseline,
												natural_baseline);
1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243

  if (priv->orientation == GTK_ORIENTATION_VERTICAL)
    {
      gint down_panel_height;
      gint up_panel_height;

      gtk_spin_button_panel_get_size (spin_button, priv->down_panel, NULL, &down_panel_height);
      gtk_spin_button_panel_get_size (spin_button, priv->up_panel, NULL, &up_panel_height);

      *minimum += up_panel_height + down_panel_height;
      *natural += up_panel_height + down_panel_height;
1244 1245 1246 1247 1248

      if (minimum_baseline && *minimum_baseline != -1)
	*minimum_baseline += up_panel_height;
      if (natural_baseline && *natural_baseline != -1)
	*natural_baseline += up_panel_height;
1249
    }
1250 1251
}

1252 1253