gtkscale.c 53.7 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 47

/**
 * SECTION:gtkscale
48
 * @Short_description: Base class for GtkHScale and GtkVScale
49 50
 * @Title: GtkScale
 *
51
 * A GtkScale is a slider control used to select a numeric value.
52
 * To use it, you'll probably want to investigate the methods on
53
 * its base class, #GtkRange, in addition to the methods for GtkScale itself.
54 55
 * 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
56
 * #GtkRange::value-changed signal.
57
 *
58 59 60 61 62
 * 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).
 *
63 64 65 66 67 68 69 70 71 72 73
 * <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>
 */


74 75 76 77 78 79
#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
				 */

80 81
typedef struct _GtkScaleMark GtkScaleMark;

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

  GSList       *marks;

  gint          digits;

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

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

101
enum {
102 103 104 105
  PROP_0,
  PROP_DIGITS,
  PROP_DRAW_VALUE,
  PROP_VALUE_POS
106 107
};

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

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

115 116 117 118 119 120 121 122
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);
123 124 125 126 127 128
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);
129
static void     gtk_scale_style_updated           (GtkWidget      *widget);
130 131
static void     gtk_scale_get_range_border        (GtkRange       *range,
                                                   GtkBorder      *border);
132 133 134 135 136 137 138 139
static void     gtk_scale_get_mark_label_size     (GtkScale        *scale,
                                                   GtkPositionType  position,
                                                   gint            *count1,
                                                   gint            *width1,
                                                   gint            *height1,
                                                   gint            *count2,
                                                   gint            *width2,
                                                   gint            *height2);
140 141 142
static void     gtk_scale_finalize                (GObject        *object);
static void     gtk_scale_screen_changed          (GtkWidget      *widget,
                                                   GdkScreen      *old_screen);
143 144
static gboolean gtk_scale_draw                    (GtkWidget      *widget,
                                                   cairo_t        *cr);
145 146 147
static void     gtk_scale_real_get_layout_offsets (GtkScale       *scale,
                                                   gint           *x,
                                                   gint           *y);
148 149 150 151 152 153 154 155 156 157 158 159 160 161
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);


162 163 164
G_DEFINE_TYPE_WITH_CODE (GtkScale, gtk_scale, GTK_TYPE_RANGE,
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
                                                gtk_scale_buildable_interface_init))
165

Elliot Lee's avatar
Elliot Lee committed
166

167
static gboolean
Havoc Pennington's avatar
Havoc Pennington committed
168 169 170 171 172 173
single_string_accumulator (GSignalInvocationHint *ihint,
                           GValue                *return_accu,
                           const GValue          *handler_return,
                           gpointer               dummy)
{
  gboolean continue_emission;
174
  const gchar *str;
Havoc Pennington's avatar
Havoc Pennington committed
175 176 177 178 179 180 181 182
  
  str = g_value_get_string (handler_return);
  g_value_set_string (return_accu, str);
  continue_emission = str == NULL;
  
  return continue_emission;
}

183

184 185
#define add_slider_binding(binding_set, keyval, mask, scroll)              \
  gtk_binding_entry_add_signal (binding_set, keyval, mask,                 \
186
                                I_("move-slider"), 1, \
187 188
                                GTK_TYPE_SCROLL_TYPE, scroll)

Elliot Lee's avatar
Elliot Lee committed
189 190 191
static void
gtk_scale_class_init (GtkScaleClass *class)
{
192
  GObjectClass   *gobject_class;
193
  GtkWidgetClass *widget_class;
Manish Singh's avatar
Manish Singh committed
194 195
  GtkRangeClass  *range_class;
  GtkBindingSet  *binding_set;
196
  
197
  gobject_class = G_OBJECT_CLASS (class);
Elliot Lee's avatar
Elliot Lee committed
198
  range_class = (GtkRangeClass*) class;
199
  widget_class = (GtkWidgetClass*) class;
200
  
201 202
  gobject_class->set_property = gtk_scale_set_property;
  gobject_class->get_property = gtk_scale_get_property;
203
  gobject_class->finalize = gtk_scale_finalize;
204

205
  widget_class->style_updated = gtk_scale_style_updated;
206
  widget_class->screen_changed = gtk_scale_screen_changed;
207
  widget_class->draw = gtk_scale_draw;
208 209
  widget_class->get_preferred_width = gtk_scale_get_preferred_width;
  widget_class->get_preferred_height = gtk_scale_get_preferred_height;
210

211
  range_class->slider_detail = "Xscale";
212
  range_class->get_range_border = gtk_scale_get_range_border;
213 214 215

  class->get_layout_offsets = gtk_scale_real_get_layout_offsets;

Matthias Clasen's avatar
Matthias Clasen committed
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
  /**
   * 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
   */
239
  signals[FORMAT_VALUE] =
240
    g_signal_new (I_("format-value"),
Manish Singh's avatar
Manish Singh committed
241
                  G_TYPE_FROM_CLASS (gobject_class),
242 243 244
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkScaleClass, format_value),
                  single_string_accumulator, NULL,
245
                  _gtk_marshal_STRING__DOUBLE,
246 247
                  G_TYPE_STRING, 1,
                  G_TYPE_DOUBLE);
