gtkspinbutton.c 67.9 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 27 28 29
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
 */

30
#include "config.h"
31 32 33
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
34 35
#include <string.h>
#include <locale.h>
36
#include "gdk/gdkkeysyms.h"
37
#include "gtkbindings.h"
38
#include "gtkspinbutton.h"
39
#include "gtkentryprivate.h"
40
#include "gtkmain.h"
41
#include "gtkmarshalers.h"
42
#include "gtksettings.h"
43
#include "gtkprivate.h"
44
#include "gtkintl.h"
45

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

52

53
struct _GtkSpinButtonPrivate
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
{
  GtkSpinButtonUpdatePolicy update_policy;
  GtkAdjustment *adjustment;

  GdkWindow     *panel;

  guint32        timer;

  gdouble        climb_rate;
  gdouble        timer_step;

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

76
enum {
77 78 79 80 81 82 83 84 85
  PROP_0,
  PROP_ADJUSTMENT,
  PROP_CLIMB_RATE,
  PROP_DIGITS,
  PROP_SNAP_TO_TICKS,
  PROP_NUMERIC,
  PROP_WRAP,
  PROP_UPDATE_POLICY,
  PROP_VALUE
86 87
};

88 89 90 91 92
/* Signals */
enum
{
  INPUT,
  OUTPUT,
93
  VALUE_CHANGED,
94
  CHANGE_VALUE,
95
  WRAPPED,
96 97
  LAST_SIGNAL
};
98

99
static void gtk_spin_button_editable_init  (GtkEditableInterface *iface);
100
static void gtk_spin_button_finalize       (GObject            *object);
101 102 103 104 105 106 107 108
static void gtk_spin_button_set_property   (GObject         *object,
					    guint            prop_id,
					    const GValue    *value,
					    GParamSpec      *pspec);
static void gtk_spin_button_get_property   (GObject         *object,
					    guint            prop_id,
					    GValue          *value,
					    GParamSpec      *pspec);
109
static void gtk_spin_button_destroy        (GtkWidget          *widget);
110 111 112 113
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);
114 115 116 117
static void gtk_spin_button_get_preferred_width  (GtkWidget          *widget,
                                                  gint               *minimum,
                                                  gint               *natural);

118 119
static void gtk_spin_button_size_allocate  (GtkWidget          *widget,
					    GtkAllocation      *allocation);
Benjamin Otte's avatar
Benjamin Otte committed
120 121
static gint gtk_spin_button_draw           (GtkWidget          *widget,
                                            cairo_t            *cr);
122 123 124 125 126 127 128 129 130 131 132 133
static gint gtk_spin_button_button_press   (GtkWidget          *widget,
					    GdkEventButton     *event);
static gint gtk_spin_button_button_release (GtkWidget          *widget,
					    GdkEventButton     *event);
static gint gtk_spin_button_motion_notify  (GtkWidget          *widget,
					    GdkEventMotion     *event);
static gint gtk_spin_button_enter_notify   (GtkWidget          *widget,
					    GdkEventCrossing   *event);
static gint gtk_spin_button_leave_notify   (GtkWidget          *widget,
					    GdkEventCrossing   *event);
static gint gtk_spin_button_focus_out      (GtkWidget          *widget,
					    GdkEventFocus      *event);
134 135
static void gtk_spin_button_grab_notify    (GtkWidget          *widget,
					    gboolean            was_grabbed);
136 137
static void gtk_spin_button_state_changed  (GtkWidget          *widget,
					    GtkStateType        previous_state);
138 139
static void gtk_spin_button_style_set      (GtkWidget          *widget,
                                            GtkStyle           *previous_style);
140
static void gtk_spin_button_draw_arrow     (GtkSpinButton      *spin_button,
Benjamin Otte's avatar
Benjamin Otte committed
141
					    cairo_t            *cr,
142
					    GtkArrowType        arrow_type);
143
static gboolean gtk_spin_button_timer          (GtkSpinButton      *spin_button);
144
static void gtk_spin_button_stop_spinning  (GtkSpinButton      *spin);
145
static void gtk_spin_button_value_changed  (GtkAdjustment      *adjustment,
146
					    GtkSpinButton      *spin_button); 
147 148
static gint gtk_spin_button_key_release    (GtkWidget          *widget,
					    GdkEventKey        *event);
149 150
static gint gtk_spin_button_scroll         (GtkWidget          *widget,
					    GdkEventScroll     *event);
