gtkscale.c 53.3 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
Elliot Lee's avatar
Elliot Lee committed
2
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3
 * Copyright (C) 2001 Red Hat, Inc.
Elliot Lee's avatar
Elliot Lee committed
4 5
 *
 * This library is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU Lesser General Public
Elliot Lee's avatar
Elliot Lee committed
7 8 9 10 11 12
 * 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
13
 * Lesser General Public License for more details.
Elliot Lee's avatar
Elliot Lee committed
14
 *
15
 * You should have received a copy of the GNU Lesser General Public
16 17 18
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
Elliot Lee's avatar
Elliot Lee committed
19
 */
20 21

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

28
#include "config.h"
29

Elliot Lee's avatar
Elliot Lee committed
30
#include <math.h>
31 32
#include <stdlib.h>

33
#include "gtkscaleprivate.h"
34 35
#include "gtkiconfactory.h"
#include "gtkicontheme.h"
36
#include "gtkmarshalers.h"
37
#include "gtkbindings.h"
38
#include "gtkorientable.h"
39
#include "gtktypebuiltins.h"
40
#include "gtkprivate.h"
41
#include "gtkintl.h"
42 43
#include "gtkbuildable.h"
#include "gtkbuilderprivate.h"
44

45 46
#include "a11y/gtkscaleaccessible.h"

47 48 49

/**
 * SECTION:gtkscale
50
 * @Short_description: A slider widget for selecting a value from a range
51 52
 * @Title: GtkScale
 *
53
 * A GtkScale is a slider control used to select a numeric value. 
54
 * To use it, you'll probably want to investigate the methods on
55
 * its base class, #GtkRange, in addition to the methods for GtkScale itself.
56 57
 * To set the value of a scale, you would normally use gtk_range_set_value().
 * To detect changes to the value, you would normally use the
58
 * #GtkRange::value-changed signal.
59
 *
60 61 62 63 64
 * Note that using the same upper and lower bounds for the #GtkScale (through
 * the #GtkRange methods) will hide the slider itself. This is useful for
 * applications that want to show an undeterminate value on the scale, without
 * changing the layout of the application (such as movie or music players).
 *
65 66 67 68 69 70 71 72 73 74 75
 * <refsect2 id="GtkScale-BUILDER-UI"><title>GtkScale as GtkBuildable</title>
 * GtkScale supports a custom &lt;marks&gt; element, which
 * can contain multiple &lt;mark&gt; elements. The "value" and "position"
 * attributes have the same meaning as gtk_scale_add_mark() parameters of the
 * same name. If the element is not empty, its content is taken as the markup
 * to show at the mark. It can be translated with the usual "translatable and
 * "context" attributes.
 * </refsect2>
 */


76 77 78 79 80 81
#define	MAX_DIGITS	(64)	/* don't change this,
				 * a) you don't need to and
				 * b) you might cause buffer owerflows in
				 *    unrelated code portions otherwise
				 */

82 83
typedef struct _GtkScaleMark GtkScaleMark;

84
struct _GtkScalePrivate
85 86 87 88 89 90 91 92 93 94 95
{
  PangoLayout  *layout;

  GSList       *marks;

  gint          digits;

  guint         draw_value : 1;
  guint         value_pos  : 2;
};

96 97 98
struct _GtkScaleMark
{
  gdouble          value;
99
  gchar           *markup;
100
  GtkPositionType  position; /* always GTK_POS_TOP or GTK_POS_BOTTOM */
101 102
};

103
enum {
104 105 106 107
  PROP_0,
  PROP_DIGITS,
  PROP_DRAW_VALUE,
  PROP_VALUE_POS
108 109
};

Havoc Pennington's avatar
Havoc Pennington committed
110 111 112 113 114 115
enum {
  FORMAT_VALUE,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];
Elliot Lee's avatar
Elliot Lee committed
116

117 118 119 120 121 122 123 124
static void     gtk_scale_set_property            (GObject        *object,
                                                   guint           prop_id,
                                                   const GValue   *value,
                                                   GParamSpec     *pspec);
static void     gtk_scale_get_property            (GObject        *object,
                                                   guint           prop_id,
                                                   GValue         *value,
                                                   GParamSpec     *pspec);
125 126 127 128 129 130
static void     gtk_scale_get_preferred_width     (GtkWidget      *widget,
                                                   gint           *minimum,
                                                   gint           *natural);
static void     gtk_scale_get_preferred_height    (GtkWidget      *widget,
                                                   gint           *minimum,
                                                   gint           *natural);
131
static void     gtk_scale_style_updated           (GtkWidget      *widget);
132 133
static void     gtk_scale_get_range_border        (GtkRange       *range,
                                                   GtkBorder      *border);
134 135 136 137 138 139 140 141
static void     gtk_scale_get_mark_label_size     (GtkScale        *scale,
                                                   GtkPositionType  position,
                                                   gint            *count1,
                                                   gint            *width1,
                                                   gint            *height1,
                                                   gint            *count2,
                                                   gint            *width2,
                                                   gint            *height2);
142 143 144
static void     gtk_scale_finalize                (GObject        *object);
static void     gtk_scale_screen_changed          (GtkWidget      *widget,
                                                   GdkScreen      *old_screen);
145 146
static gboolean gtk_scale_draw                    (GtkWidget      *widget,
                                                   cairo_t        *cr);
147 148 149
static void     gtk_scale_real_get_layout_offsets (GtkScale       *scale,
                                                   gint           *x,
                                                   gint           *y);
150 151 152 153 154 155 156 157 158 159 160 161 162 163
static void     gtk_scale_buildable_interface_init   (GtkBuildableIface *iface);
static gboolean gtk_scale_buildable_custom_tag_start (GtkBuildable  *buildable,
                                                      GtkBuilder    *builder,
                                                      GObject       *child,
                                                      const gchar   *tagname,
                                                      GMarkupParser *parser,
                                                      gpointer      *data);