248

249 250 251
  g_object_class_install_property (gobject_class,
                                   PROP_DIGITS,
                                   g_param_spec_int ("digits",
252 253
						     P_("Digits"),
						     P_("The number of decimal places that are displayed in the value"),
254 255 256
						     -1,
						     MAX_DIGITS,
						     1,
257
						     GTK_PARAM_READWRITE));
258 259 260
  
  g_object_class_install_property (gobject_class,
                                   PROP_DRAW_VALUE,
261
                                   g_param_spec_boolean ("draw-value",
262 263
							 P_("Draw Value"),
							 P_("Whether the current value is displayed as a string next to the slider"),
264
							 TRUE,
265
							 GTK_PARAM_READWRITE));
266 267 268
  
  g_object_class_install_property (gobject_class,
                                   PROP_VALUE_POS,
269
                                   g_param_spec_enum ("value-pos",
270 271
						      P_("Value Position"),
						      P_("The position in which the current value is displayed"),
272
						      GTK_TYPE_POSITION_TYPE,
273
						      GTK_POS_TOP,
274
						      GTK_PARAM_READWRITE));
275

276
  gtk_widget_class_install_style_property (widget_class,
277
					   g_param_spec_int ("slider-length",
278 279
							     P_("Slider Length"),
							     P_("Length of scale's slider"),
280 281 282
							     0,
							     G_MAXINT,
							     31,
283
							     GTK_PARAM_READABLE));
284 285

  gtk_widget_class_install_style_property (widget_class,
286
					   g_param_spec_int ("value-spacing",
287 288
							     P_("Value spacing"),
							     P_("Space between value text and the slider/trough area"),
289 290 291
							     0,
							     G_MAXINT,
							     2,
292
							     GTK_PARAM_READABLE));