Owen Taylor's avatar
Owen Taylor committed
151
static void gtk_spin_button_activate       (GtkEntry           *entry);
152 153 154 155 156
static void gtk_spin_button_get_text_area_size (GtkEntry *entry,
						gint     *x,
						gint     *y,
						gint     *width,
						gint     *height);
157
static void gtk_spin_button_snap           (GtkSpinButton      *spin_button,
158
					    gdouble             val);
159
static void gtk_spin_button_insert_text    (GtkEditable        *editable,
160
					    const gchar        *new_text,
161
					    gint                new_text_length,
162
					    gint               *position);
163
static void gtk_spin_button_real_spin      (GtkSpinButton      *spin_button,
164
					    gdouble             step);
165 166 167
static void gtk_spin_button_real_change_value (GtkSpinButton   *spin,
					       GtkScrollType    scroll);

168
static gint gtk_spin_button_default_input  (GtkSpinButton      *spin_button,
169
					    gdouble            *new_val);
170
static gint gtk_spin_button_default_output (GtkSpinButton      *spin_button);
171

172
static gint spin_button_get_arrow_size     (GtkSpinButton      *spin_button);
173
static gint spin_button_get_shadow_type    (GtkSpinButton      *spin_button);
174

175
static guint spinbutton_signals[LAST_SIGNAL] = {0};
176

177
#define NO_ARROW 2
178

Matthias Clasen's avatar
Matthias Clasen committed
179 180
G_DEFINE_TYPE_WITH_CODE (GtkSpinButton, gtk_spin_button, GTK_TYPE_ENTRY,
			 G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
Matthias Clasen's avatar
Matthias Clasen committed
181
						gtk_spin_button_editable_init))
182

183 184 185 186 187
#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)