static void     gtk_scale_buildable_custom_finished  (GtkBuildable  *buildable,
                                                      GtkBuilder    *builder,
                                                      GObject       *child,
                                                      const gchar   *tagname,
                                                      gpointer       user_data);


164 165 166
G_DEFINE_TYPE_WITH_CODE (GtkScale, gtk_scale, GTK_TYPE_RANGE,
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
                                                gtk_scale_buildable_interface_init))
167

Elliot Lee's avatar
Elliot Lee committed
168

169 170
#define add_slider_binding(binding_set, keyval, mask, scroll)              \
  gtk_binding_entry_add_signal (binding_set, keyval, mask,                 \
171
                                I_("move-slider"), 1, \
172 173
                                GTK_TYPE_SCROLL_TYPE, scroll)

Elliot Lee's avatar
Elliot Lee committed
174 175 176
static void
gtk_scale_class_init (GtkScaleClass *class)
{
177
  GObjectClass   *gobject_class;
178
  GtkWidgetClass *widget_class;
Manish Singh's avatar
Manish Singh committed
179 180
  GtkRangeClass  *range_class;
  GtkBindingSet  *binding_set;
181
  
182
  gobject_class = G_OBJECT_CLASS (class);
Elliot Lee's avatar
Elliot Lee committed
183
  range_class = (GtkRangeClass*) class;
184
  widget_class = (GtkWidgetClass*) class;
185
  
186 187
  gobject_class->set_property = gtk_scale_set_property;
  gobject_class->get_property = gtk_scale_get_property;
188
  gobject_class->finalize = gtk_scale_finalize;
189

190
  widget_class->style_updated = gtk_scale_style_updated;
191
  widget_class->screen_changed = gtk_scale_screen_changed;
192
  widget_class->draw = gtk_scale_draw;
193 194
  widget_class->get_preferred_width = gtk_scale_get_preferred_width;
  widget_class->get_preferred_height = gtk_scale_get_preferred_height;
195

196
  range_class->slider_detail = "Xscale";
197
  range_class->get_range_border = gtk_scale_get_range_border;
198 199 200

  class->get_layout_offsets = gtk_scale_real_get_layout_offsets;

Matthias Clasen's avatar
Matthias Clasen committed
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
  /**
   * GtkScale::format-value:
   * @scale: the object which received the signal
   * @value: the value to format
   *
   * Signal which allows you to change how the scale value is displayed.
   * Connect a signal handler which returns an allocated string representing 
   * @value. That string will then be used to display the scale's value.
   *
   * Here's an example signal handler which displays a value 1.0 as
   * with "--&gt;1.0&lt;--".
   * |[
   * static gchar*
   * format_value_callback (GtkScale *scale,
   *                        gdouble   value)
   * {
   *   return g_strdup_printf ("--&gt;&percnt;0.*g&lt;--",
   *                           gtk_scale_get_digits (scale), value);
   *  }
   * ]|
   *
   * Return value: allocated string representing @value
   */
224
  signals[FORMAT_VALUE] =
225
    g_signal_new (I_("format-value"),
Manish Singh's avatar
Manish Singh committed
226
                  G_TYPE_FROM_CLASS (gobject_class),
227 228
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkScaleClass, format_value),
229
                  _gtk_single_string_accumulator, NULL,
230
                  _gtk_marshal_STRING__DOUBLE,
231 232
                  G_TYPE_STRING, 1,
                  G_TYPE_DOUBLE);
233

234 235 236
  g_object_class_install_property (gobject_class,
                                   PROP_DIGITS,
                                   g_param_spec_int ("digits",
237 238
						     P_("Digits"),
						     P_("The number of decimal places that are displayed in the value"),
239 240 241
						     -1,
						     MAX_DIGITS,
						     1,
242
						     GTK_PARAM_READWRITE));
243 244 245
  
  g_object_class_install_property (gobject_class,
                                   PROP_DRAW_VALUE,
246
                                   g_param_spec_boolean ("draw-value",
247 248
							 P_("Draw Value"),
							 P_("Whether the current value is displayed as a string next to the slider"),
249
							 TRUE,
250
							 GTK_PARAM_READWRITE));
251 252 253
  
  g_object_class_install_property (gobject_class,
                                   PROP_VALUE_POS,
254
                                   g_param_spec_enum ("value-pos",
255 256
						      P_("Value Position"),
						      P_("The position in which the current value is displayed"),
257
						      GTK_TYPE_POSITION_TYPE,
258
						      GTK_POS_TOP,
259
						      GTK_PARAM_READWRITE));
260

261
  gtk_widget_class_install_style_property (widget_class,
262
					   g_param_spec_int ("slider-length",
263 264
							     P_("Slider Length"),
							     P_("Length of scale's slider"),
265 266 267
							     0,
							     G_MAXINT,
							     31,
268
							     GTK_PARAM_READABLE));
269 270

  gtk_widget_class_install_style_property (widget_class,
271
					   g_param_spec_int ("value-spacing",
272 273
							     P_("Value spacing"),
							     P_("Space between value text and the slider/trough area"),
274 275 276
							     0,
							     G_MAXINT,
							     2,
277
							     GTK_PARAM_READABLE));
278 279 280 281 282 283 284
  
  /* All bindings (even arrow keys) are on both h/v scale, because
   * blind users etc. don't care about scale orientation.
   */
  
  binding_set = gtk_binding_set_by_class (class);

285
  add_slider_binding (binding_set, GDK_KEY_Left, 0,
286 287
                      GTK_SCROLL_STEP_LEFT);

288
  add_slider_binding (binding_set, GDK_KEY_Left, GDK_CONTROL_MASK,
289 290
                      GTK_SCROLL_PAGE_LEFT);