293 294 295 296 297 298 299
  
  /* 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);

300
  add_slider_binding (binding_set, GDK_KEY_Left, 0,
301 302
                      GTK_SCROLL_STEP_LEFT);

303
  add_slider_binding (binding_set, GDK_KEY_Left, GDK_CONTROL_MASK,
304 305
                      GTK_SCROLL_PAGE_LEFT);

306
  add_slider_binding (binding_set, GDK_KEY_KP_Left, 0,
307 308
                      GTK_SCROLL_STEP_LEFT);

309
  add_slider_binding (binding_set, GDK_KEY_KP_Left, GDK_CONTROL_MASK,
310 311
                      GTK_SCROLL_PAGE_LEFT);

312
  add_slider_binding (binding_set, GDK_KEY_Right, 0,
313 314
                      GTK_SCROLL_STEP_RIGHT);

315
  add_slider_binding (binding_set, GDK_KEY_Right, GDK_CONTROL_MASK,
316 317
                      GTK_SCROLL_PAGE_RIGHT);

318
  add_slider_binding (binding_set, GDK_KEY_KP_Right, 0,
319 320
                      GTK_SCROLL_STEP_RIGHT);

321
  add_slider_binding (binding_set, GDK_KEY_KP_Right, GDK_CONTROL_MASK,
322 323
                      GTK_SCROLL_PAGE_RIGHT);

324
  add_slider_binding (binding_set, GDK_KEY_Up, 0,
325 326
                      GTK_SCROLL_STEP_UP);

327
  add_slider_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK,
328 329
                      GTK_SCROLL_PAGE_UP);

330
  add_slider_binding (binding_set, GDK_KEY_KP_Up, 0,
331 332
                      GTK_SCROLL_STEP_UP);

333
  add_slider_binding (binding_set, GDK_KEY_KP_Up, GDK_CONTROL_MASK,
334 335
                      GTK_SCROLL_PAGE_UP);

336
  add_slider_binding (binding_set, GDK_KEY_Down, 0,
337 338
                      GTK_SCROLL_STEP_DOWN);

339
  add_slider_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK,
340 341
                      GTK_SCROLL_PAGE_DOWN);

342
  add_slider_binding (binding_set, GDK_KEY_KP_Down, 0,
343 344
                      GTK_SCROLL_STEP_DOWN);

345
  add_slider_binding (binding_set, GDK_KEY_KP_Down, GDK_CONTROL_MASK,
346 347
                      GTK_SCROLL_PAGE_DOWN);
   
348
  add_slider_binding (binding_set, GDK_KEY_Page_Up, GDK_CONTROL_MASK,
349
                      GTK_SCROLL_PAGE_LEFT);
350

351
  add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, GDK_CONTROL_MASK,
352 353
                      GTK_SCROLL_PAGE_LEFT);  

354
  add_slider_binding (binding_set, GDK_KEY_Page_Up, 0,
355
                      GTK_SCROLL_PAGE_UP);
356

357
  add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, 0,
358 359
                      GTK_SCROLL_PAGE_UP);
  
360
  add_slider_binding (binding_set, GDK_KEY_Page_Down, GDK_CONTROL_MASK,
361
                      GTK_SCROLL_PAGE_RIGHT);
362

363
  add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, GDK_CONTROL_MASK,
364 365
                      GTK_SCROLL_PAGE_RIGHT);

366
  add_slider_binding (binding_set, GDK_KEY_Page_Down, 0,
367
                      GTK_SCROLL_PAGE_DOWN);
368

369
  add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, 0,
370 371 372 373
                      GTK_SCROLL_PAGE_DOWN);

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

374
  add_slider_binding (binding_set, GDK_KEY_plus, 0,
375 376
                      GTK_SCROLL_STEP_FORWARD);  

377
  add_slider_binding (binding_set, GDK_KEY_minus, 0,
378 379
                      GTK_SCROLL_STEP_BACKWARD);  

380
  add_slider_binding (binding_set, GDK_KEY_plus, GDK_CONTROL_MASK,
381 382
                      GTK_SCROLL_PAGE_FORWARD);  

383
  add_slider_binding (binding_set, GDK_KEY_minus, GDK_CONTROL_MASK,
384 385 386
                      GTK_SCROLL_PAGE_BACKWARD);


387
  add_slider_binding (binding_set, GDK_KEY_KP_Add, 0,
388 389
                      GTK_SCROLL_STEP_FORWARD);  

390
  add_slider_binding (binding_set, GDK_KEY_KP_Subtract, 0,
391 392
                      GTK_SCROLL_STEP_BACKWARD);  

393
  add_slider_binding (binding_set, GDK_KEY_KP_Add, GDK_CONTROL_MASK,
394 395
                      GTK_SCROLL_PAGE_FORWARD);  

396
  add_slider_binding (binding_set, GDK_KEY_KP_Subtract, GDK_CONTROL_MASK,
397 398 399
                      GTK_SCROLL_PAGE_BACKWARD);
  
  
400
  add_slider_binding (binding_set, GDK_KEY_Home, 0,
401 402
                      GTK_SCROLL_START);

403
  add_slider_binding (binding_set, GDK_KEY_KP_Home, 0,
404 405
                      GTK_SCROLL_START);

406
  add_slider_binding (binding_set, GDK_KEY_End, 0,
407 408
                      GTK_SCROLL_END);

409
  add_slider_binding (binding_set, GDK_KEY_KP_End, 0,
410
                      GTK_SCROLL_END);
411

412
  g_type_class_add_private (gobject_class, sizeof (GtkScalePrivate));
Elliot Lee's avatar
Elliot Lee committed
413 414
}

415 416 417 418
static void
gtk_scale_orientation_notify (GtkRange         *range,
                              const GParamSpec *pspec)
{
419 420 421 422 423
  GtkOrientation orientation;

  orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (range));
  gtk_range_set_flippable (range,
                           orientation == GTK_ORIENTATION_HORIZONTAL);
424 425 426 427 428
}

static void
gtk_scale_init (GtkScale *scale)
{
429
  GtkScalePrivate *priv;
430
  GtkRange *range = GTK_RANGE (scale);
431
  GtkStyleContext *context;
432

433 434
  scale->priv = G_TYPE_INSTANCE_GET_PRIVATE (scale,
                                             GTK_TYPE_SCALE,
435
                                             GtkScalePrivate);
436 437
  priv = scale->priv;

438
  gtk_widget_set_can_focus (GTK_WIDGET (scale), TRUE);
439

440
  gtk_range_set_slider_size_fixed (range, TRUE);
441

442 443 444
  priv->draw_value = TRUE;
  priv->value_pos = GTK_POS_TOP;
  priv->digits = 1;
445
  gtk_range_set_round_digits (range, priv->digits);
446 447 448 449 450

  gtk_scale_orientation_notify (range, NULL);
  g_signal_connect (scale, "notify::orientation",
                    G_CALLBACK (gtk_scale_orientation_notify),
                    NULL);
451 452 453

  context = gtk_widget_get_style_context (GTK_WIDGET (scale));
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE);
454 455
}

456
static void
457 458 459 460
gtk_scale_set_property (GObject      *object,
			guint         prop_id,
			const GValue *value,
			GParamSpec   *pspec)
461 462 463 464 465
{
  GtkScale *scale;

  scale = GTK_SCALE (object);

466
  switch (prop_id)
467
    {
468 469
    case PROP_DIGITS:
      gtk_scale_set_digits (scale, g_value_get_int (value));
470
      break;
471 472
    case PROP_DRAW_VALUE:
      gtk_scale_set_draw_value (scale, g_value_get_boolean (value));
473
      break;
474 475
    case PROP_VALUE_POS:
      gtk_scale_set_value_pos (scale, g_value_get_enum (value));
476 477
      break;
    default:
478
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
479 480 481 482 483
      break;
    }
}

static void
484 485 486 487
gtk_scale_get_property (GObject      *object,
			guint         prop_id,
			GValue       *value,
			GParamSpec   *pspec)
488
{
489
  GtkScale *scale = GTK_SCALE (object);
490
  GtkScalePrivate *priv = scale->priv;
491

492
  switch (prop_id)
493
    {
494
    case PROP_DIGITS:
495
      g_value_set_int (value, priv->digits);
496
      break;
497
    case PROP_DRAW_VALUE:
498
      g_value_set_boolean (value, priv->draw_value);
499
      break;
500
    case PROP_VALUE_POS:
501
      g_value_set_enum (value, priv->value_pos);
502 503
      break;
    default:
504
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
505 506 507 508
      break;
    }
}

509 510 511
/**
 * gtk_scale_new:
 * @orientation: the scale's orientation.
512 513
 * @adjustment: (allow-none): the #GtkAdjustment which sets the range
 *              of the scale, or %NULL to create a new adjustment.
514 515 516 517 518
 *
 * Creates a new #GtkScale.
 *
 * Return value: a new #GtkScale
 *
519
 * Since: 3.0
520 521 522 523
 **/