188 189 190
static void
gtk_spin_button_class_init (GtkSpinButtonClass *class)
{
191
  GObjectClass     *gobject_class = G_OBJECT_CLASS (class);
192 193
  GtkWidgetClass   *widget_class = GTK_WIDGET_CLASS (class);
  GtkEntryClass    *entry_class = GTK_ENTRY_CLASS (class);
194
  GtkBindingSet    *binding_set;
195

196
  gobject_class->finalize = gtk_spin_button_finalize;
197 198
  gobject_class->set_property = gtk_spin_button_set_property;
  gobject_class->get_property = gtk_spin_button_get_property;
199

200
  widget_class->destroy = gtk_spin_button_destroy;
201 202 203 204
  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;
205
  widget_class->get_preferred_width = gtk_spin_button_get_preferred_width;
206
  widget_class->size_allocate = gtk_spin_button_size_allocate;
Benjamin Otte's avatar
Benjamin Otte committed
207
  widget_class->draw = gtk_spin_button_draw;
208 209 210 211 212 213 214 215
  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;
216
  widget_class->grab_notify = gtk_spin_button_grab_notify;
217
  widget_class->state_changed = gtk_spin_button_state_changed;
218
  widget_class->style_set = gtk_spin_button_style_set;
219

Owen Taylor's avatar
Owen Taylor committed
220
  entry_class->activate = gtk_spin_button_activate;
221
  entry_class->get_text_area_size = gtk_spin_button_get_text_area_size;
222 223 224

  class->input = NULL;
  class->output = NULL;
225
  class->change_value = gtk_spin_button_real_change_value;
226

227 228 229
  g_object_class_install_property (gobject_class,
                                   PROP_ADJUSTMENT,
                                   g_param_spec_object ("adjustment",
230
                                                        P_("Adjustment"),
231
                                                        P_("The adjustment that holds the value of the spin button"),
232
                                                        GTK_TYPE_ADJUSTMENT,
233
                                                        GTK_PARAM_READWRITE));
234 235 236
  
  g_object_class_install_property (gobject_class,
                                   PROP_CLIMB_RATE,
237
                                   g_param_spec_double ("climb-rate",
238 239
							P_("Climb Rate"),
							P_("The acceleration rate when you hold down a button"),
240 241 242
							0.0,
							G_MAXDOUBLE,
							0.0,
243
							GTK_PARAM_READWRITE));  
244 245 246 247
  
  g_object_class_install_property (gobject_class,
                                   PROP_DIGITS,
                                   g_param_spec_uint ("digits",
248 249
						      P_("Digits"),
						      P_("The number of decimal places to display"),
250
						      0,
251
						      MAX_DIGITS,
252
						      0,
253
						      GTK_PARAM_READWRITE));
254 255 256
  
  g_object_class_install_property (gobject_class,
                                   PROP_SNAP_TO_TICKS,
257
                                   g_param_spec_boolean ("snap-to-ticks",
258 259
							 P_("Snap to Ticks"),
							 P_("Whether erroneous values are automatically changed to a spin button's nearest step increment"),
260
							 FALSE,
261
							 GTK_PARAM_READWRITE));
262 263 264 265
  
  g_object_class_install_property (gobject_class,
                                   PROP_NUMERIC,
                                   g_param_spec_boolean ("numeric",
266 267
							 P_("Numeric"),
							 P_("Whether non-numeric characters should be ignored"),
268
							 FALSE,
269
							 GTK_PARAM_READWRITE));
270 271 272 273
  
  g_object_class_install_property (gobject_class,
                                   PROP_WRAP,
                                   g_param_spec_boolean ("wrap",
274 275
							 P_("Wrap"),
							 P_("Whether a spin button should wrap upon reaching its limits"),
276
							 FALSE,
277
							 GTK_PARAM_READWRITE));
278 279 280
  
  g_object_class_install_property (gobject_class,
                                   PROP_UPDATE_POLICY,
281
                                   g_param_spec_enum ("update-policy",
282 283
						      P_("Update Policy"),
						      P_("Whether the spin button should update always, or only when the value is legal"),
284 285
						      GTK_TYPE_SPIN_BUTTON_UPDATE_POLICY,
						      GTK_UPDATE_ALWAYS,
286
						      GTK_PARAM_READWRITE));
287 288 289 290
  
  g_object_class_install_property (gobject_class,
                                   PROP_VALUE,
                                   g_param_spec_double ("value",
291 292
							P_("Value"),
							P_("Reads the current value, or sets a new value"),
293 294 295
							-G_MAXDOUBLE,
							G_MAXDOUBLE,
							0.0,
296
							GTK_PARAM_READWRITE));  
297 298
  
  gtk_widget_class_install_style_property_parser (widget_class,
299
						  g_param_spec_enum ("shadow-type", 
Matthias Clasen's avatar
Matthias Clasen committed
300
								     "Shadow Type", 
301
								     P_("Style of bevel around the spin button"),
302
								     GTK_TYPE_SHADOW_TYPE,
303
								     GTK_SHADOW_IN,
304
								     GTK_PARAM_READABLE),
305
						  gtk_rc_property_parse_enum);
306
  spinbutton_signals[INPUT] =
Matthias Clasen's avatar
Matthias Clasen committed
307
    g_signal_new (I_("input"),
Manish Singh's avatar
Manish Singh committed
308 309 310 311 312 313 314
		  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);
315

316 317 318 319 320 321 322 323 324 325 326 327 328 329
  /**
   * GtkSpinButton::output:
   * @spin_button: the object which received the signal
   * 
   * The ::output signal can be used to change to formatting
   * of the value that is displayed in the spin buttons entry.
   * |[
   * /&ast; show leading zeros &ast;/
   * static gboolean
   * on_output (GtkSpinButton *spin,
   *            gpointer       data)
   * {
   *    GtkAdjustment *adj;
   *    gchar *text;
330
   *    int value;
331 332 333 334 335 336 337 338 339 340 341 342 343
   *    
   *    adj = gtk_spin_button_get_adjustment (spin);
   *    value = (int)gtk_adjustment_get_value (adj);
   *    text = g_strdup_printf ("%02d", value);
   *    gtk_entry_set_text (GTK_ENTRY (spin), text);
   *    g_free (text);
   *    
   *    return TRUE;
   * }
   * ]|
   *
   * Returns: %TRUE if the value has been displayed.
   */
344
  spinbutton_signals[OUTPUT] =
Matthias Clasen's avatar
Matthias Clasen committed
345
    g_signal_new (I_("output"),
Manish Singh's avatar
Manish Singh committed
346 347 348 349 350 351
		  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);
352 353

  spinbutton_signals[VALUE_CHANGED] =
354
    g_signal_new (I_("value-changed"),
Manish Singh's avatar
Manish Singh committed
355 356 357 358 359 360
		  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);
361

362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
  /**
   * 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"),
		  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);

380 381
  /* Action signals */
  spinbutton_signals[CHANGE_VALUE] =
382
    g_signal_new (I_("change-value"),
Manish Singh's avatar
Manish Singh committed
383
                  G_TYPE_FROM_CLASS (gobject_class),
384 385 386 387 388 389 390 391 392
                  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);
  
  binding_set = gtk_binding_set_by_class (class);
  
393 394 395 396 397 398 399 400
  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);
