gtkspinbutton.c 58.5 KB
Newer Older
1 2 3
/* GTK - The GIMP Toolkit
 * 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 31 32
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
33 34
#include <string.h>
#include <locale.h>
35 36 37 38
#include "gdk/gdkkeysyms.h"
#include "gtkspinbutton.h"
#include "gtkmain.h"
#include "gtksignal.h"
39
#include "gtksettings.h"
40
#include "gtkintl.h"
41

42 43 44 45 46
#define MIN_SPIN_BUTTON_WIDTH              30
#define ARROW_SIZE                         11
#define SPIN_BUTTON_INITIAL_TIMER_DELAY    200
#define SPIN_BUTTON_TIMER_DELAY            20
#define MAX_TIMER_CALLS                    5
47
#define EPSILON                            1e-5
48
#define	MAX_DIGITS			   20
49

50
enum {
51 52 53 54 55 56 57 58 59
  PROP_0,
  PROP_ADJUSTMENT,
  PROP_CLIMB_RATE,
  PROP_DIGITS,
  PROP_SNAP_TO_TICKS,
  PROP_NUMERIC,
  PROP_WRAP,
  PROP_UPDATE_POLICY,
  PROP_VALUE
60 61
};

62 63 64 65 66
/* Signals */
enum
{
  INPUT,
  OUTPUT,
67
  VALUE_CHANGED,
68 69
  LAST_SIGNAL
};
70 71 72

static void gtk_spin_button_class_init     (GtkSpinButtonClass *klass);
static void gtk_spin_button_init           (GtkSpinButton      *spin_button);
73
static void gtk_spin_button_finalize       (GObject            *object);
74 75 76 77 78 79 80 81
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);
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
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);
static void gtk_spin_button_size_request   (GtkWidget          *widget,
					    GtkRequisition     *requisition);
static void gtk_spin_button_size_allocate  (GtkWidget          *widget,
					    GtkAllocation      *allocation);
static gint gtk_spin_button_expose         (GtkWidget          *widget,
					    GdkEventExpose     *event);
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);
static void gtk_spin_button_draw_arrow     (GtkSpinButton      *spin_button, 
					    guint               arrow);
static gint gtk_spin_button_timer          (GtkSpinButton      *spin_button);
107
static void gtk_spin_button_value_changed  (GtkAdjustment      *adjustment,
108 109 110
					    GtkSpinButton      *spin_button); 
static gint gtk_spin_button_key_press      (GtkWidget          *widget,
					    GdkEventKey        *event);
111 112
static gint gtk_spin_button_key_release    (GtkWidget          *widget,
					    GdkEventKey        *event);
113 114
static gint gtk_spin_button_scroll         (GtkWidget          *widget,
					    GdkEventScroll     *event);
Owen Taylor's avatar
Owen Taylor committed
115
static void gtk_spin_button_activate       (GtkEntry           *entry);
116
static void gtk_spin_button_snap           (GtkSpinButton      *spin_button,
117
					    gdouble             val);
Owen Taylor's avatar
Owen Taylor committed
118
static void gtk_spin_button_insert_text    (GtkEntry           *entry,
119
					    const gchar        *new_text,
120
					    gint                new_text_length,
121
					    gint               *position);
122
static void gtk_spin_button_real_spin      (GtkSpinButton      *spin_button,
123
					    gdouble             step);
124
static gint gtk_spin_button_default_input  (GtkSpinButton      *spin_button,
125
					    gdouble            *new_val);
126
static gint gtk_spin_button_default_output (GtkSpinButton      *spin_button);
127
static gint spin_button_get_shadow_type    (GtkSpinButton      *spin_button);
128 129


130
static GtkEntryClass *parent_class = NULL;
131
static guint spinbutton_signals[LAST_SIGNAL] = {0};
132 133


134
GtkType
135
gtk_spin_button_get_type (void)
136
{
137
  static GtkType spin_button_type = 0;
138 139 140

  if (!spin_button_type)
    {
141
      static const GtkTypeInfo spin_button_info =
142 143 144 145 146 147
      {
	"GtkSpinButton",
	sizeof (GtkSpinButton),
	sizeof (GtkSpinButtonClass),
	(GtkClassInitFunc) gtk_spin_button_class_init,
	(GtkObjectInitFunc) gtk_spin_button_init,
148 149
	/* reserved_1 */ NULL,
        /* reserved_2 */ NULL,
150
        (GtkClassInitFunc) NULL,
151 152
      };

153
      spin_button_type = gtk_type_unique (GTK_TYPE_ENTRY, &spin_button_info);
154 155 156 157 158 159 160
    }
  return spin_button_type;
}