GtkWidget *
gtk_scale_new (GtkOrientation  orientation,
               GtkAdjustment  *adjustment)
524
{
525 526
  g_return_val_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment),
                        NULL);
527

528 529 530 531 532
  return g_object_new (GTK_TYPE_SCALE,
                       "orientation", orientation,
                       "adjustment",  adjustment,
                       NULL);
}
533

534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
/**
 * 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
 *
553 554
 * Since: 3.0
 */
555 556 557 558 559 560
GtkWidget *
gtk_scale_new_with_range (GtkOrientation orientation,
                          gdouble        min,
                          gdouble        max,
                          gdouble        step)
{
561
  GtkAdjustment *adj;
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584
  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);
585 586
}

Matthias Clasen's avatar
Matthias Clasen committed
587 588 589
/**
 * gtk_scale_set_digits:
 * @scale: a #GtkScale
590
 * @digits: the number of decimal places to display,
Matthias Clasen's avatar
Matthias Clasen committed
591
 *     e.g. use 1 to display 1.0, 2 to display 1.00, etc
592
 *
Matthias Clasen's avatar
Matthias Clasen committed
593 594 595 596
 * 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
597 598 599 600
void
gtk_scale_set_digits (GtkScale *scale,
		      gint      digits)
{
601
  GtkScalePrivate *priv;
602
  GtkRange *range;
603

Elliot Lee's avatar
Elliot Lee committed
604 605
  g_return_if_fail (GTK_IS_SCALE (scale));

606
  priv = scale->priv;
607 608
  range = GTK_RANGE (scale);
  
609
  digits = CLAMP (digits, -1, MAX_DIGITS);
610

611
  if (priv->digits != digits)
Elliot Lee's avatar
Elliot Lee committed
612
    {
613 614
      priv->digits = digits;
      if (priv->draw_value)
615
        gtk_range_set_round_digits (range, digits);
616

617
      _gtk_scale_clear_layout (scale);
618
      gtk_widget_queue_resize (GTK_WIDGET (scale));
619 620

      g_object_notify (G_OBJECT (scale), "digits");
Elliot Lee's avatar
Elliot Lee committed
621 622 623
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
624 625 626 627 628 629 630 631
/**
 * 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
 */