401

402
  g_type_class_add_private (class, sizeof (GtkSpinButtonPrivate));
403 404
}

405
static void
406
gtk_spin_button_editable_init (GtkEditableInterface *iface)
407 408 409 410
{
  iface->insert_text = gtk_spin_button_insert_text;
}

411
static void
412 413 414 415
gtk_spin_button_set_property (GObject      *object,
			      guint         prop_id,
			      const GValue *value,
			      GParamSpec   *pspec)
416
{
417
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
418
  GtkSpinButtonPrivate *priv = spin_button->priv;
419

420
  switch (prop_id)
421 422 423
    {
      GtkAdjustment *adjustment;

424 425
    case PROP_ADJUSTMENT:
      adjustment = GTK_ADJUSTMENT (g_value_get_object (value));
426
      if (!adjustment)
Javier Jardón's avatar
Javier Jardón committed
427
	adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
428 429
      gtk_spin_button_set_adjustment (spin_button, adjustment);
      break;
430
    case PROP_CLIMB_RATE:
431
      gtk_spin_button_configure (spin_button,
432
				 priv->adjustment,
433
				 g_value_get_double (value),
434
				 priv->digits);
435
      break;
436
    case PROP_DIGITS:
437
      gtk_spin_button_configure (spin_button,
438 439
				 priv->adjustment,
				 priv->climb_rate,
440
				 g_value_get_uint (value));
441
      break;
442 443
    case PROP_SNAP_TO_TICKS:
      gtk_spin_button_set_snap_to_ticks (spin_button, g_value_get_boolean (value));
444
      break;
445 446
    case PROP_NUMERIC:
      gtk_spin_button_set_numeric (spin_button, g_value_get_boolean (value));
447
      break;
448 449
    case PROP_WRAP:
      gtk_spin_button_set_wrap (spin_button, g_value_get_boolean (value));
450
      break;
451 452
    case PROP_UPDATE_POLICY:
      gtk_spin_button_set_update_policy (spin_button, g_value_get_enum (value));
453
      break;
454 455
    case PROP_VALUE:
      gtk_spin_button_set_value (spin_button, g_value_get_double (value));
456 457
      break;
    default:
458
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
459 460 461 462 463
      break;
    }
}

static void
464 465 466 467
gtk_spin_button_get_property (GObject      *object,
			      guint         prop_id,
			      GValue       *value,
			      GParamSpec   *pspec)
468
{
469
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
470
  GtkSpinButtonPrivate *priv = spin_button->priv;
471

472
  switch (prop_id)
473
    {
474
    case PROP_ADJUSTMENT:
475
      g_value_set_object (value, priv->adjustment);
476
      break;
477
    case PROP_CLIMB_RATE:
478
      g_value_set_double (value, priv->climb_rate);
479
      break;
480
    case PROP_DIGITS:
481
      g_value_set_uint (value, priv->digits);
482
      break;
483
    case PROP_SNAP_TO_TICKS:
484
      g_value_set_boolean (value, priv->snap_to_ticks);
485
      break;
486
    case PROP_NUMERIC:
487
      g_value_set_boolean (value, priv->numeric);
488
      break;
489
    case PROP_WRAP:
490
      g_value_set_boolean (value, priv->wrap);
491
      break;
492
    case PROP_UPDATE_POLICY:
493
      g_value_set_enum (value, priv->update_policy);
494
      break;
495
     case PROP_VALUE:
496
       g_value_set_double (value, priv->adjustment->value);
497 498
      break;
    default:
499
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
500 501 502 503
      break;
    }
}