291
  add_slider_binding (binding_set, GDK_KEY_KP_Left, 0,
292 293
                      GTK_SCROLL_STEP_LEFT);

294
  add_slider_binding (binding_set, GDK_KEY_KP_Left, GDK_CONTROL_MASK,
295 296
                      GTK_SCROLL_PAGE_LEFT);

297
  add_slider_binding (binding_set, GDK_KEY_Right, 0,
298 299
                      GTK_SCROLL_STEP_RIGHT);

300
  add_slider_binding (binding_set, GDK_KEY_Right, GDK_CONTROL_MASK,
301 302
                      GTK_SCROLL_PAGE_RIGHT);

303
  add_slider_binding (binding_set, GDK_KEY_KP_Right, 0,
304 305
                      GTK_SCROLL_STEP_RIGHT);

306
  add_slider_binding (binding_set, GDK_KEY_KP_Right, GDK_CONTROL_MASK,
307 308
                      GTK_SCROLL_PAGE_RIGHT);

309
  add_slider_binding (binding_set, GDK_KEY_Up, 0,
310 311
                      GTK_SCROLL_STEP_UP);

312
  add_slider_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK,
313 314
                      GTK_SCROLL_PAGE_UP);

315
  add_slider_binding (binding_set, GDK_KEY_KP_Up, 0,
316 317
                      GTK_SCROLL_STEP_UP);

318
  add_slider_binding (binding_set, GDK_KEY_KP_Up, GDK_CONTROL_MASK,
319 320
                      GTK_SCROLL_PAGE_UP);

321
  add_slider_binding (binding_set, GDK_KEY_Down, 0,
322 323
                      GTK_SCROLL_STEP_DOWN);

324
  add_slider_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK,
325 326
                      GTK_SCROLL_PAGE_DOWN);

327
  add_slider_binding (binding_set, GDK_KEY_KP_Down, 0,
328 329
                      GTK_SCROLL_STEP_DOWN);

330
  add_slider_binding (binding_set, GDK_KEY_KP_Down, GDK_CONTROL_MASK,
331 332
                      GTK_SCROLL_PAGE_DOWN);
   
333
  add_slider_binding (binding_set, GDK_KEY_Page_Up, GDK_CONTROL_MASK,
334
                      GTK_SCROLL_PAGE_LEFT);
335

336
  add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, GDK_CONTROL_MASK,
337 338
                      GTK_SCROLL_PAGE_LEFT);  

339
  add_slider_binding (binding_set, GDK_KEY_Page_Up, 0,
340
                      GTK_SCROLL_PAGE_UP);
341

342
  add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, 0,
343 344
                      GTK_SCROLL_PAGE_UP);
  
345
  add_slider_binding (binding_set, GDK_KEY_Page_Down, GDK_CONTROL_MASK,
346
                      GTK_SCROLL_PAGE_RIGHT);
347

348
  add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, GDK_CONTROL_MASK,
349 350
                      GTK_SCROLL_PAGE_RIGHT);

351
  add_slider_binding (binding_set, GDK_KEY_Page_Down, 0,
352
                      GTK_SCROLL_PAGE_DOWN);
353

354
  add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, 0,
355 356 357 358
                      GTK_SCROLL_PAGE_DOWN);

  /* Logical bindings (vs. visual bindings above) */

359
  add_slider_binding (binding_set, GDK_KEY_plus, 0,
360 361
                      GTK_SCROLL_STEP_FORWARD);  

362
  add_slider_binding (binding_set, GDK_KEY_minus, 0,
363 364
                      GTK_SCROLL_STEP_BACKWARD);  

365
  add_slider_binding (binding_set, GDK_KEY_plus, GDK_CONTROL_MASK,
366 367
                      GTK_SCROLL_PAGE_FORWARD);  

368
  add_slider_binding (binding_set, GDK_KEY_minus, GDK_CONTROL_MASK,
369 370 371
                      GTK_SCROLL_PAGE_BACKWARD);


372
  add_slider_binding (binding_set, GDK_KEY_KP_Add, 0,
373 374
                      GTK_SCROLL_STEP_FORWARD);  

375
  add_slider_binding (binding_set, GDK_KEY_KP_Subtract, 0,
376 377
                      GTK_SCROLL_STEP_BACKWARD);  

378
  add_slider_binding (binding_set, GDK_KEY_KP_Add, GDK_CONTROL_MASK,
379 380
                      GTK_SCROLL_PAGE_FORWARD);  

381
  add_slider_binding (binding_set, GDK_KEY_KP_Subtract, GDK_CONTROL_MASK,
382 383 384
                      GTK_SCROLL_PAGE_BACKWARD);
  
  
385
  add_slider_binding (binding_set, GDK_KEY_Home, 0,
386 387
                      GTK_SCROLL_START);

388
  add_slider_binding (binding_set, GDK_KEY_KP_Home, 0,
389 390
                      GTK_SCROLL_START);

391
  add_slider_binding (binding_set, GDK_KEY_End, 0,
392 393
                      GTK_SCROLL_END);

394
  add_slider_binding (binding_set, GDK_KEY_KP_End, 0,
395
                      GTK_SCROLL_END);
396

397
  g_type_class_add_private (gobject_class, sizeof (GtkScalePrivate));
398 399

  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SCALE_ACCESSIBLE);
Elliot Lee's avatar
Elliot Lee committed
400 401
}

402 403 404 405
static void
gtk_scale_orientation_notify (GtkRange         *range,
                              const GParamSpec *pspec)
{
406 407 408 409 410
  GtkOrientation orientation;

  orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (range));
  gtk_range_set_flippable (range,
                           orientation == GTK_ORIENTATION_HORIZONTAL);
411 412 413 414 415
}

