gtkspinbutton.c 55.1 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 47
#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_TEXT_LENGTH                    256
#define MAX_TIMER_CALLS                    5
48
#define EPSILON                            1e-5
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 138 139 140
{
  static guint spin_button_type = 0;

  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 224 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
  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,
						      5,
						      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_newc ("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 435
}

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

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

static void
gtk_spin_button_unmap (GtkWidget *widget)
{
  g_return_if_fail (widget != NULL);
  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)
{
459
  GtkSpinButton *spin_button;
460 461
  GdkWindowAttr attributes;
  gint attributes_mask;
462
  guint real_width;
463
  gint return_val;
464 465 466

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_SPIN_BUTTON (widget));
467
  
468
  spin_button = GTK_SPIN_BUTTON (widget);
469

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

476 477
  widget->allocation.width = real_width;
  
478 479 480 481 482 483 484 485 486 487 488
  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;

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

500 501 502 503 504 505 506
  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);
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
}

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

  g_return_if_fail (widget != NULL);
  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
523
      gdk_window_set_user_data (spin->panel, NULL);
524 525 526 527 528
      gdk_window_destroy (spin->panel);
      spin->panel = NULL;
    }
}

529
static int
530
compute_double_length (double val, int digits)
531
{
532
  int a;
533 534 535 536
  int extra;

  a = 1;
  if (fabs (val) > 1.0)
537
    a = floor (log10 (fabs (val))) + 1;  
538 539 540 541

  extra = 0;
  
  /* The dot: */
542
  if (digits > 0)
543 544 545 546 547 548
    extra++;

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

549
  return a + digits + extra;
550 551
}

552 553 554 555
static void
gtk_spin_button_size_request (GtkWidget      *widget,
			      GtkRequisition *requisition)
{
556
  GtkEntry *entry;
557
  GtkSpinButton *spin_button;
558
  
559 560 561 562
  g_return_if_fail (widget != NULL);
  g_return_if_fail (requisition != NULL);
  g_return_if_fail (GTK_IS_SPIN_BUTTON (widget));

563
  entry = GTK_ENTRY (widget);
564
  spin_button = GTK_SPIN_BUTTON (widget);
565
  
566 567 568
  GTK_WIDGET_CLASS (parent_class)->size_request (widget, requisition);

  if (entry->width_chars < 0)
569 570 571 572 573 574
    {
      PangoFontMetrics metrics;
      PangoFont *font;
      gchar *lang;
      gint width;
      gint w;
575
      int string_len;
576 577 578 579 580 581 582 583 584 585 586 587


      font = pango_context_load_font (gtk_widget_get_pango_context (widget),
                                      widget->style->font_desc);
      lang = pango_context_get_lang (gtk_widget_get_pango_context (widget));
      pango_font_get_metrics (font, lang, &metrics);
      g_free (lang);
      g_object_unref (G_OBJECT (font));
      
      /* Get max of MIN_SPIN_BUTTON_WIDTH, size of upper, size of lower */
      
      width = MIN_SPIN_BUTTON_WIDTH;
588 589

      string_len = compute_double_length (spin_button->adjustment->upper,
590
                                          spin_button->digits);
591
      w = MIN (string_len, 10) * PANGO_PIXELS (metrics.approximate_digit_width);
592
      width = MAX (width, w);
593 594 595
      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);
596 597 598 599
      width = MAX (width, w);
      
      requisition->width = width + ARROW_SIZE + 2 * widget->style->xthickness;
    }
600 601
  else
    requisition->width += ARROW_SIZE + 2 * widget->style->xthickness;
602 603 604 605 606 607 608 609 610 611 612 613 614
}

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

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

  child_allocation = *allocation;
615 616
  if (child_allocation.width > ARROW_SIZE + 2 * widget->style->xthickness)
    child_allocation.width -= ARROW_SIZE + 2 * widget->style->xthickness;
617

618
  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
619
    child_allocation.x += ARROW_SIZE + 2 * widget->style->xthickness;
620

621 622 623
  GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, &child_allocation);

  widget->allocation = *allocation;
Tim Janik's avatar
Tim Janik committed
624 625 626

  if (GTK_WIDGET_REALIZED (widget))
    {
627
      child_allocation.width = ARROW_SIZE + 2 * widget->style->xthickness;
628 629 630 631
      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 - 
632
			      2 * widget->style->xthickness);
633
      else
634 635
	child_allocation.x = allocation->x;      

636
      child_allocation.y = allocation->y + (allocation->height - widget->requisition.height) / 2;
Tim Janik's avatar
Tim Janik committed
637 638 639 640 641 642 643

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