632 633 634 635 636
gint
gtk_scale_get_digits (GtkScale *scale)
{
  g_return_val_if_fail (GTK_IS_SCALE (scale), -1);

637
  return scale->priv->digits;
638 639
}

Matthias Clasen's avatar
Matthias Clasen committed
640 641 642
/**
 * gtk_scale_set_draw_value:
 * @scale: a #GtkScale
Matthias Clasen's avatar
Matthias Clasen committed
643
 * @draw_value: %TRUE to draw the value
Matthias Clasen's avatar
Matthias Clasen committed
644 645 646 647
 * 
 * Specifies whether the current value is displayed as a string next 
 * to the slider.
 */
Elliot Lee's avatar
Elliot Lee committed
648 649
void
gtk_scale_set_draw_value (GtkScale *scale,
650
			  gboolean  draw_value)
Elliot Lee's avatar
Elliot Lee committed
651
{
652
  GtkScalePrivate *priv;
653

Elliot Lee's avatar
Elliot Lee committed
654 655
  g_return_if_fail (GTK_IS_SCALE (scale));

656 657
  priv = scale->priv;

658 659
  draw_value = draw_value != FALSE;

660
  if (priv->draw_value != draw_value)
Elliot Lee's avatar
Elliot Lee committed
661
    {
662
      priv->draw_value = draw_value;
663
      if (draw_value)
664
        gtk_range_set_round_digits (GTK_RANGE (scale), priv->digits);
665
      else
666
        gtk_range_set_round_digits (GTK_RANGE (scale), -1);
Elliot Lee's avatar
Elliot Lee committed
667

668 669
      _gtk_scale_clear_layout (scale);

670
      gtk_widget_queue_resize (GTK_WIDGET (scale));
671

672
      g_object_notify (G_OBJECT (scale), "draw-value");
Elliot Lee's avatar
Elliot Lee committed
673 674 675
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
676 677 678 679 680 681 682 683 684
/**
 * 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
 */
685 686 687 688 689
gboolean
gtk_scale_get_draw_value (GtkScale *scale)
{
  g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE);

690
  return scale->priv->draw_value;
691 692
}

Matthias Clasen's avatar
Matthias Clasen committed
693 694 695 696 697 698 699
/**
 * 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
700 701 702 703
void
gtk_scale_set_value_pos (GtkScale        *scale,
			 GtkPositionType  pos)
{
704
  GtkScalePrivate *priv;
705 706
  GtkWidget *widget;

Elliot Lee's avatar
Elliot Lee committed
707 708
  g_return_if_fail (GTK_IS_SCALE (scale));

709 710 711
  priv = scale->priv;

  if (priv->value_pos != pos)
Elliot Lee's avatar
Elliot Lee committed
712
    {
713
      priv->value_pos = pos;
714
      widget = GTK_WIDGET (scale);
Elliot Lee's avatar
Elliot Lee committed
715

716
      _gtk_scale_clear_layout (scale);
717
      if (gtk_widget_get_visible (widget) && gtk_widget_get_mapped (widget))
718
	gtk_widget_queue_resize (widget);
719

720
      g_object_notify (G_OBJECT (scale), "value-pos");
Elliot Lee's avatar
Elliot Lee committed
721 722 723
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
724 725 726 727 728 729 730 731
/**
 * 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
 */
732 733 734 735 736
GtkPositionType
gtk_scale_get_value_pos (GtkScale *scale)
{
  g_return_val_if_fail (GTK_IS_SCALE (scale), 0);

737
  return scale->priv->value_pos;
738 739 740 741 742 743
}

static void
gtk_scale_get_range_border (GtkRange  *range,
                            GtkBorder *border)
{
744
  GtkScalePrivate *priv;
745 746 747 748 749 750
  GtkWidget *widget;
  GtkScale *scale;
  gint w, h;
  
  widget = GTK_WIDGET (range);
  scale = GTK_SCALE (range);
751
  priv = scale->priv;
752 753 754 755 756 757 758 759

  _gtk_scale_get_value_size (scale, &w, &h);

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

760
  if (priv->draw_value)
761 762
    {
      gint value_spacing;
763
      gtk_widget_style_get (widget, "value-spacing", &value_spacing, NULL);
764

765
      switch (priv->value_pos)
766 767 768 769 770 771 772 773 774 775 776 777 778 779 780
        {
        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;
        }
    }
781 782 783 784 785 786 787 788 789 790 791 792 793

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


794 795
      gtk_scale_get_mark_label_size (scale, GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);

796
      if (gtk_orientable_get_orientation (GTK_ORIENTABLE (scale)) == GTK_ORIENTATION_HORIZONTAL)
797 798 799 800
        {
          if (n1 > 0)
            border->top += h1 + value_spacing + slider_width / 2;
          if (n2 > 0)
801
            border->bottom += h2 + value_spacing + slider_width / 2;
802 803 804 805 806 807 808 809 810
        }
      else
        {
          if (n1 > 0)
            border->left += w1 + value_spacing + slider_width / 2;
          if (n2 > 0)
            border->right += w2 + value_spacing + slider_width / 2;
        }
    }
811 812 813
}