static void
gtk_scale_init (GtkScale *scale)
{
416
  GtkScalePrivate *priv;
417
  GtkRange *range = GTK_RANGE (scale);
418
  GtkStyleContext *context;
419

420 421
  scale->priv = G_TYPE_INSTANCE_GET_PRIVATE (scale,
                                             GTK_TYPE_SCALE,
422
                                             GtkScalePrivate);
423 424
  priv = scale->priv;

425
  gtk_widget_set_can_focus (GTK_WIDGET (scale), TRUE);
426

427
  gtk_range_set_slider_size_fixed (range, TRUE);
428

429 430 431
  priv->draw_value = TRUE;
  priv->value_pos = GTK_POS_TOP;
  priv->digits = 1;
432
  gtk_range_set_round_digits (range, priv->digits);
433 434 435 436 437

  gtk_scale_orientation_notify (range, NULL);
  g_signal_connect (scale, "notify::orientation",
                    G_CALLBACK (gtk_scale_orientation_notify),
                    NULL);
438 439 440

  context = gtk_widget_get_style_context (GTK_WIDGET (scale));
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE);
441 442
}

443
static void
444 445 446 447
gtk_scale_set_property (GObject      *object,
			guint         prop_id,
			const GValue *value,
			GParamSpec   *pspec)
448 449 450 451 452
{
  GtkScale *scale;

  scale = GTK_SCALE (object);

453
  switch (prop_id)
454
    {
455 456
    case PROP_DIGITS:
      gtk_scale_set_digits (scale, g_value_get_int (value));
457
      break;
458 459
    case PROP_DRAW_VALUE:
      gtk_scale_set_draw_value (scale, g_value_get_boolean (value));
460
      break;
461 462
    case PROP_VALUE_POS:
      gtk_scale_set_value_pos (scale, g_value_get_enum (value));
463 464
      break;
    default:
465
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
466 467 468 469 470
      break;
    }
}

static void
471 472 473 474
gtk_scale_get_property (GObject      *object,
			guint         prop_id,
			GValue       *value,
			GParamSpec   *pspec)
475
{
476
  GtkScale *scale = GTK_SCALE (object);
477
  GtkScalePrivate *priv = scale->priv;
478

479
  switch (prop_id)
480
    {
481
    case PROP_DIGITS:
482
      g_value_set_int (value, priv->digits);
483
      break;
484
    case PROP_DRAW_VALUE:
485
      g_value_set_boolean (value, priv->draw_value);
486
      break;
487
    case PROP_VALUE_POS:
488
      g_value_set_enum (value, priv->value_pos);
489 490
      break;
    default:
491
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
492 493 494 495
      break;
    }
}

496 497 498
/**
 * gtk_scale_new:
 * @orientation: the scale's orientation.
499 500
 * @adjustment: (allow-none): the #GtkAdjustment which sets the range
 *              of the scale, or %NULL to create a new adjustment.
501 502 503 504 505
 *
 * Creates a new #GtkScale.
 *
 * Return value: a new #GtkScale
 *
506
 * Since: 3.0
507 508 509 510
 **/
GtkWidget *
gtk_scale_new (GtkOrientation  orientation,
               GtkAdjustment  *adjustment)
511
{
512 513
  g_return_val_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment),
                        NULL);
514

515 516 517 518 519
  return g_object_new (GTK_TYPE_SCALE,
                       "orientation", orientation,
                       "adjustment",  adjustment,
                       NULL);
}
520

521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
/**
 * gtk_scale_new_with_range:
 * @orientation: the scale's orientation.
 * @min: minimum value
 * @max: maximum value
 * @step: step increment (tick size) used with keyboard shortcuts
 *
 * Creates a new scale widget with the given orientation that lets the
 * user input a number between @min and @max (including @min and @max)
 * with the increment @step.  @step must be nonzero; it's the distance
 * the slider moves when using the arrow keys to adjust the scale
 * value.
 *
 * Note that the way in which the precision is derived works best if @step
 * is a power of ten. If the resulting precision is not suitable for your
 * needs, use gtk_scale_set_digits() to correct it.
 *
 * Return value: a new #GtkScale
 *
540 541
 * Since: 3.0
 */
542 543 544 545 546 547
GtkWidget *
gtk_scale_new_with_range (GtkOrientation orientation,
                          gdouble        min,
                          gdouble        max,
                          gdouble        step)
{
548
  GtkAdjustment *adj;
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571
  gint digits;

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

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

  if (fabs (step) >= 1.0 || step == 0.0)
    {
      digits = 0;
    }
  else
    {
      digits = abs ((gint) floor (log10 (fabs (step))));
      if (digits > 5)
        digits = 5;
    }

  return g_object_new (GTK_TYPE_SCALE,
                       "orientation", orientation,
                       "adjustment",  adj,
                       "digits",      digits,
                       NULL);
572 573
}

Matthias Clasen's avatar
Matthias Clasen committed
574 575 576
/**
 * gtk_scale_set_digits:
 * @scale: a #GtkScale
577
 * @digits: the number of decimal places to display,
Matthias Clasen's avatar
Matthias Clasen committed
578
 *     e.g. use 1 to display 1.0, 2 to display 1.00, etc
579
 *
Matthias Clasen's avatar
Matthias Clasen committed
580 581 582 583
 * Sets the number of decimal places that are displayed in the value.
 * Also causes the value of the adjustment to be rounded off to this
 * number of digits, so the retrieved value matches the value the user saw.
 */