646 647 648
static gint
gtk_spin_button_expose (GtkWidget      *widget,
			GdkEventExpose *event)
649 650 651
{
  GtkSpinButton *spin;

652 653 654
  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_SPIN_BUTTON (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);
655 656 657 658 659

  spin = GTK_SPIN_BUTTON (widget);

  if (GTK_WIDGET_DRAWABLE (widget))
    {
660 661
      GtkShadowType shadow_type;

662 663 664 665
      /* FIXME this seems like really broken code -
       * why aren't we looking at event->window
       * and acting accordingly?
       */
666 667 668

      shadow_type = spin_button_get_shadow_type (spin);
      if (shadow_type != GTK_SHADOW_NONE)
669
	gtk_paint_box (widget->style, spin->panel,
670
		       GTK_STATE_NORMAL, shadow_type,
671
		       &event->area, widget, "spinbutton",
672
		       0, 0, 
673
		       ARROW_SIZE + 2 * widget->style->xthickness,
674 675 676 677
		       widget->requisition.height); 
      else
	 {
	    gdk_window_set_back_pixmap (spin->panel, NULL, TRUE);
678 679 680
	    gdk_window_clear_area (spin->panel,
                                   event->area.x, event->area.y,
                                   event->area.width, event->area.height);
681 682 683 684
	 }
       gtk_spin_button_draw_arrow (spin, GTK_ARROW_UP);
       gtk_spin_button_draw_arrow (spin, GTK_ARROW_DOWN);

685
       GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
686 687 688 689 690 691 692 693 694
    }

  return FALSE;
}

static void
gtk_spin_button_draw_arrow (GtkSpinButton *spin_button, 
			    guint          arrow)
{
695
  GtkShadowType spin_shadow_type;
696 697 698
  GtkStateType state_type;
  GtkShadowType shadow_type;
  GtkWidget *widget;
699 700
  gint x;
  gint y;
701 702 703 704 705

  g_return_if_fail (spin_button != NULL);
  g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
  
  widget = GTK_WIDGET (spin_button);
706
  spin_shadow_type = spin_button_get_shadow_type (spin_button);
707 708 709

  if (GTK_WIDGET_DRAWABLE (spin_button))
    {
710 711 712 713 714 715 716 717 718 719 720
      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;
	}
721
      else
722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
	{
	  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;
	}
738 739
      if (arrow == GTK_ARROW_UP)
	{
740
	  if (spin_shadow_type != GTK_SHADOW_NONE)
741
	    {
742 743
	      x = widget->style->xthickness;
	      y = widget->style->ythickness;
744 745 746
	    }
	  else
	    {
747 748
	      x = widget->style->xthickness - 1;
	      y = widget->style->ythickness - 1;
749
	    }
750 751 752 753 754
	  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 
755
			   - widget->style->ythickness);
756 757 758
	}
      else
	{
759
	  if (spin_shadow_type != GTK_SHADOW_NONE)
760
	    {
761
	      x = widget->style->xthickness;
762 763 764 765
	      y = widget->requisition.height / 2;
	    }
	  else
	    {
766
	      x = widget->style->xthickness - 1;
767 768
	      y = widget->requisition.height / 2 + 1;
	    }
769 770 771 772 773
	  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 
774
			   - widget->style->ythickness);
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 836 837 838 839 840 841 842 843 844 845 846 847 848 849
	}
    }
}

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

  g_return_val_if_fail (widget != NULL, FALSE);
  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 (widget != NULL, FALSE);
  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 (widget != NULL, FALSE);
  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
850
  if (GTK_ENTRY (widget)->editable)
851
    gtk_spin_button_update (GTK_SPIN_BUTTON (widget));
852

853
  return GTK_WIDGET_CLASS (parent_class)->focus_out_event (widget, event);
854 855
}

856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885
static gint
gtk_spin_button_scroll (GtkWidget      *widget,
			GdkEventScroll *event)
{
  GtkSpinButton *spin;

  g_return_val_if_fail (widget != NULL, FALSE);
  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;
}