/* FIXME this could actually be static at the moment. */
814
void
815 816 817
_gtk_scale_get_value_size (GtkScale *scale,
                           gint     *width,
                           gint     *height)
Elliot Lee's avatar
Elliot Lee committed
818
{
819
  GtkScalePrivate *priv = scale->priv;
Elliot Lee's avatar
Elliot Lee committed
820 821
  GtkRange *range;

822
  if (priv->draw_value)
Elliot Lee's avatar
Elliot Lee committed
823
    {
824
      GtkAdjustment *adjustment;
825 826
      PangoLayout *layout;
      PangoRectangle logical_rect;
Havoc Pennington's avatar
Havoc Pennington committed
827
      gchar *txt;
828
      
Elliot Lee's avatar
Elliot Lee committed
829 830
      range = GTK_RANGE (scale);

831
      layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
832
      adjustment = gtk_range_get_adjustment (range);
833

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

840
      if (width)
841
	*width = logical_rect.width;
842
      if (height)
Havoc Pennington's avatar
Havoc Pennington committed
843 844
	*height = logical_rect.height;

845
      txt = _gtk_scale_format_value (scale, gtk_adjustment_get_upper (adjustment));
Havoc Pennington's avatar
Havoc Pennington committed
846 847
      pango_layout_set_text (layout, txt, -1);
      g_free (txt);
848
      
849
      pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
850 851

      if (width)
852
	*width = MAX (*width, logical_rect.width);
853
      if (height)
854
	*height = MAX (*height, logical_rect.height);
855

Manish Singh's avatar
Manish Singh committed
856
      g_object_unref (layout);
Elliot Lee's avatar
Elliot Lee committed
857
    }
858 859 860 861 862 863 864 865 866 867
  else
    {
      if (width)
	*width = 0;
      if (height)
	*height = 0;
    }

}

868 869 870 871 872 873 874 875 876 877
static void
gtk_scale_get_mark_label_size (GtkScale        *scale,
                               GtkPositionType  position,
                               gint            *count1,
                               gint            *width1,
                               gint            *height1,
                               gint            *count2,
                               gint            *width2,
                               gint            *height2)
{
878
  GtkScalePrivate *priv = scale->priv;
879 880 881 882 883 884 885 886 887 888 889 890 891 892 893
  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;

894
      if (mark->markup && *mark->markup)
895 896 897 898
        {
          pango_layout_set_markup (layout, mark->markup, -1);
          pango_layout_get_pixel_extents (layout, NULL, &logical_rect);

899 900
          w = logical_rect.width;
          h = logical_rect.height;
901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924
        }
      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);
}

925
static void
926
gtk_scale_style_updated (GtkWidget *widget)
927
{
928 929
  gint slider_length;
  GtkRange *range;
930

931
  range = GTK_RANGE (widget);
932
  
933
  gtk_widget_style_get (widget,
934
                        "slider-length", &slider_length,
935
                        NULL);
936 937 938

  gtk_range_set_min_slider_size (range, slider_length);

939 940
  _gtk_scale_clear_layout (GTK_SCALE (widget));

941
  GTK_WIDGET_CLASS (gtk_scale_parent_class)->style_updated (widget);
Elliot Lee's avatar
Elliot Lee committed
942 943
}

944 945 946 947 948 949
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
950

951
static void
952 953 954
gtk_scale_get_preferred_width (GtkWidget *widget,
                               gint      *minimum,
                               gint      *natural)
955
{
956
  GTK_WIDGET_CLASS (gtk_scale_parent_class)->get_preferred_width (widget, minimum, natural);
957
  
958 959 960 961 962
  if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
    {
      gint n1, w1, h1, n2, w2, h2;
      gint slider_length;
      gint w;
963

964
      gtk_widget_style_get (widget, "slider-length", &slider_length, NULL);
965 966 967 968 969

      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);
970 971 972 973
      w = MAX (w1, w2);

      *minimum = MAX (*minimum, w);
      *natural = MAX (*natural, w);
974
    }