Elliot Lee's avatar
Elliot Lee committed
584 585 586 587
void
gtk_scale_set_digits (GtkScale *scale,
		      gint      digits)
{
588
  GtkScalePrivate *priv;
589
  GtkRange *range;
590

Elliot Lee's avatar
Elliot Lee committed
591 592
  g_return_if_fail (GTK_IS_SCALE (scale));

593
  priv = scale->priv;
594 595
  range = GTK_RANGE (scale);
  
596
  digits = CLAMP (digits, -1, MAX_DIGITS);
597

598
  if (priv->digits != digits)
Elliot Lee's avatar
Elliot Lee committed
599
    {
600 601
      priv->digits = digits;
      if (priv->draw_value)
602
        gtk_range_set_round_digits (range, digits);
603

604
      _gtk_scale_clear_layout (scale);
605
      gtk_widget_queue_resize (GTK_WIDGET (scale));
606 607

      g_object_notify (G_OBJECT (scale), "digits");
Elliot Lee's avatar
Elliot Lee committed
608 609 610
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
611 612 613 614 615 616 617 618
/**
 * gtk_scale_get_digits:
 * @scale: a #GtkScale
 *
 * Gets the number of decimal places that are displayed in the value.
 *
 * Returns: the number of decimal places that are displayed
 */
619 620 621 622 623
gint
gtk_scale_get_digits (GtkScale *scale)
{
  g_return_val_if_fail (GTK_IS_SCALE (scale), -1);

624
  return scale->priv->digits;
625 626
}

Matthias Clasen's avatar
Matthias Clasen committed
627 628 629
/**
 * gtk_scale_set_draw_value:
 * @scale: a #GtkScale
Matthias Clasen's avatar
Matthias Clasen committed
630
 * @draw_value: %TRUE to draw the value
Matthias Clasen's avatar
Matthias Clasen committed
631 632 633 634
 * 
 * Specifies whether the current value is displayed as a string next 
 * to the slider.
 */
Elliot Lee's avatar
Elliot Lee committed
635 636
void
gtk_scale_set_draw_value (GtkScale *scale,
637
			  gboolean  draw_value)
Elliot Lee's avatar
Elliot Lee committed
638
{
639
  GtkScalePrivate *priv;
640

Elliot Lee's avatar
Elliot Lee committed
641 642
  g_return_if_fail (GTK_IS_SCALE (scale));

643 644
  priv = scale->priv;

645 646
  draw_value = draw_value != FALSE;

647
  if (priv->draw_value != draw_value)
Elliot Lee's avatar
Elliot Lee committed
648
    {
649
      priv->draw_value = draw_value;
650
      if (draw_value)
651
        gtk_range_set_round_digits (GTK_RANGE (scale), priv->digits);
652
      else
653
        gtk_range_set_round_digits (GTK_RANGE (scale), -1);
Elliot Lee's avatar
Elliot Lee committed
654

655 656
      _gtk_scale_clear_layout (scale);

657
      gtk_widget_queue_resize (GTK_WIDGET (scale));
658

659
      g_object_notify (G_OBJECT (scale), "draw-value");
Elliot Lee's avatar
Elliot Lee committed
660 661 662
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
663 664 665 666 667 668 669 670 671
/**
 * gtk_scale_get_draw_value:
 * @scale: a #GtkScale
 *
 * Returns whether the current value is displayed as a string 
 * next to the slider.
 *
 * Returns: whether the current value is displayed as a string
 */
672 673 674 675 676
gboolean
gtk_scale_get_draw_value (GtkScale *scale)
{
  g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE);

677
  return scale->priv->draw_value;
678 679
}

Matthias Clasen's avatar
Matthias Clasen committed
680 681 682 683 684 685 686
/**
 * gtk_scale_set_value_pos:
 * @scale: a #GtkScale
 * @pos: the position in which the current value is displayed
 * 
 * Sets the position in which the current value is displayed.
 */
Elliot Lee's avatar
Elliot Lee committed
687 688 689 690
void
gtk_scale_set_value_pos (GtkScale        *scale,
			 GtkPositionType  pos)
{
691
  GtkScalePrivate *priv;
692 693
  GtkWidget *widget;

Elliot Lee's avatar
Elliot Lee committed
694 695
  g_return_if_fail (GTK_IS_SCALE (scale));

696 697 698
  priv = scale->priv;

  if (priv->value_pos != pos)
Elliot Lee's avatar
Elliot Lee committed
699
    {
700
      priv->value_pos = pos;
701
      widget = GTK_WIDGET (scale);
Elliot Lee's avatar
Elliot Lee committed
702

703
      _gtk_scale_clear_layout (scale);
704
      if (gtk_widget_get_visible (widget) && gtk_widget_get_mapped (widget))
705
	gtk_widget_queue_resize (widget);
706

707
      g_object_notify (G_OBJECT (scale), "value-pos");
Elliot Lee's avatar
Elliot Lee committed
708 709 710
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
711 712 713 714 715 716 717 718
/**
 * gtk_scale_get_value_pos:
 * @scale: a #GtkScale
 *
 * Gets the position in which the current value is displayed.
 *
 * Returns: the position in which the current value is displayed
 */
719 720 721 722 723
GtkPositionType
gtk_scale_get_value_pos (GtkScale *scale)
{
  g_return_val_if_fail (GTK_IS_SCALE (scale), 0);

724
  return scale->priv->value_pos;
725 726 727 728 729 730
}

static void
gtk_scale_get_range_border (GtkRange  *range,
                            GtkBorder *border)
{
731
  GtkScalePrivate *priv;
732 733 734 735 736 737
  GtkWidget *widget;
  GtkScale *scale;
  gint w, h;
  
  widget = GTK_WIDGET (range);
  scale = GTK_SCALE (range);
738
  priv = scale->priv;
739 740 741 742 743 744 745 746

  _gtk_scale_get_value_size (scale, &w, &h);

  border->left = 0;
  border->right = 0;
  border->top = 0;
  border->bottom = 0;

747
  if (priv->draw_value)
748 749
    {
      gint value_spacing;
750
      gtk_widget_style_get (widget, "value-spacing", &value_spacing, NULL);
751

752
      switch (priv->value_pos)
753 754 755 756 757 758 759 760 761 762 763 764 765 766 767
        {
        case GTK_POS_LEFT:
          border->left += w + value_spacing;
          break;
        case GTK_POS_RIGHT:
          border->right += w + value_spacing;
          break;
        case GTK_POS_TOP:
          border->top += h + value_spacing;
          break;
        case GTK_POS_BOTTOM:
          border->bottom += h + value_spacing;
          break;
        }
    }
768 769 770 771 772 773 774 775 776 777 778 779 780

  if (priv->marks)
    {
      gint slider_width;
      gint value_spacing;
      gint n1, w1, h1, n2, w2, h2;
  
      gtk_widget_style_get (widget, 
                            "slider-width", &slider_width,
                            "value-spacing", &value_spacing, 
                            NULL);


781 782
      gtk_scale_get_mark_label_size (scale, GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);

783
      if (gtk_orientable_get_orientation (GTK_ORIENTABLE (scale)) == GTK_ORIENTATION_HORIZONTAL)
784 785 786 787
        {
          if (n1 > 0)
            border->top += h1 + value_spacing + slider_width / 2;
          if (n2 > 0)
788
            border->bottom += h2 + value_spacing + slider_width / 2;
789 790 791 792 793 794 795 796 797
        }
      else
        {
          if (n1 > 0)
            border->left += w1 + value_spacing + slider_width / 2;
          if (n2 > 0)
            border->right += w2 + value_spacing + slider_width / 2;
        }
    }
798 799 800
}

/* FIXME this could actually be static at the moment. */
801
void
802 803 804
_gtk_scale_get_value_size (GtkScale *scale,
                           gint     *width,
                           gint     *height)
Elliot Lee's avatar
Elliot Lee committed
805
{
806
  GtkScalePrivate *priv = scale->priv;
Elliot Lee's avatar
Elliot Lee committed
807 808
  GtkRange *range;

809
  if (priv->draw_value)
Elliot Lee's avatar
Elliot Lee committed
810
    {
811
      GtkAdjustment *adjustment;
812 813
      PangoLayout *layout;
      PangoRectangle logical_rect;
Havoc Pennington's avatar
Havoc Pennington committed
814
      gchar *txt;
815
      
Elliot Lee's avatar
Elliot Lee committed
816 817
      range = GTK_RANGE (scale);

818
      layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
819
      adjustment = gtk_range_get_adjustment (range);
820

821
      txt = _gtk_scale_format_value (scale, gtk_adjustment_get_lower (adjustment));
Havoc Pennington's avatar
Havoc Pennington committed
822 823 824
      pango_layout_set_text (layout, txt, -1);
      g_free (txt);
      
825
      pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
Elliot Lee's avatar
Elliot Lee committed
826

827
      if (width)
828
	*width = logical_rect.width;
829
      if (height)
Havoc Pennington's avatar
Havoc Pennington committed
830 831
	*height = logical_rect.height;

832
      txt = _gtk_scale_format_value (scale, gtk_adjustment_get_upper (adjustment));
Havoc Pennington's avatar
Havoc Pennington committed
833 834
      pango_layout_set_text (layout, txt, -1);
      g_free (txt);
835
      
836
      pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
837 838

      if (width)
839
	*width = MAX (*width, logical_rect.width);
840
      if (height)
841
	*height = MAX (*height, logical_rect.height);
842

Manish Singh's avatar
Manish Singh committed
843
      g_object_unref (layout);
Elliot Lee's avatar
Elliot Lee committed
844
    }
845 846 847 848 849 850 851 852 853 854
  else
    {
      if (width)
	*width = 0;
      if (height)
	*height = 0;
    }

}

855 856 857 858 859 860 861 862 863 864
static void
gtk_scale_get_mark_label_size (GtkScale        *scale,
                               GtkPositionType  position,
                               gint            *count1,
                               gint            *width1,
                               gint            *height1,
                               gint            *count2,
                               gint            *width2,
                               gint            *height2)
{
865
  GtkScalePrivate *priv = scale->priv;
866 867 868 869 870 871 872 873 874 875 876 877 878 879 880
  PangoLayout *layout;
  PangoRectangle logical_rect;
  GSList *m;
  gint w, h;

  *count1 = *count2 = 0;
  *width1 = *width2 = 0;
  *height1 = *height2 = 0;

  layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);

  for (m = priv->marks; m; m = m->next)
    {
      GtkScaleMark *mark = m->data;

881
      if (mark->markup && *mark->markup)
882 883 884 885
        {
          pango_layout_set_markup (layout, mark->markup, -1);
          pango_layout_get_pixel_extents (layout, NULL, &logical_rect);

886 887
          w = logical_rect.width;
          h = logical_rect.height;
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911
        }
      else
        {
          w = 0;
          h = 0;
        }

      if (mark->position == position)
        {
          (*count1)++;
          *width1 = MAX (*width1, w);
          *height1 = MAX (*height1, h);
        }
      else
        {
          (*count2)++;
          *width2 = MAX (*width2, w);
          *height2 = MAX (*height2, h);
        }
    }

  g_object_unref (layout);
}

912
static void
913
gtk_scale_style_updated (GtkWidget *widget)
914
{
915 916
  gint slider_length;
  GtkRange *range;
917

918
  range = GTK_RANGE (widget);
919
  
920
  gtk_widget_style_get (widget,
921
                        "slider-length", &slider_length,
922
                        NULL);
923 924 925

  gtk_range_set_min_slider_size (range, slider_length);

926 927
  _gtk_scale_clear_layout (GTK_SCALE (widget));

928
  GTK_WIDGET_CLASS (gtk_scale_parent_class)->style_updated (widget);
Elliot Lee's avatar
Elliot Lee committed
929 930
}

931 932 933 934 935 936
static void
gtk_scale_screen_changed (GtkWidget *widget,
                          GdkScreen *old_screen)
{
  _gtk_scale_clear_layout (GTK_SCALE (widget));
}
Elliot Lee's avatar
Elliot Lee committed
937

938
static void
939 940 941
gtk_scale_get_preferred_width (GtkWidget *widget,
                               gint      *minimum,
                               gint      *natural)
942
{
943
  GTK_WIDGET_CLASS (gtk_scale_parent_class)->get_preferred_width (widget, minimum, natural);
944
  
945 946 947 948 949
  if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
    {
      gint n1, w1, h1, n2, w2, h2;
      gint slider_length;
      gint w;
950

951
      gtk_widget_style_get (widget, "slider-length", &slider_length, NULL);
952 953 954 955 956

      gtk_scale_get_mark_label_size (GTK_SCALE (widget), GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);

      w1 = (n1 - 1) * w1 + MAX (w1, slider_length);
      w2 = (n2 - 1) * w2 + MAX (w2, slider_length);
957 958 959 960
      w = MAX (w1, w2);

      *minimum = MAX (*minimum, w);
      *natural = MAX (*natural, w);
961
    }
962 963 964 965 966 967 968 969 970 971 972
}

static void
gtk_scale_get_preferred_height (GtkWidget *widget,
                                gint      *minimum,
                                gint      *natural)
{
  GTK_WIDGET_CLASS (gtk_scale_parent_class)->get_preferred_height (widget, minimum, natural);


  if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_VERTICAL)
973
    {
974 975 976 977 978 979
      gint n1, w1, h1, n2, w2, h2;
      gint slider_length;
      gint h;

      gtk_widget_style_get (widget, "slider-length", &slider_length, NULL);

980
      gtk_scale_get_mark_label_size (GTK_SCALE (widget), GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
981 982
      h1 = (n1 - 1) * h1 + MAX (h1, slider_length);
      h2 = (n2 - 1) * h1 + MAX (h2, slider_length);
983 984 985 986
      h = MAX (h1, h2);

      *minimum = MAX (*minimum, h);
      *natural = MAX (*natural, h);
987 988 989
    }
}

990 991 992 993 994 995 996
static gint
find_next_pos (GtkWidget      *widget,
               GSList          *list,
               gint            *marks,
               GtkPositionType  pos,
               gint             match)
{
997
  GtkAllocation allocation;
998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008
  GSList *m;
  gint i;

  for (m = list->next, i = 1; m; m = m->next, i++)
    {
      GtkScaleMark *mark = m->data;

      if (match == (mark->position == pos))
        return marks[i];
    }

1009
  gtk_widget_get_allocation (widget, &allocation);
1010
  if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
1011
    return allocation.width;
1012
  else
1013
    return allocation.height;
1014 1015
}

1016
static gboolean
1017 1018
gtk_scale_draw (GtkWidget *widget,
                cairo_t   *cr)
1019 1020
{
  GtkScale *scale = GTK_SCALE (widget);
1021
  GtkScalePrivate *priv = scale->priv;
1022
  GtkRange *range = GTK_RANGE (scale);
1023 1024
  GtkStateFlags state = 0;
  GtkStyleContext *context;
1025 1026 1027 1028
  gint *marks;
  gint focus_padding;
  gint slider_width;
  gint value_spacing;
1029
  gint min_sep = 4;
1030

1031
  context = gtk_widget_get_style_context (widget);
1032 1033
  gtk_widget_style_get (widget,
                        "focus-padding", &focus_padding,
1034 1035
                        "slider-width", &slider_width,
                        "value-spacing", &value_spacing,
1036
                        NULL);
1037 1038 1039 1040

  /* We need to chain up _first_ so the various geometry members of
   * GtkRange struct are updated.
   */
1041
  GTK_WIDGET_CLASS (gtk_scale_parent_class)->draw (widget, cr);
1042

1043
  if (!gtk_widget_is_sensitive (widget))
1044
    state |= GTK_STATE_FLAG_INSENSITIVE;
1045 1046 1047

  if (priv->marks)
    {
1048 1049
      GtkOrientation orientation;
      GdkRectangle range_rect;
1050 1051 1052 1053 1054
      gint i;
      gint x1, x2, x3, y1, y2, y3;
      PangoLayout *layout;
      PangoRectangle logical_rect;
      GSList *m;
1055 1056
      gint min_pos_before, min_pos_after;
      gint min_pos, max_pos;
1057

1058
      orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (range));
1059
      _gtk_range_get_stop_positions (range, &marks);
1060
      layout = gtk_widget_create_pango_layout (widget, NULL);
1061
      gtk_range_get_range_rect (range, &range_rect);
1062

1063 1064
      min_pos_before = min_pos_after = 0;

1065 1066 1067
      for (m = priv->marks, i = 0; m; m = m->next, i++)
        {
          GtkScaleMark *mark = m->data;
1068 1069

          if (orientation == GTK_ORIENTATION_HORIZONTAL)
1070
            {
1071
              x1 = marks[i];
1072 1073
              if (mark->position == GTK_POS_TOP)
                {
1074
                  y1 = range_rect.y;
1075
                  y2 = y1 - slider_width / 2;
1076
                  min_pos = min_pos_before;
1077
                  max_pos = find_next_pos (widget, m, marks + i, GTK_POS_TOP, 1) - min_sep;
1078 1079 1080
                }
              else
                {
1081
                  y1 = range_rect.y + range_rect.height;
1082
                  y2 = y1 + slider_width / 2;
1083
                  min_pos = min_pos_after;
1084
                  max_pos = find_next_pos (widget, m, marks + i, GTK_POS_BOTTOM, 1) - min_sep;
1085 1086
                }

1087 1088 1089 1090
              gtk_style_context_save (context);
              gtk_style_context_add_class (context, GTK_STYLE_CLASS_MARK);
              gtk_style_context_set_state (context, state);

1091
              gtk_render_line (context, cr, x1, y1, x1, y2);
1092 1093 1094 1095 1096

              if (mark->markup)
                {
                  pango_layout_set_markup (layout, mark->markup, -1);
                  pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1097

1098
                  x3 = x1 - logical_rect.width / 2;
1099 1100 1101 1102
                  if (x3 < min_pos)
                    x3 = min_pos;
                  if (x3 + logical_rect.width > max_pos)
                        x3 = max_pos - logical_rect.width;
1103 1104
                  if (x3 < 0)
                     x3 = 0;
1105
                  if (mark->position == GTK_POS_TOP)
1106 1107 1108 1109
                    {
                      y3 = y2 - value_spacing - logical_rect.height;
                      min_pos_before = x3 + logical_rect.width + min_sep;
                    }
1110
                  else
1111 1112 1113 1114
                    {
                      y3 = y2 + value_spacing;
                      min_pos_after = x3 + logical_rect.width + min_sep;
                    }
1115

1116
                  gtk_render_layout (context, cr, x3, y3, layout);
1117
                }
1118 1119

              gtk_style_context_restore (context);
1120 1121 1122
            }
          else
            {
1123
              if (mark->position == GTK_POS_TOP)
1124
                {
1125 1126
                  x1 = range_rect.x;
                  x2 = range_rect.x - slider_width / 2;
1127
                  min_pos = min_pos_before;
1128
                  max_pos = find_next_pos (widget, m, marks + i, GTK_POS_TOP, 1) - min_sep;
1129 1130 1131
                }
              else
                {
1132 1133
                  x1 = range_rect.x + range_rect.width;
                  x2 = range_rect.x + range_rect.width + slider_width / 2;
1134
                  min_pos = min_pos_after;
1135
                  max_pos = find_next_pos (widget, m, marks + i, GTK_POS_BOTTOM, 1) - min_sep;
1136
                }
1137
              y1 = marks[i];
1138

1139 1140 1141 1142
              gtk_style_context_save (context);
              gtk_style_context_add_class (context, GTK_STYLE_CLASS_MARK);
              gtk_style_context_set_state (context, state);

1143
              gtk_render_line (context, cr, x1, y1, x2, y1);
1144 1145 1146 1147 1148

              if (mark->markup)
                {
                  pango_layout_set_markup (layout, mark->markup, -1);
                  pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1149

1150 1151 1152 1153 1154
                  y3 = y1 - logical_rect.height / 2;
                  if (y3 < min_pos)
                    y3 = min_pos;
                  if (y3 + logical_rect.height > max_pos)
                    y3 = max_pos - logical_rect.height;
1155 1156
                  if (y3 < 0)
                    y3 = 0;
1157
                  if (mark->position == GTK_POS_TOP)
1158 1159 1160 1161
                    {
                      x3 = x2 - value_spacing - logical_rect.width;
                      min_pos_before = y3 + logical_rect.height + min_sep;
                    }
1162
                  else
1163 1164 1165 1166
                    {
                      x3 = x2 + value_spacing;
                      min_pos_after = y3 + logical_rect.height + min_sep;
                    }
1167

1168
                  gtk_render_layout (context, cr, x3, y3, layout);
1169
                }
1170 1171

              gtk_style_context_restore (context);
1172
            }
1173
        }
1174 1175 1176 1177 1178

      g_object_unref (layout);
      g_free (marks);
    }

1179
  if (priv->draw_value)
1180
    {
1181 1182
      GtkAllocation allocation;

1183 1184 1185 1186 1187
      PangoLayout *layout;
      gint x, y;

      layout = gtk_scale_get_layout (scale);
      gtk_scale_get_layout_offsets (scale, &x, &y);
1188
      gtk_widget_get_allocation (widget, &allocation);
1189

1190 1191 1192 1193
      gtk_render_layout (context, cr,
                         x - allocation.x,
                         y - allocation.y,
                         layout);
1194 1195 1196 1197 1198 1199 1200 1201 1202 1203
    }

  return FALSE;
}

static void
gtk_scale_real_get_layout_offsets (GtkScale *scale,
                                   gint     *x,
                                   gint     *y)
{
1204
  GtkScalePrivate *priv = scale->priv;
1205
  GtkAllocation allocation;
1206 1207
  GtkWidget *widget = GTK_WIDGET (scale);
  GtkRange *range = GTK_RANGE (widget);
1208
  GdkRectangle range_rect;
1209 1210
  PangoLayout *layout = gtk_scale_get_layout (scale);
  PangoRectangle logical_rect;
1211
  gint slider_start, slider_end;
1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225
  gint value_spacing;

  if (!layout)
    {
      *x = 0;
      *y = 0;

      return;
    }

  gtk_widget_style_get (widget, "value-spacing", &value_spacing, NULL);

  pango_layout_get_pixel_extents (layout, NULL, &logical_rect);

1226
  gtk_widget_get_allocation (widget, &allocation);
1227 1228 1229 1230
  gtk_range_get_range_rect (range, &range_rect);
  gtk_range_get_slider_range (range,
                              &slider_start,
                              &slider_end);
1231

1232
  if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL)
1233
    {
1234
      switch (priv->value_pos)
1235 1236
        {
        case GTK_POS_LEFT:
1237 1238
          *x = range_rect.x - value_spacing - logical_rect.width;
          *y = range_rect.y + (range_rect.height - logical_rect.height) / 2;
1239 1240 1241
          break;

        case GTK_POS_RIGHT:
1242 1243
          *x = range_rect.x + range_rect.width + value_spacing;
          *y = range_rect.y + (range_rect.height - logical_rect.height) / 2;
12