504 505 506
static void
gtk_spin_button_init (GtkSpinButton *spin_button)
{
507
  GtkSpinButtonPrivate *priv;
508 509 510

  spin_button->priv = G_TYPE_INSTANCE_GET_PRIVATE (spin_button,
                                                   GTK_TYPE_SPIN_BUTTON,
511
                                                   GtkSpinButtonPrivate);
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
  priv = spin_button->priv;

  priv->adjustment = NULL;
  priv->panel = NULL;
  priv->timer = 0;
  priv->climb_rate = 0.0;
  priv->timer_step = 0.0;
  priv->update_policy = GTK_UPDATE_ALWAYS;
  priv->in_child = NO_ARROW;
  priv->click_child = NO_ARROW;
  priv->button = 0;
  priv->need_timer = FALSE;
  priv->timer_calls = 0;
  priv->digits = 0;
  priv->numeric = FALSE;
  priv->wrap = FALSE;
  priv->snap_to_ticks = FALSE;
529

530
  gtk_spin_button_set_adjustment (spin_button,
Javier Jardón's avatar
Javier Jardón committed
531
                                  gtk_adjustment_new (0, 0, 0, 0, 0, 0));
532 533 534
}

static void
535
gtk_spin_button_finalize (GObject *object)
536
{
537
  gtk_spin_button_set_adjustment (GTK_SPIN_BUTTON (object), NULL);
538
  
Matthias Clasen's avatar
Matthias Clasen committed
539
  G_OBJECT_CLASS (gtk_spin_button_parent_class)->finalize (object);
540 541
}

542
static void
543
gtk_spin_button_destroy (GtkWidget *widget)
544
{
545
  gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));
546

547
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->destroy (widget);
548 549
}

550 551 552
static void
gtk_spin_button_map (GtkWidget *widget)
{
553
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
554
  GtkSpinButtonPrivate *priv = spin_button->priv;
555

556
  if (gtk_widget_get_realized (widget) && !gtk_widget_get_mapped (widget))
557
    {
Matthias Clasen's avatar
Matthias Clasen committed
558
      GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->map (widget);
559
      gdk_window_show (priv->panel);
560 561 562 563 564 565
    }
}

static void
gtk_spin_button_unmap (GtkWidget *widget)
{
566
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
567
  GtkSpinButtonPrivate *priv = spin_button->priv;
568

569
  if (gtk_widget_get_mapped (widget))
570
    {
571 572
      gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));

573
      gdk_window_hide (priv->panel);
Matthias Clasen's avatar
Matthias Clasen committed
574
      GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unmap (widget);
575 576 577 578 579 580
    }
}

static void
gtk_spin_button_realize (GtkWidget *widget)
{
581
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
582
  GtkSpinButtonPrivate *priv = spin_button->priv;
583
  GtkAllocation allocation;
584
  GtkRequisition requisition;
585 586
  GtkStyle *style;
  GdkWindowAttr attributes;
587
  gint attributes_mask;
Manish Singh's avatar
Manish Singh committed
588
  gboolean return_val;
589
  gint arrow_size;
590

591 592
  style = gtk_widget_get_style (widget);

593
  arrow_size = spin_button_get_arrow_size (spin_button);
594

595
  gtk_widget_get_preferred_size (widget, &requisition, NULL);
596
  gtk_widget_get_allocation (widget, &allocation);
597

598 599
  gtk_widget_set_events (widget, gtk_widget_get_events (widget) |
			 GDK_KEY_RELEASE_MASK);
Matthias Clasen's avatar
Matthias Clasen committed
600
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->realize (widget);
601

602
  attributes.window_type = GDK_WINDOW_CHILD;
603
  attributes.wclass = GDK_INPUT_ONLY;
604 605 606 607 608 609
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.event_mask = gtk_widget_get_events (widget);
  attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK 
    | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK 
    | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;

610
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
611

612 613
  attributes.x = allocation.x + allocation.width - arrow_size - 2 * style->xthickness;
  attributes.y = allocation.y + (allocation.height - requisition.height) / 2;
614
  attributes.width = arrow_size + 2 * style->xthickness;
615
  attributes.height = requisition.height;
616

617
  priv->panel = gdk_window_new (gtk_widget_get_window (widget),
618 619 620
                                &attributes, attributes_mask);
  gdk_window_set_user_data (priv->panel, widget);

621
  return_val = FALSE;
Manish Singh's avatar
Manish Singh committed
622
  g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
623 624
  if (return_val == FALSE)
    gtk_spin_button_default_output (spin_button);
625 626

  gtk_widget_queue_resize (GTK_WIDGET (spin_button));
627 628 629 630 631
}

static void
gtk_spin_button_unrealize (GtkWidget *widget)
{
632
  GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
633
  GtkSpinButtonPrivate *priv = spin->priv;
634

Matthias Clasen's avatar
Matthias Clasen committed
635
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unrealize (widget);
636

637
  if (priv->panel)
638
    {
639 640 641
      gdk_window_set_user_data (priv->panel, NULL);
      gdk_window_destroy (priv->panel);
      priv->panel = NULL;
642 643 644
    }
}