886 887 888 889 890 891 892 893 894 895 896 897 898 899
static gint
gtk_spin_button_button_press (GtkWidget      *widget,
			      GdkEventButton *event)
{
  GtkSpinButton *spin;

  g_return_val_if_fail (widget != NULL, FALSE);
  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)
    {
900
      if (event->window == spin->panel)
901 902 903 904 905
	{
	  if (!GTK_WIDGET_HAS_FOCUS (widget))
	    gtk_widget_grab_focus (widget);
	  gtk_grab_add (widget);
	  spin->button = event->button;
906
	  
Owen Taylor's avatar
Owen Taylor committed
907
	  if (GTK_ENTRY (widget)->editable)
908
	    gtk_spin_button_update (spin);
909
	  
910 911 912 913 914
	  if (event->y <= widget->requisition.height / 2)
	    {
	      spin->click_child = GTK_ARROW_UP;
	      if (event->button == 1)
		{
915
		 gtk_spin_button_real_spin (spin, 
916
					    spin->adjustment->step_increment);
917 918 919 920 921 922 923 924 925 926 927
		  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)
		{
928
		 gtk_spin_button_real_spin (spin, 
929
					    spin->adjustment->page_increment);
930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945
		  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)
		{
946 947
		  gtk_spin_button_real_spin (spin,
					     -spin->adjustment->step_increment);
948 949 950 951 952 953 954 955 956 957 958
		  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)
		{
959 960
		  gtk_spin_button_real_spin (spin,
					     -spin->adjustment->page_increment);
961 962 963 964 965 966 967 968 969 970 971
		  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);
	    }
972
	  return TRUE;
973
	}
974
      else
975
	return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event);
976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
    }
  return FALSE;
}

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

  g_return_val_if_fail (widget != NULL, FALSE);
  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 &&
1008
	      event->x <= ARROW_SIZE + 2 * widget->style->xthickness)
1009 1010 1011
	    {
	      if (spin->click_child == GTK_ARROW_UP &&
		  event->y <= widget->requisition.height / 2)
1012
		{
1013
		  gdouble diff;
1014 1015 1016 1017 1018

		  diff = spin->adjustment->upper - spin->adjustment->value;
		  if (diff > EPSILON)
		    gtk_spin_button_real_spin (spin, diff);
		}
1019 1020
	      else if (spin->click_child == GTK_ARROW_DOWN &&
		       event->y > widget->requisition.height / 2)
1021
		{
1022
		  gdouble diff;
1023 1024 1025 1026 1027

		  diff = spin->adjustment->value - spin->adjustment->lower;
		  if (diff > EPSILON)
		    gtk_spin_button_real_spin (spin, -diff);
		}
1028 1029 1030 1031 1032 1033 1034
	    }
	}		  
      gtk_grab_remove (widget);
      click_child = spin->click_child;
      spin->click_child = 2;
      spin->button = 0;
      gtk_spin_button_draw_arrow (spin, click_child);
1035
      return TRUE;
1036 1037
    }
  else
1038
    return GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event);
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 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082

  return FALSE;
}

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

  g_return_val_if_fail (widget != NULL, FALSE);
  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;
    }
	  
1083
  return GTK_WIDGET_CLASS (parent_class)->motion_notify_event (widget, event);
1084 1085 1086 1087 1088
}

static gint
gtk_spin_button_timer (GtkSpinButton *spin_button)
{
1089 1090
  gboolean retval = FALSE;
  
1091
  GDK_THREADS_ENTER ();
1092 1093 1094

  if (spin_button->timer)
    {
1095 1096 1097 1098
      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);
1099 1100 1101 1102 1103 1104 1105 1106

      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);
	}
1107
      else 
1108
	{
1109 1110
	  if (spin_button->climb_rate > 0.0 && spin_button->timer_step 
	      < spin_button->adjustment->page_increment)
1111
	    {
1112 1113 1114 1115 1116 1117 1118
	      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;
		}
1119
	    }
1120
	  retval = TRUE;
1121
	}
1122
    }
1123

1124
  GDK_THREADS_LEAVE ();
1125 1126

  return retval;
1127 1128 1129
}

static void
1130
gtk_spin_button_value_changed (GtkAdjustment *adjustment,
1131 1132
			       GtkSpinButton *spin_button)
{
1133
  gint return_val;
1134

1135 1136
  g_return_if_fail (adjustment != NULL);
  g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
1137

1138 1139 1140 1141 1142
  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);
1143

1144 1145 1146
  gtk_signal_emit (GTK_OBJECT (spin_button), 
		   spinbutton_signals[VALUE_CHANGED]);

1147 1148
  gtk_spin_button_draw_arrow (spin_button, GTK_ARROW_UP);
  gtk_spin_button_draw_arrow (spin_button, GTK_ARROW_DOWN);
1149 1150
  
  g_object_notify (G_OBJECT (spin_button), "value");
1151
}
1152

1153 1154 1155 1156 1157
static gint
gtk_spin_button_key_press (GtkWidget     *widget,
			   GdkEventKey   *event)
{
  GtkSpinButton *spin;
1158
  gint key;
1159
  gboolean key_repeat = FALSE;
1160

1161 1162
  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_SPIN_BUTTON (widget), FALSE);
1163