975 976 977 978 979 980 981 982 983 984 985
}

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)
986
    {
987 988 989 990 991 992
      gint n1, w1, h1, n2, w2, h2;
      gint slider_length;
      gint h;

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

993
      gtk_scale_get_mark_label_size (GTK_SCALE (widget), GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
994 995
      h1 = (n1 - 1) * h1 + MAX (h1, slider_length);
      h2 = (n2 - 1) * h1 + MAX (h2, slider_length);
996 997 998 999
      h = MAX (h1, h2);

      *minimum = MAX (*minimum, h);
      *natural = MAX (*natural, h);
1000 1001 1002
    }
}

1003 1004 1005 1006 1007 1008 1009
static gint
find_next_pos (GtkWidget      *widget,
               GSList          *list,
               gint            *marks,
               GtkPositionType  pos,
               gint             match)
{
1010
  GtkAllocation allocation;
1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021
  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];
    }

1022
  gtk_widget_get_allocation (widget, &allocation);
1023
  if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
1024
    return allocation.width;
1025
  else
1026
    return allocation.height;
1027 1028
}

1029
static gboolean
1030 1031
gtk_scale_draw (GtkWidget *widget,
                cairo_t   *cr)
1032 1033
{
  GtkScale *scale = GTK_SCALE (widget);
1034
  GtkScalePrivate *priv = scale->priv;
1035
  GtkRange *range = GTK_RANGE (scale);
1036 1037
  GtkStateFlags state = 0;
  GtkStyleContext *context;
1038 1039 1040 1041
  gint *marks;
  gint focus_padding;
  gint slider_width;
  gint value_spacing;
1042
  gint min_sep = 4;
1043

1044
  context = gtk_widget_get_style_context (widget);
1045 1046
  gtk_widget_style_get (widget,
                        "focus-padding", &focus_padding,
1047 1048
                        "slider-width", &slider_width,
                        "value-spacing", &value_spacing,
1049
                        NULL);
1050 1051 1052 1053

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

1056
  if (!gtk_widget_is_sensitive (widget))
1057
    state |= GTK_STATE_FLAG_INSENSITIVE;
1058 1059 1060

  if (priv->marks)
    {
1061 1062
      GtkOrientation orientation;
      GdkRectangle range_rect;
1063 1064 1065 1066 1067
      gint i;
      gint x1, x2, x3, y1, y2, y3;
      PangoLayout *layout;
      PangoRectangle logical_rect;
      GSList *m;
1068 1069
      gint min_pos_before, min_pos_after;
      gint min_pos, max_pos;
1070

1071
      orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (range));
1072
      _gtk_range_get_stop_positions (range, &marks);
1073
      layout = gtk_widget_create_pango_layout (widget, NULL);
1074
      gtk_range_get_range_rect (range, &range_rect);
1075

1076 1077
      min_pos_before = min_pos_after = 0;

1078 1079 1080
      for (m = priv->marks, i = 0; m; m = m->next, i++)
        {
          GtkScaleMark *mark = m->data;
1081 1082

          if (orientation == GTK_ORIENTATION_HORIZONTAL)
1083
            {
1084
              x1 = marks[i];
1085 1086
              if (mark->position == GTK_POS_TOP)
                {
1087
                  y1 = range_rect.y;
1088
                  y2 = y1 - slider_width / 2;
1089
                  min_pos = min_pos_before;
1090
                  max_pos = find_next_pos (widget, m, marks + i, GTK_POS_TOP, 1) - min_sep;
1091 1092 1093
                }
              else
                {
1094
                  y1 = range_rect.y + range_rect.height;
1095
                  y2 = y1 + slider_width / 2;
1096
                  min_pos = min_pos_after;
1097
                  max_pos = find_next_pos (widget, m, marks + i, GTK_POS_BOTTOM, 1) - min_sep;
1098 1099
                }

1100 1101 1102 1103
              gtk_style_context_save (context);
              gtk_style_context_add_class (context, GTK_STYLE_CLASS_MARK);
              gtk_style_context_set_state (context, state);

1104
              gtk_render_line (context, cr, x1, y1, x1, y2);
1105 1106 1107 1108 1109

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

1111
                  x3 = x1 - logical_rect.width / 2;
1112 1113 1114 1115
                  if (x3 < min_pos)
                    x3 = min_pos;
                  if (x3 + logical_rect.width > max_pos)
                        x3 = max_pos - logical_rect.width;
1116 1117
                  if (x3 < 0)
                     x3 = 0;
1118
                  if (mark->position == GTK_POS_TOP)
1119 1120 1121 1122
                    {
                      y3 = y2 - value_spacing - logical_rect.height;
                      min_pos_before = x3 + logical_rect.width + min_sep;
                    }
1123
                  else
1124 1125 1126 1127
                    {
                      y3 = y2 + value_spacing;
                      min_pos_after = x3 + logical_rect.width + min_sep;
                    }
1128

1129
                  gtk_render_layout (context, cr, x3, y3, layout);
1130
                }
1131 1132

              gtk_style_context_restore (context);
1133 1134 1135
            }
          else
            {
1136
              if (mark->position == GTK_POS_TOP)
1137
                {
1138 1139
                  x1 = range_rect.x;
                  x2 = range_rect.x - slider_width / 2;
1140
                  min_pos = min_pos_before;
1141
                  max_pos = find_next_pos (widget, m, marks + i, GTK_POS_TOP, 1) - min_sep;
1142 1143 1144
                }
              else
                {
1145 1146
                  x1 = range_rect.x + range_rect.width;
                  x2 = range_rect.x + range_rect.width + slider_width / 2;
1147
                  min_pos = min_pos_after;
1148
                  max_pos = find_next_pos (widget, m, marks + i, GTK_POS_BOTTOM, 1) - min_sep;
1149
                }
1150
              y1 = marks[i];
1151

1152 1153 1154 1155
              gtk_style_context_save (context);
              gtk_style_context_add_class (context, GTK_STYLE_CLASS_MARK);
              gtk_style_context_set_state (context, state);

1156
              gtk_render_line (context, cr, x1, y1, x2, y1);
1157 1158 1159 1160 1161

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

1163 1164 1165 1166 1167
                  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;
1168 1169
                  if (y3 < 0)
                    y3 = 0;
1170
                  if (mark->position == GTK_POS_TOP)
1171 1172 1173 1174
                    {
                      x3 = x2 - value_spacing - logical_rect.width;
                      min_pos_before = y3 + logical_rect.height + min_sep;
                    }
1175
                  else
1176 1177 1178 1179
                    {
                      x3 = x2 + value_spacing;
                      min_pos_after = y3 + logical_rect.height + min_sep;
                    }
1180

1181
                  gtk_render_layout (context, cr, x3, y3, layout);
1182
                }
1183 1184

              gtk_style_context_restore (context);
1185
            }