645
static int
646
compute_double_length (double val, int digits)
647
{
648
  int a;
649 650 651 652
  int extra;

  a = 1;
  if (fabs (val) > 1.0)
653
    a = floor (log10 (fabs (val))) + 1;  
654 655 656 657

  extra = 0;
  
  /* The dot: */
658
  if (digits > 0)
659 660 661 662 663 664
    extra++;

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

665
  return a + digits + extra;
666 667
}

668
static void
669 670 671
gtk_spin_button_get_preferred_width (GtkWidget *widget,
                                     gint      *minimum,
                                     gint      *natural)
672
{
673
  GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
674
  GtkSpinButtonPrivate *priv = spin_button->priv;
675
  GtkEntry *entry = GTK_ENTRY (widget);
676
  GtkStyle *style;
677
  gint arrow_size;
678

679 680
  style = gtk_widget_get_style (widget);

681
  arrow_size = spin_button_get_arrow_size (spin_button);
682

683
  GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->get_preferred_width (widget, minimum, natural);
684

685
  if (gtk_entry_get_width_chars (entry) < 0)
686
    {
687
      PangoContext *context;
688
      PangoFontMetrics *metrics;
689 690
      gint width;
      gint w;
691
      gint string_len;
692
      gint max_string_len;
693
      gint digit_width;
694 695
      gboolean interior_focus;
      gint focus_width;
696
      gint xborder, yborder;
697
      GtkBorder inner_border;
698 699

      gtk_widget_style_get (widget,
700 701 702
                            "interior-focus", &interior_focus,
                            "focus-line-width", &focus_width,
                            NULL);
703

704
      context = gtk_widget_get_pango_context (widget);
705
      metrics = pango_context_get_metrics (context,
706
                                           style->font_desc,
707
                                           pango_context_get_language (context));
708 709

      digit_width = pango_font_metrics_get_approximate_digit_width (metrics);
Kristian Rietveld's avatar
Kristian Rietveld committed
710 711
      digit_width = PANGO_SCALE *
        ((digit_width + PANGO_SCALE - 1) / PANGO_SCALE);
712 713

      pango_font_metrics_unref (metrics);
714

715 716
      /* Get max of MIN_SPIN_BUTTON_WIDTH, size of upper, size of lower */
      width = MIN_SPIN_BUTTON_WIDTH;
717 718
      max_string_len = MAX (10, compute_double_length (1e9 * priv->adjustment->step_increment,
                                                       priv->digits));
719

720 721
      string_len = compute_double_length (priv->adjustment->upper,
                                          priv->digits);
Kristian Rietveld's avatar
Kristian Rietveld committed
722
      w = PANGO_PIXELS (MIN (string_len, max_string_len) * digit_width);
723
      width = MAX (width, w);
724
      string_len = compute_double_length (priv->adjustment->lower, priv->digits);
Kristian Rietveld's avatar
Kristian Rietveld committed
725
      w = PANGO_PIXELS (MIN (string_len, max_string_len) * digit_width);
726
      width = MAX (width, w);
727

728
      _gtk_entry_get_borders (entry, &xborder, &yborder);
729
      _gtk_entry_effective_inner_border (entry, &inner_border);
730

731 732 733 734
      width += xborder * 2 + inner_border.left + inner_border.right;

      *minimum = width;
      *natural = width;
735
    }
736

737 738
  *minimum += arrow_size + 2 * style->xthickness;
  *natural += arrow_size + 2 * style->xthickness;
739 740 741 742 743 744
}

static void
gtk_spin_button_size_allocate (GtkWidget     *widget,
			       GtkAllocation *allocation)
{
745
  GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
746
  GtkSpinButtonPrivate *priv = spin->priv;
747
  GtkAllocation panel_allocation;
748
  GtkRequisition requisition;
749
  gint arrow_size;
750 751
  gint panel_width;

752
  arrow_size = spin_button_get_arrow_size (spin);
753 754
  panel_width = arrow_size + 2 * gtk_widget_get_style (widget)->xthickness;

755
  gtk_widget_get_preferred_size (widget, &requisition, NULL);
756

757 758
  gtk_widget_set_allocation (widget, allocation);

759
  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
760
    panel_allocation.x = allocation->x;
761
  else
762
    panel_allocation.x = allocation->x + allocation->width - panel_width;