static void
gtk_spin_button_class_init (GtkSpinButtonClass *class)
{
161
  GObjectClass     *gobject_class = G_OBJECT_CLASS (class);
162 163
  GtkObjectClass   *object_class;
  GtkWidgetClass   *widget_class;
Owen Taylor's avatar
Owen Taylor committed
164
  GtkEntryClass    *entry_class;
165

166 167
  object_class   = (GtkObjectClass*)   class;
  widget_class   = (GtkWidgetClass*)   class;
Owen Taylor's avatar
Owen Taylor committed
168
  entry_class    = (GtkEntryClass*)    class; 
169

170 171
  parent_class = gtk_type_class (GTK_TYPE_ENTRY);

172 173
  gobject_class->finalize = gtk_spin_button_finalize;

174 175
  gobject_class->set_property = gtk_spin_button_set_property;
  gobject_class->get_property = gtk_spin_button_get_property;
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193

  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;
  widget_class->size_request = gtk_spin_button_size_request;
  widget_class->size_allocate = gtk_spin_button_size_allocate;
  widget_class->expose_event = gtk_spin_button_expose;
  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_press_event = gtk_spin_button_key_press;
  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;

Owen Taylor's avatar
Owen Taylor committed
194 195
  entry_class->insert_text = gtk_spin_button_insert_text;
  entry_class->activate = gtk_spin_button_activate;
196 197 198 199

  class->input = NULL;
  class->output = NULL;

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
  g_object_class_install_property (gobject_class,
                                   PROP_ADJUSTMENT,
                                   g_param_spec_object ("adjustment",
                                                        _("Adjustment"),
                                                        _("The adjustment that holds the value of the spinbutton"),
                                                        GTK_TYPE_ADJUSTMENT,
                                                        G_PARAM_READWRITE));
  
  g_object_class_install_property (gobject_class,
                                   PROP_CLIMB_RATE,
                                   g_param_spec_double ("climb_rate",
							_("Climb Rate"),
							_("The acceleration rate when you hold down a button"),
							0.0,
							G_MAXDOUBLE,
							0.0,
							G_PARAM_READWRITE));  
  
  g_object_class_install_property (gobject_class,
                                   PROP_DIGITS,
                                   g_param_spec_uint ("digits",
						      _("Digits"),
						      _("The number of decimal places to display"),
						      0,
224
						      MAX_DIGITS,
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
						      0,
						      G_PARAM_READWRITE));
  
  g_object_class_install_property (gobject_class,
                                   PROP_SNAP_TO_TICKS,
                                   g_param_spec_boolean ("snap_to_ticks",
							 _("Snap to Ticks"),
							 _("Whether erroneous values are automatically changed to a spin button's nearest step increment"),
							 FALSE,
							 G_PARAM_READWRITE));
  
  g_object_class_install_property (gobject_class,
                                   PROP_NUMERIC,
                                   g_param_spec_boolean ("numeric",
							 _("Numeric"),
							 _("Whether non-numeric characters should be ignored"),
							 FALSE,
							 G_PARAM_READWRITE));
  
  g_object_class_install_property (gobject_class,
                                   PROP_WRAP,
                                   g_param_spec_boolean ("wrap",
							 _("Wrap"),
							 _("Whether a spin button should wrap upon reaching its limits"),
							 FALSE,
							 G_PARAM_READWRITE));
  
  g_object_class_install_property (gobject_class,
                                   PROP_UPDATE_POLICY,
                                   g_param_spec_enum ("update_policy",
						      _("Update Policy"),
						      _("Whether the spin button should update always, or only when the value is legal"),
						      GTK_TYPE_SPIN_BUTTON_UPDATE_POLICY,
						      GTK_UPDATE_ALWAYS,
						      G_PARAM_READWRITE));
  
  g_object_class_install_property (gobject_class,
                                   PROP_VALUE,
                                   g_param_spec_double ("value",
							_("Value"),
							_("Reads the current value, or sets a new value"),
							-G_MAXDOUBLE,
							G_MAXDOUBLE,
							0.0,
							G_PARAM_READWRITE));  
270 271 272 273
  
  gtk_widget_class_install_style_property_parser (widget_class,
						  g_param_spec_enum ("shadow_type", "Shadow Type", NULL,
								     GTK_TYPE_SHADOW_TYPE,
274
								     GTK_SHADOW_IN,
275 276
								     G_PARAM_READABLE),
						  gtk_rc_property_parse_enum);
277 278 279
  spinbutton_signals[INPUT] =
    gtk_signal_new ("input",
		    GTK_RUN_LAST,
280
		    GTK_CLASS_TYPE (object_class),
281 282 283 284 285
		    GTK_SIGNAL_OFFSET (GtkSpinButtonClass, input),
		    gtk_marshal_INT__POINTER,
		    GTK_TYPE_INT, 1, GTK_TYPE_POINTER);

  spinbutton_signals[OUTPUT] =
286 287 288 289 290 291 292
    g_signal_new ("output",
                  G_TYPE_FROM_CLASS(object_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET(GtkSpinButtonClass, output),
                  _gtk_boolean_handled_accumulator, NULL,
                  gtk_marshal_BOOLEAN__VOID,
                  G_TYPE_BOOLEAN, 0);
293 294 295 296 297 298 299 300

  spinbutton_signals[VALUE_CHANGED] =
    gtk_signal_new ("value_changed",
		    GTK_RUN_LAST,
		    GTK_CLASS_TYPE (object_class),
		    GTK_SIGNAL_OFFSET (GtkSpinButtonClass, value_changed),
		    gtk_marshal_VOID__VOID,
		    GTK_TYPE_NONE, 0);
301 302
}

303
static void
304 305 306 307
gtk_spin_button_set_property (GObject      *object,
			      guint         prop_id,
			      const GValue *value,
			      GParamSpec   *pspec)
308 309 310 311 312
{
  GtkSpinButton *spin_button;

  spin_button = GTK_SPIN_BUTTON (object);
  
313
  switch (prop_id)
314 315 316
    {
      GtkAdjustment *adjustment;

317 318
    case PROP_ADJUSTMENT:
      adjustment = GTK_ADJUSTMENT (g_value_get_object (value));
319 320 321 322
      if (!adjustment)
	adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
      gtk_spin_button_set_adjustment (spin_button, adjustment);
      break;
323
    case PROP_CLIMB_RATE:
324 325
      gtk_spin_button_configure (spin_button,
				 spin_button->adjustment,
326
				 g_value_get_double (value),
327 328
				 spin_button->digits);
      break;
329
    case PROP_DIGITS:
330 331 332
      gtk_spin_button_configure (spin_button,
				 spin_button->adjustment,
				 spin_button->climb_rate,
333
				 g_value_get_uint (value));
334
      break;
335 336
    case PROP_SNAP_TO_TICKS:
      gtk_spin_button_set_snap_to_ticks (spin_button, g_value_get_boolean (value));
337
      break;
338 339
    case PROP_NUMERIC:
      gtk_spin_button_set_numeric (spin_button, g_value_get_boolean (value));
340
      break;
341 342
    case PROP_WRAP:
      gtk_spin_button_set_wrap (spin_button, g_value_get_boolean (value));
343
      break;
344 345
    case PROP_UPDATE_POLICY:
      gtk_spin_button_set_update_policy (spin_button, g_value_get_enum (value));
346
      break;
347 348
    case PROP_VALUE:
      gtk_spin_button_set_value (spin_button, g_value_get_double (value));
349 350 351 352 353 354 355
      break;
    default:
      break;
    }
}

static void
356 357 358 359
gtk_spin_button_get_property (GObject      *object,
			      guint         prop_id,
			      GValue       *value,
			      GParamSpec   *pspec)
360 361 362 363 364
{
  GtkSpinButton *spin_button;

  spin_button = GTK_SPIN_BUTTON (object);
  
365
  switch (prop_id)
366
    {
367 368
    case PROP_ADJUSTMENT:
      g_value_set_object (value, G_OBJECT (spin_button->adjustment));
369
      break;
370 371
    case PROP_CLIMB_RATE:
      g_value_set_double (value, spin_button->climb_rate);
372
      break;
373 374
    case PROP_DIGITS:
      g_value_set_uint (value, spin_button->digits);
375
      break;
376 377
    case PROP_SNAP_TO_TICKS:
      g_value_set_boolean (value, spin_button->snap_to_ticks);
378
      break;
379 380
    case PROP_NUMERIC:
      g_value_set_boolean (value, spin_button->numeric);
381
      break;
382 383
    case PROP_WRAP:
      g_value_set_boolean (value, spin_button->wrap);
384
      break;
385 386
    case PROP_UPDATE_POLICY:
      g_value_set_enum (value, spin_button->update_policy);
387
      break;
388 389
     case PROP_VALUE:
       g_value_set_double (value, spin_button->adjustment->value);
390 391
      break;
    default:
392
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
393 394 395 396
      break;
    }
}

397 398 399 400
static void
gtk_spin_button_init (GtkSpinButton *spin_button)
{
  spin_button->adjustment = NULL;
401
  spin_button->panel = NULL;
402
  spin_button->timer = 0;
403
  spin_button->ev_time = 0;
404 405
  spin_button->climb_rate = 0.0;
  spin_button->timer_step = 0.0;
406
  spin_button->update_policy = GTK_UPDATE_ALWAYS;
407 408 409
  spin_button->in_child = 2;
  spin_button->click_child = 2;
  spin_button->button = 0;
410
  spin_button->need_timer = FALSE;
411 412
  spin_button->timer_calls = 0;
  spin_button->digits = 0;
413 414 415
  spin_button->numeric = FALSE;
  spin_button->wrap = FALSE;
  spin_button->snap_to_ticks = FALSE;
416
  gtk_spin_button_set_adjustment (spin_button,
417
	  (GtkAdjustment*) gtk_adjustment_new (0, 0, 0, 0, 0, 0));
418 419 420
}

static void
421
gtk_spin_button_finalize (GObject *object)
422 423 424 425 426
{
  g_return_if_fail (GTK_IS_SPIN_BUTTON (object));

  gtk_object_unref (GTK_OBJECT (GTK_SPIN_BUTTON (object)->adjustment));
  
427
  G_OBJECT_CLASS (parent_class)->finalize (object);
428 429 430 431 432 433 434
}

static void
gtk_spin_button_map (GtkWidget *widget)
{
  g_return_if_fail (GTK_IS_SPIN_BUTTON (widget));

Tim Janik's avatar
Tim Janik committed
435
  if (GTK_WIDGET_REALIZED (widget) && !GTK_WIDGET_MAPPED (widget))
436 437
    {
      GTK_WIDGET_CLASS (parent_class)->map (widget);
438
      gdk_window_show (GTK_SPIN_BUTTON (widget)->panel);
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
    }
}

static void
gtk_spin_button_unmap (GtkWidget *widget)
{
  g_return_if_fail (GTK_IS_SPIN_BUTTON (widget));

  if (GTK_WIDGET_MAPPED (widget))
    {
      gdk_window_hide (GTK_SPIN_BUTTON (widget)->panel);
      GTK_WIDGET_CLASS (parent_class)->unmap (widget);
    }
}

static void
gtk_spin_button_realize (GtkWidget *widget)
{
457
  GtkSpinButton *spin_button;
458 459
  GdkWindowAttr attributes;
  gint attributes_mask;
460
  guint real_width;
461
  gint return_val;
462 463

  g_return_if_fail (GTK_IS_SPIN_BUTTON (widget));
464
  
465
  spin_button = GTK_SPIN_BUTTON (widget);
466

467
  real_width = widget->allocation.width;
468
  widget->allocation.width -= ARROW_SIZE + 2 * widget->style->xthickness;
469 470
  gtk_widget_set_events (widget, gtk_widget_get_events (widget) |
			 GDK_KEY_RELEASE_MASK);
471
  GTK_WIDGET_CLASS (parent_class)->realize (widget);
472

473 474
  widget->allocation.width = real_width;
  
475 476 477 478 479 480 481 482 483 484 485
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.colormap = gtk_widget_get_colormap (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;

  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

486
  attributes.x = (widget->allocation.x + widget->allocation.width - ARROW_SIZE -
487
		  2 * widget->style->xthickness);
488 489
  attributes.y = widget->allocation.y + (widget->allocation.height -
					 widget->requisition.height) / 2;
490
  attributes.width = ARROW_SIZE + 2 * widget->style->xthickness;
491
  attributes.height = widget->requisition.height;
492
  
493 494 495
  spin_button->panel = gdk_window_new (gtk_widget_get_parent_window (widget), 
				       &attributes, attributes_mask);
  gdk_window_set_user_data (spin_button->panel, widget);
496

497 498 499 500 501 502 503
  gtk_style_set_background (widget->style, spin_button->panel, GTK_STATE_NORMAL);

  return_val = FALSE;
  gtk_signal_emit (GTK_OBJECT (spin_button), spinbutton_signals[OUTPUT],
		   &return_val);
  if (return_val == FALSE)
    gtk_spin_button_default_output (spin_button);
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518
}

static void
gtk_spin_button_unrealize (GtkWidget *widget)
{
  GtkSpinButton *spin;

  g_return_if_fail (GTK_IS_SPIN_BUTTON (widget));

  spin = GTK_SPIN_BUTTON (widget);

  GTK_WIDGET_CLASS (parent_class)->unrealize (widget);

  if (spin->panel)
    {
Tim Janik's avatar
Tim Janik committed
519
      gdk_window_set_user_data (spin->panel, NULL);
520 521 522 523 524
      gdk_window_destroy (spin->panel);
      spin->panel = NULL;
    }
}

525
static int
526
compute_double_length (double val, int digits)
527
{
528
  int a;
529 530 531 532
  int extra;

  a = 1;
  if (fabs (val) > 1.0)
533
    a = floor (log10 (fabs (val))) + 1;  
534 535 536 537

  extra = 0;
  
  /* The dot: */
538
  if (digits > 0)
539 540 541 542 543 544
    extra++;

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

545
  return a + digits + extra;
546 547
}

548 549 550 551
static void
gtk_spin_button_size_request (GtkWidget      *widget,
			      GtkRequisition *requisition)
{
552
  GtkEntry *entry;
553
  GtkSpinButton *spin_button;
554
  
555 556 557
  g_return_if_fail (requisition != NULL);
  g_return_if_fail (GTK_IS_SPIN_BUTTON (widget));

558
  entry = GTK_ENTRY (widget);
559
  spin_button = GTK_SPIN_BUTTON (widget);
560
  
561 562 563
  GTK_WIDGET_CLASS (parent_class)->size_request (widget, requisition);

  if (entry->width_chars < 0)
564
    {
565
      PangoContext *context;
566 567 568
      PangoFontMetrics metrics;
      gint width;
      gint w;
569
      int string_len;
570

571
      context = gtk_widget_get_pango_context (widget);
572 573 574 575
      pango_context_get_metrics (context,
				 widget->style->font_desc,
				 pango_context_get_language (context),
				 &metrics);
576 577 578 579
      
      /* Get max of MIN_SPIN_BUTTON_WIDTH, size of upper, size of lower */
      
      width = MIN_SPIN_BUTTON_WIDTH;
580 581

      string_len = compute_double_length (spin_button->adjustment->upper,
582
                                          spin_button->digits);
583
      w = MIN (string_len, 10) * PANGO_PIXELS (metrics.approximate_digit_width);
584
      width = MAX (width, w);
585 586 587
      string_len = compute_double_length (spin_button->adjustment->lower,
					  spin_button->adjustment->step_increment);
      w = MIN (string_len, 10) * PANGO_PIXELS (metrics.approximate_digit_width);
588 589 590 591
      width = MAX (width, w);
      
      requisition->width = width + ARROW_SIZE + 2 * widget->style->xthickness;
    }
592 593
  else
    requisition->width += ARROW_SIZE + 2 * widget->style->xthickness;
594 595 596 597 598 599 600 601 602 603 604 605
}

static void
gtk_spin_button_size_allocate (GtkWidget     *widget,
			       GtkAllocation *allocation)
{
  GtkAllocation child_allocation;

  g_return_if_fail (GTK_IS_SPIN_BUTTON (widget));
  g_return_if_fail (allocation != NULL);

  child_allocation = *allocation;
606 607
  if (child_allocation.width > ARROW_SIZE + 2 * widget->style->xthickness)
    child_allocation.width -= ARROW_SIZE + 2 * widget->style->xthickness;
608

609
  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
610
    child_allocation.x += ARROW_SIZE + 2 * widget->style->xthickness;
611

612 613 614
  GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, &child_allocation);

  widget->allocation = *allocation;
Tim Janik's avatar
Tim Janik committed
615 616 617

  if (GTK_WIDGET_REALIZED (widget))
    {
618
      child_allocation.width = ARROW_SIZE + 2 * widget->style->xthickness;
619 620 621 622
      child_allocation.height = widget->requisition.height;

      if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
	child_allocation.x = (allocation->x + allocation->width - ARROW_SIZE - 
623
			      2 * widget->style->xthickness);
624
      else
625 626
	child_allocation.x = allocation->x;      

627
      child_allocation.y = allocation->y + (allocation->height - widget->requisition.height) / 2;
Tim Janik's avatar
Tim Janik committed
628 629 630 631 632 633 634

      gdk_window_move_resize (GTK_SPIN_BUTTON (widget)->panel, 
			      child_allocation.x,
			      child_allocation.y,
			      child_allocation.width,
			      child_allocation.height); 
    }
635 636
}

637 638 639
static gint
gtk_spin_button_expose (GtkWidget      *widget,
			GdkEventExpose *event)
640 641 642
{
  GtkSpinButton *spin;

643 644
  g_return_val_if_fail (GTK_IS_SPIN_BUTTON (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);
645 646 647 648 649

  spin = GTK_SPIN_BUTTON (widget);

  if (GTK_WIDGET_DRAWABLE (widget))
    {
650 651
      GtkShadowType shadow_type;

652 653 654 655
      /* FIXME this seems like really broken code -
       * why aren't we looking at event->window
       * and acting accordingly?
       */
656 657 658

      shadow_type = spin_button_get_shadow_type (spin);
      if (shadow_type != GTK_SHADOW_NONE)
659
	gtk_paint_box (widget->style, spin->panel,
660
		       GTK_STATE_NORMAL, shadow_type,
661
		       &event->area, widget, "spinbutton",
662
		       0, 0, 
663
		       ARROW_SIZE + 2 * widget->style->xthickness,
664 665 666 667
		       widget->requisition.height); 
      else
	 {
	    gdk_window_set_back_pixmap (spin->panel, NULL, TRUE);
668 669 670
	    gdk_window_clear_area (spin->panel,
                                   event->area.x, event->area.y,
                                   event->area.width, event->area.height);
671 672 673 674
	 }
       gtk_spin_button_draw_arrow (spin, GTK_ARROW_UP);
       gtk_spin_button_draw_arrow (spin, GTK_ARROW_DOWN);

675
       GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
676 677 678 679 680 681 682 683 684
    }

  return FALSE;
}

static void
gtk_spin_button_draw_arrow (GtkSpinButton *spin_button, 
			    guint          arrow)
{
685
  GtkShadowType spin_shadow_type;
686 687 688
  GtkStateType state_type;
  GtkShadowType shadow_type;
  GtkWidget *widget;
689 690
  gint x;
  gint y;
691 692 693 694

  g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
  
  widget = GTK_WIDGET (spin_button);
695
  spin_shadow_type = spin_button_get_shadow_type (spin_button);
696 697 698

  if (GTK_WIDGET_DRAWABLE (spin_button))
    {
699 700 701 702 703 704 705 706 707 708 709
      if (!spin_button->wrap &&
	  (((arrow == GTK_ARROW_UP &&
	  (spin_button->adjustment->upper - spin_button->adjustment->value
	   <= EPSILON))) ||
	  ((arrow == GTK_ARROW_DOWN &&
	  (spin_button->adjustment->value - spin_button->adjustment->lower
	   <= EPSILON)))))
	{
	  shadow_type = GTK_SHADOW_ETCHED_IN;
	  state_type = GTK_STATE_NORMAL;
	}
710
      else
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726
	{
	  if (spin_button->in_child == arrow)
	    {
	      if (spin_button->click_child == arrow)
		state_type = GTK_STATE_ACTIVE;
	      else
		state_type = GTK_STATE_PRELIGHT;
	    }
	  else
	    state_type = GTK_STATE_NORMAL;
	  
	  if (spin_button->click_child == arrow)
	    shadow_type = GTK_SHADOW_IN;
	  else
	    shadow_type = GTK_SHADOW_OUT;
	}
727 728
      if (arrow == GTK_ARROW_UP)
	{
729
	  if (spin_shadow_type != GTK_SHADOW_NONE)
730
	    {
731 732
	      x = widget->style->xthickness;
	      y = widget->style->ythickness;
733 734 735
	    }
	  else
	    {
736 737
	      x = widget->style->xthickness - 1;
	      y = widget->style->ythickness - 1;
738
	    }
739 740 741 742 743
	  gtk_paint_arrow (widget->style, spin_button->panel,
			   state_type, shadow_type, 
			   NULL, widget, "spinbutton",
			   arrow, TRUE, 
			   x, y, ARROW_SIZE, widget->requisition.height / 2 
744
			   - widget->style->ythickness);
745 746 747
	}
      else
	{
748
	  if (spin_shadow_type != GTK_SHADOW_NONE)
749
	    {
750
	      x = widget->style->xthickness;
751 752 753 754
	      y = widget->requisition.height / 2;
	    }
	  else
	    {
755
	      x = widget->style->xthickness - 1;
756 757
	      y = widget->requisition.height / 2 + 1;
	    }
758 759 760 761 762
	  gtk_paint_arrow (widget->style, spin_button->panel,
			   state_type, shadow_type, 
			   NULL, widget, "spinbutton",
			   arrow, TRUE, 
			   x, y, ARROW_SIZE, widget->requisition.height / 2 
763
			   - widget->style->ythickness);
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 790 791 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 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835
	}
    }
}

static gint
gtk_spin_button_enter_notify (GtkWidget        *widget,
			      GdkEventCrossing *event)
{
  GtkSpinButton *spin;

  g_return_val_if_fail (GTK_IS_SPIN_BUTTON (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  spin = GTK_SPIN_BUTTON (widget);

  if (event->window == spin->panel)
    {
      gint x;
      gint y;

      gdk_window_get_pointer (spin->panel, &x, &y, NULL);

      if (y <= widget->requisition.height / 2)
	{
	  spin->in_child = GTK_ARROW_UP;
	  if (spin->click_child == 2) 
	    gtk_spin_button_draw_arrow (spin, GTK_ARROW_UP);
	}
      else
	{
	  spin->in_child = GTK_ARROW_DOWN;
	  if (spin->click_child == 2) 
	    gtk_spin_button_draw_arrow (spin, GTK_ARROW_DOWN);
	}
    }
  return FALSE;
}

static gint
gtk_spin_button_leave_notify (GtkWidget        *widget,
			      GdkEventCrossing *event)
{
  GtkSpinButton *spin;

  g_return_val_if_fail (GTK_IS_SPIN_BUTTON (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  spin = GTK_SPIN_BUTTON (widget);

  if (event->window == spin->panel && spin->click_child == 2)
    {
      if (spin->in_child == GTK_ARROW_UP) 
	{
	  spin->in_child = 2;
	  gtk_spin_button_draw_arrow (spin, GTK_ARROW_UP);
	}
      else
	{
	  spin->in_child = 2;
	  gtk_spin_button_draw_arrow (spin, GTK_ARROW_DOWN);
	}
    }
  return FALSE;
}

static gint
gtk_spin_button_focus_out (GtkWidget     *widget,
			   GdkEventFocus *event)
{
  g_return_val_if_fail (GTK_IS_SPIN_BUTTON (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

Owen Taylor's avatar
Owen Taylor committed
836
  if (GTK_ENTRY (widget)->editable)
837
    gtk_spin_button_update (GTK_SPIN_BUTTON (widget));
838

839
  return GTK_WIDGET_CLASS (parent_class)->focus_out_event (widget, event);
840 841
}

842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870
static gint
gtk_spin_button_scroll (GtkWidget      *widget,
			GdkEventScroll *event)
{
  GtkSpinButton *spin;

  g_return_val_if_fail (GTK_IS_SPIN_BUTTON (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  spin = GTK_SPIN_BUTTON (widget);

  if (event->direction == GDK_SCROLL_UP)
    {
      if (!GTK_WIDGET_HAS_FOCUS (widget))
	gtk_widget_grab_focus (widget);
      gtk_spin_button_real_spin (spin, spin->adjustment->step_increment);
    }
  else if (event->direction == GDK_SCROLL_DOWN)
    {
      if (!GTK_WIDGET_HAS_FOCUS (widget))
	gtk_widget_grab_focus (widget);
      gtk_spin_button_real_spin (spin, -spin->adjustment->step_increment); 
    }
  else
    return FALSE;

  return TRUE;
}

871 872 873 874 875 876 877 878 879 880 881 882 883
static gint
gtk_spin_button_button_press (GtkWidget      *widget,
			      GdkEventButton *event)
{
  GtkSpinButton *spin;

  g_return_val_if_fail (GTK_IS_SPIN_BUTTON (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  spin = GTK_SPIN_BUTTON (widget);

  if (!spin->button)
    {
884
      if (event->window == spin->panel)
885 886 887 888 889
	{
	  if (!GTK_WIDGET_HAS_FOCUS (widget))
	    gtk_widget_grab_focus (widget);
	  gtk_grab_add (widget);
	  spin->button = event->button;
890
	  
Owen Taylor's avatar
Owen Taylor committed
891
	  if (GTK_ENTRY (widget)->editable)
892
	    gtk_spin_button_update (spin);
893
	  
894 895 896 897 898
	  if (event->y <= widget->requisition.height / 2)
	    {
	      spin->click_child = GTK_ARROW_UP;
	      if (event->button == 1)
		{
899
		 gtk_spin_button_real_spin (spin, 
900
					    spin->adjustment->step_increment);
901 902 903 904 905 906 907 908 909 910 911
		  if (!spin->timer)
		    {
		      spin->timer_step = spin->adjustment->step_increment;
		      spin->need_timer = TRUE;
		      spin->timer = gtk_timeout_add 
			(SPIN_BUTTON_INITIAL_TIMER_DELAY, 
			 (GtkFunction) gtk_spin_button_timer, (gpointer) spin);
		    }
		}
	      else if (event->button == 2)
		{
912
		 gtk_spin_button_real_spin (spin, 
913
					    spin->adjustment->page_increment);
914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
		  if (!spin->timer) 
		    {
		      spin->timer_step = spin->adjustment->page_increment;
		      spin->need_timer = TRUE;
		      spin->timer = gtk_timeout_add 
			(SPIN_BUTTON_INITIAL_TIMER_DELAY, 
			 (GtkFunction) gtk_spin_button_timer, (gpointer) spin);
		    }
		}
	      gtk_spin_button_draw_arrow (spin, GTK_ARROW_UP);
	    }
	  else 
	    {
	      spin->click_child = GTK_ARROW_DOWN;
	      if (event->button == 1)
		{
930 931
		  gtk_spin_button_real_spin (spin,
					     -spin->adjustment->step_increment);
932 933 934 935 936 937 938 939 940 941 942
		  if (!spin->timer)
		    {
		      spin->timer_step = spin->adjustment->step_increment;
		      spin->need_timer = TRUE;
		      spin->timer = gtk_timeout_add 
			(SPIN_BUTTON_INITIAL_TIMER_DELAY, 
			 (GtkFunction) gtk_spin_button_timer, (gpointer) spin);
		    }
		}      
	      else if (event->button == 2)
		{
943 944
		  gtk_spin_button_real_spin (spin,
					     -spin->adjustment->page_increment);
945 946 947 948 949 950 951 952 953 954 955
		  if (!spin->timer) 
		    {
		      spin->timer_step = spin->adjustment->page_increment;
		      spin->need_timer = TRUE;
		      spin->timer = gtk_timeout_add 
			(SPIN_BUTTON_INITIAL_TIMER_DELAY, 
			 (GtkFunction) gtk_spin_button_timer, (gpointer) spin);
		    }
		}
	      gtk_spin_button_draw_arrow (spin, GTK_ARROW_DOWN);
	    }
956
	  return TRUE;
957
	}
958
      else
959
	return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event);
960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990
    }
  return FALSE;
}

static gint
gtk_spin_button_button_release (GtkWidget      *widget,
				GdkEventButton *event)
{
  GtkSpinButton *spin;

  g_return_val_if_fail (GTK_IS_SPIN_BUTTON (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  spin = GTK_SPIN_BUTTON (widget);

  if (event->button == spin->button)
    {
      guint click_child;

      if (spin->timer)
	{
	  gtk_timeout_remove (spin->timer);
	  spin->timer = 0;
	  spin->timer_calls = 0;
	  spin->need_timer = FALSE;
	}

      if (event->button == 3)
	{
	  if (event->y >= 0 && event->x >= 0 && 
	      event->y <= widget->requisition.height &&
991
	      event->x <= ARROW_SIZE + 2 * widget->style->xthickness)
992 993 994
	    {
	      if (spin->click_child == GTK_ARROW_UP &&
		  event->y <= widget->requisition.height / 2)
995
		{
996
		  gdouble diff;
997 998 999 1000 1001

		  diff = spin->adjustment->upper - spin->adjustment->value;
		  if (diff > EPSILON)
		    gtk_spin_button_real_spin (spin, diff);
		}
1002 1003
	      else if (spin->click_child == GTK_ARROW_DOWN &&
		       event->y > widget->requisition.height / 2)
1004
		{
1005
		  gdouble diff;
1006 1007 1008 1009 1010

		  diff = spin->adjustment->value - spin->adjustment->lower;
		  if (diff > EPSILON)
		    gtk_spin_button_real_spin (spin, -diff);
		}
1011 1012 1013 1014 1015 1016 1017
	    }
	}		  
      gtk_grab_remove (widget);
      click_child = spin->click_child;
      spin->click_child = 2;
      spin->button = 0;
      gtk_spin_button_draw_arrow (spin, click_child);
1018
      return TRUE;
1019 1020
    }
  else
1021
    return GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event);
1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064

  return FALSE;
}

static gint
gtk_spin_button_motion_notify (GtkWidget      *widget,
			       GdkEventMotion *event)
{
  GtkSpinButton *spin;

  g_return_val_if_fail (GTK_IS_SPIN_BUTTON (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  spin = GTK_SPIN_BUTTON (widget);
  
  if (spin->button)
    return FALSE;

  if (event->window == spin->panel)
    {
      gint y;

      y = event->y;
      if (event->is_hint)
	gdk_window_get_pointer (spin->panel, NULL, &y, NULL);

      if (y <= widget->requisition.height / 2 && 
	  spin->in_child == GTK_ARROW_DOWN)
	{
	  spin->in_child = GTK_ARROW_UP;
	  gtk_spin_button_draw_arrow (spin, GTK_ARROW_UP);
	  gtk_spin_button_draw_arrow (spin, GTK_ARROW_DOWN);
	}
      else if (y > widget->requisition.height / 2 && 
	  spin->in_child == GTK_ARROW_UP)
	{
	  spin->in_child = GTK_ARROW_DOWN;
	  gtk_spin_button_draw_arrow (spin, GTK_ARROW_UP);
	  gtk_spin_button_draw_arrow (spin, GTK_ARROW_DOWN);
	}
      return FALSE;
    }
	  
1065
  return GTK_WIDGET_CLASS (parent_class)->motion_notify_event (widget, event);
1066 1067 1068 1069 1070
}

static gint
gtk_spin_button_timer (GtkSpinButton *spin_button)
{
1071 1072
  gboolean retval = FALSE;
  
1073
  GDK_THREADS_ENTER ();
1074 1075 1076

  if (spin_button->timer)
    {
1077 1078 1079 1080
      if (spin_button->click_child == GTK_ARROW_UP)
	gtk_spin_button_real_spin (spin_button,	spin_button->timer_step);
      else
	gtk_spin_button_real_spin (spin_button,	-spin_button->timer_step);
1081 1082 1083 1084 1085 1086 1087 1088

      if (spin_button->need_timer)
	{
	  spin_button->need_timer = FALSE;
	  spin_button->timer = gtk_timeout_add 
	    (SPIN_BUTTON_TIMER_DELAY, (GtkFunction) gtk_spin_button_timer, 
	     (gpointer) spin_button);
	}
1089
      else 
1090
	{
1091 1092
	  if (spin_button->climb_rate > 0.0 && spin_button->timer_step 
	      < spin_button->adjustment->page_increment)
1093
	    {
1094 1095 1096 1097 1098 1099 1100
	      if (spin_button->timer_calls < MAX_TIMER_CALLS)
		spin_button->timer_calls++;
	      else 
		{
		  spin_button->timer_calls = 0;
		  spin_button->timer_step += spin_button->climb_rate;
		}
1101
	    }
1102
	  retval = TRUE;
1103
	}
1104
    }
1105

1106
  GDK_THREADS_LEAVE ();
1107 1108

  return retval;
1109 1110 1111
}

static void
1112
gtk_spin_button_value_changed (GtkAdjustment *adjustment,
1113 1114
			       GtkSpinButton *spin_button)
{
1115
  gint return_val;
1116

1117
  g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
1118

1119 1120 1121 1122 1123
  return_val = FALSE;
  gtk_signal_emit (GTK_OBJECT (spin_button), spinbutton_signals[OUTPUT],
		   &return_val);
  if (return_val == FALSE)
    gtk_spin_button_default_output (spin_button);
1124

1125 1126 1127
  gtk_signal_emit (GTK_OBJECT (spin_button), 
		   spinbutton_signals[VALUE_CHANGED]);

1128 1129
  gtk_spin_button_draw_arrow (spin_button, GTK_ARROW_UP);
  gtk_spin_button_draw_arrow (spin_button, GTK_ARROW_DOWN);
1130 1131
  
  g_object_notify (G_OBJECT (spin_button), "value");
1132
}
1133

1134 1135 1136 1137 1138
static gint
gtk_spin_button_key_press (GtkWidget     *widget,
			   GdkEventKey   *event)
{
  GtkSpinButton *spin;
1139
  gint key;
1140
  gboolean key_repeat = FALSE;
1141 1142
  gboolean retval = FALSE;
  
1143
  g_return_val_if_fail (GTK_IS_SPIN_BUTTON (widget), FALSE);
1144
  g_return_val_if_fail (event != NULL, FALSE);
1145 1146
  
  spin = GTK_SPIN_BUTTON (widget);
1147
  key = event->keyval;
1148

1149 1150
  key_repeat = (event->time == spin->ev_time);

1151
  if (GTK_ENTRY (widget)->editable)
1152
    {
1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248
      switch (key)
        {
        case GDK_KP_Up:
        case GDK_Up:

          if (GTK_WIDGET_HAS_FOCUS (widget))
            {
              gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), 
                                            "key_press_event");
              if (!key_repeat)
                spin->timer_step = spin->adjustment->step_increment;

              gtk_spin_button_real_spin (spin, spin->timer_step);

              if (key_repeat)
                {
                  if (spin->climb_rate > 0.0 && spin->timer_step
                      < spin->adjustment->page_increment)
                    {
                      if (spin->timer_calls < MAX_TIMER_CALLS)
                        spin->timer_calls++;
                      else 
                        {
                          spin->timer_calls = 0;
                          spin->timer_step += spin->climb_rate;
                        }
                    }
                }
              retval = TRUE;
            }
          break;

        case GDK_KP_Down:
        case GDK_Down:

          if (GTK_WIDGET_HAS_FOCUS (widget))
            {
              gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), 
                                            "key_press_event");
              if (!key_repeat)
                spin->timer_step = spin->adjustment->step_increment;

              gtk_spin_button_real_spin (spin, -spin->timer_step);

              if (key_repeat)
                {
                  if (spin->climb_rate > 0.0 && spin->timer_step
                      < spin->adjustment->page_increment)
                    {
                      if (spin->timer_calls < MAX_TIMER_CALLS)
                        spin->timer_calls++;
                      else 
                        {
                          spin->timer_calls = 0;
                          spin->timer_step += spin->climb_rate;
                        }
                    }
                }
              retval = TRUE;
            }
          break;

        case GDK_KP_Page_Up:
        case GDK_Page_Up:

          if (event->state & GDK_CONTROL_MASK)
            {
              gdouble diff = spin->adjustment->upper - spin->adjustment->value;
              if (diff > EPSILON)
                gtk_spin_button_real_spin (spin, diff);
            }
          else
            gtk_spin_button_real_spin (spin, spin->adjustment->page_increment);

          retval = TRUE;
          break;
          
        case GDK_KP_Page_Down:
        case GDK_Page_Down:

          if (event->state & GDK_CONTROL_MASK)
            {
              gdouble diff = spin->adjustment->value - spin->adjustment->lower;
              if (diff > EPSILON)
                gtk_spin_button_real_spin (spin, -diff);
            }
          else
            gtk_spin_button_real_spin (spin, -spin->adjustment->page_increment);

          retval = TRUE;
          break;

        default:
          break;
        }
    }
1249

1250 1251 1252
  if (retval)
    {
      gtk_spin_button_update (spin);
1253 1254
      return TRUE;
    }
1255 1256
  else
    return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
1257 1258
}

1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272
static gint
gtk_spin_button_key_release (GtkWidget   *widget,
			     GdkEventKey *event)
{
  GtkSpinButton *spin;

  g_return_val_if_fail (GTK_IS_SPIN_BUTTON (widget), FALSE);
  
  spin = GTK_SPIN_BUTTON (widget);
  
  spin->ev_time = event->time;
  return TRUE;
}

1273 1274
static void
gtk_spin_button_snap (GtkSpinButton *spin_button,
1275
		      gdouble        val)
1276
{
1277 1278
  gdouble inc;
  gdouble tmp;
1279 1280 1281 1282 1283 1284 1285
  
  inc = spin_button->adjustment->step_increment;
  tmp = (val - spin_button->adjustment->lower) / inc;
  if (tmp - floor (tmp) < ceil (tmp) - tmp)
    val = spin_button->adjustment->lower + floor (tmp) * inc;
  else
    val = spin_button->adjustment->lower + ceil (tmp) * inc;
1286 1287 1288 1289 1290

  if (fabs (val - spin_button->adjustment->value) > EPSILON)
    gtk_adjustment_set_value (spin_button->adjustment, val);
  else
    {
1291 1292 1293 1294 1295
      gint return_val = FALSE;
      gtk_signal_emit (GTK_OBJECT (spin_button), spinbutton_signals[OUTPUT],
		       &return_val);
      if (return_val == FALSE)
	gtk_spin_button_default_output (spin_button);
1296
    }
1297 1298 1299
}

static void
Owen Taylor's avatar
Owen Taylor committed
1300
gtk_spin_button_activate (GtkEntry *entry)
1301
{
Owen Taylor's avatar
Owen Taylor committed
1302 1303
  if (entry->editable)
    gtk_spin_button_update (GTK_SPIN_BUTTON (entry));
1304 1305
}

1306
static void
Owen Taylor's avatar
Owen Taylor committed
1307
gtk_spin_button_insert_text (GtkEntry    *entry,
1308 1309 1310 1311
			     const gchar *new_text,
			     gint         new_text_length,
			     gint        *position)
{
Owen Taylor's avatar
Owen Taylor committed
1312 1313
  GtkEditable *editable = GTK_EDITABLE (entry);
  GtkSpinButton *spin = GTK_SPIN_BUTTON (editable);
1314 1315 1316 1317 1318 1319 1320
 
  if (spin->numeric)
    {
      struct lconv *lc;
      gboolean sign;
      gint dotpos = -1;
      gint i;
1321 1322
      GdkWChar pos_sign;
      GdkWChar neg_sign;
1323
      gint entry_length;
1324 1325

      entry_length = entry->text_length;
1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338

      lc = localeconv ();

      if (*(lc->negative_sign))
	neg_sign = *(lc->negative_sign);
      else 
	neg_sign = '-';

      if (*(lc->positive_sign))
	pos_sign = *(lc->positive_sign);
      else 
	pos_sign = '+';

1339 1340 1341 1342 1343 1344 1345
      for (sign=0, i=0; i<entry_length; i++)
	if ((entry->text[i] == neg_sign) ||
	    (entry->text[i] == pos_sign))
	  {
	    sign = 1;
	    break;
	  }
1346 1347 1348 1349

      if (sign && !(*position))
	return;

1350 1351 1352 1353 1354 1355 1356
      for (dotpos=-1, i=0; i<entry_length; i++)
	if (entry->text[i] == *(lc->decimal_point))
	  {
	    dotpos = i;
	    break;
	  }

1357
      if (dotpos > -1 && *position > dotpos &&
1358
	  (gint)spin->digits - entry_length
1359
	    + dotpos - new_text_length + 1 < 0)
1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372
	return;

      for (i = 0; i < new_text_length; i++)
	{
	  if (new_text[i] == neg_sign || new_text[i] == pos_sign)
	    {
	      if (sign || (*position) || i)
		return;
	      sign = TRUE;
	    }
	  else if (new_text[i] == *(lc->decimal_point))
	    {
	      if (!spin->digits || dotpos > -1 || 
1373
 		  (new_text_length - 1 - i + entry_length
1374
		    - *position > (gint)spin->digits)) 
1375 1376 1377 1378 1379 1380 1381 1382
		return;
	      dotpos = *position + i;
	    }
	  else if (new_text[i] < 0x30 || new_text[i] > 0x39)
	    return;
	}
    }

Owen Taylor's avatar
Owen Taylor committed
1383 1384
  GTK_ENTRY_CLASS (parent_class)->insert_text (entry, new_text,
					       new_text_length, position);
1385 1386 1387 1388
}

static void
gtk_spin_button_real_spin (GtkSpinButton *spin_button,
1389
			   gdouble        increment)
1390 1391
{
  GtkAdjustment *adj;
1392
  gdouble new_value = 0.0;
1393

1394 1395 1396 1397
  g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
  
  adj = spin_button->adjustment;

1398 1399 1400
  new_value = adj->value + increment;

  if (increment > 0)
1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411
    {
      if (spin_button->wrap)
	{
	  if (fabs (adj->value - adj->upper) < EPSILON)
	    new_value = adj->lower;
	  else if (new_value > adj->upper)
	    new_value = adj->upper;
	}
      else
	new_value = MIN (new_value, adj->upper);
    }
1412
  else if (increment < 0) 
1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428
    {
      if (spin_button->wrap)
	{
	  if (fabs (adj->value - adj->lower) < EPSILON)
	    new_value = adj->upper;
	  else if (new_value < adj->lower)
	    new_value = adj->lower;
	}
      else
	new_value = MAX (new_value, adj->lower);
    }

  if (fabs (new_value - adj->value) > EPSILON)
    gtk_adjustment_set_value (adj, new_value);
}

1429 1430
static gint
gtk_spin_button_default_input (GtkSpinButton *spin_button,
1431
			       gdouble       *new_val)
1432 1433 1434 1435 1436
{
  gchar *err = NULL;

  *new_val = strtod (gtk_entry_get_text (GTK_ENTRY (spin_button)), &err);
  if (*err)
1437
    return GTK_INPUT_ERROR;
1438 1439 1440 1441 1442 1443 1444
  else
    return FALSE;
}

static gint
gtk_spin_button_default_output (GtkSpinButton *spin_button)
{
1445
  gchar *buf = g_strdup_printf ("%0.*f", spin_button->digits, spin_button->adjustment->value);
1446 1447 1448

  if (strcmp (buf, gtk_entry_get_text (GTK_ENTRY (spin_button))))
    gtk_entry_set_text (GTK_ENTRY (spin_button), buf);
1449
  g_free (buf);
1450 1451 1452
  return FALSE;
}

1453 1454 1455 1456 1457 1458 1459 1460 1461

/***********************************************************
 ***********************************************************
 ***                  Public interface                   ***
 ***********************************************************
 ***********************************************************/


void
1462
gtk_spin_button_configure (GtkSpinButton  *spin_button,
1463
			   GtkAdjustment  *adjustment,
1464
			   gdouble         climb_rate,
1465
			   guint           digits)
1466 1467 1468
{
  g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));

1469 1470 1471 1472
  if (adjustment)
    gtk_spin_button_set_adjustment (spin_button, adjustment);
  else
    adjustment = spin_button->adjustment;
1473

1474
  g_object_freeze_notify (G_OBJECT (spin_button));
1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485
  if (spin_button->digits != digits) 
    {
      spin_button->digits = digits;
      g_object_notify (G_OBJECT (spin_button), "digits");
    }

  if (spin_button->climb_rate != climb_rate)
    {
      spin_button->climb_rate = climb_rate;
      g_object_notify (G_OBJECT (spin_button), "climb_rate");
    }
1486
  g_object_thaw_notify (G_OBJECT (spin_button));
1487

1488
  gtk_adjustment_value_changed (adjustment);
1489 1490 1491 1492
}

GtkWidget *
gtk_spin_button_new (GtkAdjustment *adjustment,
1493
		     gdouble        climb_rate,
1494
		     guint          digits)
1495 1496 1497
{
  GtkSpinButton *spin;

1498 1499
  if (adjustment)
    g_return_val_if_fail (GTK_IS_ADJUSTMENT (adjustment), NULL);
1500

1501
  spin = gtk_type_new (GTK_TYPE_SPIN_BUTTON);
1502

1503
  gtk_spin_button_configure (spin, adjustment, climb_rate, digits);
1504 1505 1506 1507

  return GTK_WIDGET (spin);
}

1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537
/**
 * gtk_spin_button_new_with_range:
 * @min: Minimum allowable value
 * @max: Maximum allowable value
 * @step: Increment added or subtracted by spinning the widget
 * 
 * This is a convenience constructor that allows creation of a numeric 
 * #GtkSpinButton without manually creating an adjustment. The value is 
 * initially set to the minimum value and a page increment of 10 * @step
 * is the default. The precision of the spin button is equivalent to the 
 * precision of @step.
 * 
 * Return value: the newly instantiated spin button
 **/
GtkWidget *
gtk_spin_button_new_with_range (gdouble min,
				gdouble max,
				gdouble step)
{
  GtkObject *adj;
  GtkSpinButton *spin;
  gint digits;

  g_return_val_if_fail (min < max, NULL);
  g_return_val_if_fail (step != 0.0, NULL);

  spin = gtk_type_new (GTK_TYPE_SPIN_BUTTON);

  adj = gtk_adjustment_new (min, min, max, step, 10 * step, step);

1538
  if (fabs (step) >= 1.0 || step == 0.0)
1539 1540 1541
    digits = 0;
  else {
    digits = abs ((gint) floor (log10 (fabs (step))));
Tim Janik's avatar