1186
        }
1187 1188 1189 1190 1191

      g_object_unref (layout);
      g_free (marks);
    }

1192
  if (priv->draw_value)
1193
    {
1194 1195
      GtkAllocation allocation;

1196 1197 1198 1199 1200
      PangoLayout *layout;
      gint x, y;

      layout = gtk_scale_get_layout (scale);
      gtk_scale_get_layout_offsets (scale, &x, &y);
1201
      gtk_widget_get_allocation (widget, &allocation);
1202

1203 1204 1205 1206
      gtk_render_layout (context, cr,
                         x - allocation.x,
                         y - allocation.y,
                         layout);
1207 1208 1209 1210 1211 1212 1213 1214 1215 1216
    }

  return FALSE;
}

static void
gtk_scale_real_get_layout_offsets (GtkScale *scale,
                                   gint     *x,
                                   gint     *y)
{
1217
  GtkScalePrivate *priv = scale->priv;
1218
  GtkAllocation allocation;
1219 1220
  GtkWidget *widget = GTK_WIDGET (scale);
  GtkRange *range = GTK_RANGE (widget);
1221
  GdkRectangle range_rect;
1222 1223
  PangoLayout *layout = gtk_scale_get_layout (scale);
  PangoRectangle logical_rect;
1224
  gint slider_start, slider_end;
1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238
  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);

1239
  gtk_widget_get_allocation (widget, &allocation);
1240 1241 1242 1243
  gtk_range_get_range_rect (range, &range_rect);
  gtk_range_get_slider_range (range,
                              &slider_start,
                              &slider_end);
1244

1245
  if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL)
1246
    {
1247
      switch (priv->value_pos)
1248 1249
        {
        case GTK_POS_LEFT:
1250 1251
          *x = range_rect.x - value_spacing - logical_rect.width;
          *y = range_rect.y + (range_rect.height - logical_rect.height) / 2;
1252 1253 1254
          break;

        case GTK_POS_RIGHT: