gtkscale.c 82.4 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
Javier Jardón's avatar
Javier Jardón committed
16
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
Elliot Lee's avatar
Elliot Lee committed
17
 */
18 19

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

26
#include "config.h"
27

Elliot Lee's avatar
Elliot Lee committed
28
#include <math.h>
29 30
#include <stdlib.h>

31
#include "gtkrangeprivate.h"
32 33 34 35 36

#include "gtkadjustment.h"
#include "gtkbindings.h"
#include "gtkbuildable.h"
#include "gtkbuilderprivate.h"
37
#include "gtkcsscustomgadgetprivate.h"
38
#include "gtkicontheme.h"
39
#include "gtkintl.h"
40
#include "gtkmarshalers.h"
41
#include "gtkorientable.h"
42
#include "gtkprivate.h"
43
#include "gtktypebuiltins.h"
44
#include "gtkstylecontextprivate.h"
45
#include "gtkstylepropertyprivate.h"
46
#include "gtkwidgetprivate.h"
47
#include "gtkcsswidgetnodeprivate.h"
48

49 50
#include "a11y/gtkscaleaccessible.h"

51 52 53

/**
 * SECTION:gtkscale
54
 * @Short_description: A slider widget for selecting a value from a range
55 56
 * @Title: GtkScale
 *
57
 * A GtkScale is a slider control used to select a numeric value. 
58
 * To use it, you’ll probably want to investigate the methods on
59
 * its base class, #GtkRange, in addition to the methods for GtkScale itself.
60 61
 * 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
62
 * #GtkRange::value-changed signal.
63
 *
64 65 66 67 68
 * 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).
 *
69
 * # GtkScale as GtkBuildable
70
 *
71 72 73 74 75
 * GtkScale supports a custom <marks> element, which can contain multiple
 * <mark> 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
76
 * “context” attributes.
77 78 79 80
 *
 * # CSS nodes
 *
 * |[<!-- language="plain" -->
81
 * scale[.fine-tune][.marks-before][.marks-after]
82 83
 * ├── marks.top
 * │   ├── mark
84 85
 * │   ┊    ├── [label]
 * │   ┊    ╰── indicator
86 87
 * ┊   ┊
 * │   ╰── mark
88
 * ├── [value]
89 90 91 92 93
 * ├── contents
 * │   ╰── trough
 * │       ├── slider
 * │       ├── [highlight]
 * │       ╰── [fill]
94 95
 * ╰── marks.bottom
 *     ├── mark
96 97
 *     ┊    ├── indicator
 *     ┊    ╰── [label]
98
 *     ╰── mark
99 100
 * ]|
 *
101 102
 * GtkScale has a main CSS node with name scale and a subnode for its contents,
 * with subnodes named trough and slider.
103 104 105 106
 *
 * The main node gets the style class .fine-tune added when the scale is in
 * 'fine-tuning' mode.
 *
107 108 109 110 111 112 113 114
 * If the scale has an origin (see gtk_scale_set_has_origin()), there is a
 * subnode with name highlight below the trough node that is used for rendering
 * the highlighted part of the trough.
 *
 * If the scale is showing a fill level (see gtk_range_set_show_fill_level()),
 * there is a subnode with name fill below the trough node that is used for
 * rendering the filled in part of the trough.
 *
115
 * If marks are present, there is a marks subnode before or after the contents
116 117
 * node, below which each mark gets a node with name mark. The marks nodes get
 * either the .top or .bottom style class.
118 119 120 121 122
 *
 * The mark node has a subnode named indicator. If the mark has text, it also
 * has a subnode named label. When the mark is either above or left of the
 * scale, the label subnode is the first when present. Otherwise, the indicator
 * subnode is the first.
123
 *
124 125
 * The main CSS node gets the 'marks-before' and/or 'marks-after' style classes
 * added depending on what marks are present.
126 127 128
 *
 * If the scale is displaying the value (see #GtkScale:draw-value), there is
 * subnode with name value.
129 130 131
 */


132 133 134 135 136 137
#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
				 */

138 139
typedef struct _GtkScaleMark GtkScaleMark;

140
struct _GtkScalePrivate
141 142 143
{
  PangoLayout  *layout;

144
  GSList       *marks;
145

146 147
  GtkCssGadget *top_marks_gadget;
  GtkCssGadget *bottom_marks_gadget;
148
  GtkCssGadget *value_gadget;
149

150 151 152 153 154 155
  gint          digits;

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

156 157 158
struct _GtkScaleMark
{
  gdouble          value;
159
  int              stop_position;
160
  gchar           *markup;
161
  PangoLayout     *layout;
162
  GtkCssGadget    *gadget;
163 164
  GtkCssGadget    *indicator_gadget;
  GtkCssGadget    *label_gadget;
165
  GtkPositionType  position; /* always GTK_POS_TOP or GTK_POS_BOTTOM */
166 167
};

168
enum {
169 170 171
  PROP_0,
  PROP_DIGITS,
  PROP_DRAW_VALUE,
172
  PROP_HAS_ORIGIN,
173 174
  PROP_VALUE_POS,
  LAST_PROP
175 176
};

Havoc Pennington's avatar
Havoc Pennington committed
177 178 179 180 181
enum {
  FORMAT_VALUE,
  LAST_SIGNAL
};

182
static GParamSpec *properties[LAST_PROP];
Havoc Pennington's avatar
Havoc Pennington committed
183
static guint signals[LAST_SIGNAL];
Elliot Lee's avatar
Elliot Lee committed
184

185 186 187 188 189 190 191 192
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);
193 194 195 196 197 198
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);
199 200
static void     gtk_scale_get_range_border        (GtkRange       *range,
                                                   GtkBorder      *border);
201 202 203 204
static void     gtk_scale_get_range_size_request  (GtkRange       *range,
                                                   GtkOrientation  orientation,
                                                   gint           *minimum,
                                                   gint           *natural);
205
static void     gtk_scale_finalize                (GObject        *object);
206 207 208
static void     gtk_scale_value_style_changed     (GtkCssNode        *node,
                                                   GtkCssStyleChange *change,
                                                   GtkScale          *scale);
209 210
static void     gtk_scale_screen_changed          (GtkWidget      *widget,
                                                   GdkScreen      *old_screen);
211 212
static gboolean gtk_scale_draw                    (GtkWidget      *widget,
                                                   cairo_t        *cr);
213 214 215
static void     gtk_scale_real_get_layout_offsets (GtkScale       *scale,
                                                   gint           *x,
                                                   gint           *y);
216 217 218 219 220 221 222 223 224 225 226 227
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);
228 229
static void     gtk_scale_clear_value_layout         (GtkScale      *scale);
static void     gtk_scale_clear_mark_layouts         (GtkScale      *scale);
230 231
static gchar  * gtk_scale_format_value               (GtkScale      *scale,
                                                      gdouble        value);
232 233


234
G_DEFINE_TYPE_WITH_CODE (GtkScale, gtk_scale, GTK_TYPE_RANGE,
235
                         G_ADD_PRIVATE (GtkScale)
236 237
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
                                                gtk_scale_buildable_interface_init))
238

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
static gint
compare_marks (gconstpointer a, gconstpointer b, gpointer data)
{
  gboolean inverted = GPOINTER_TO_INT (data);
  gint val;
  const GtkScaleMark *ma, *mb;

  val = inverted ? -1 : 1;

  ma = a; mb = b;

  return (ma->value > mb->value) ? val : ((ma->value < mb->value) ? -val : 0);
}

static void
gtk_scale_notify (GObject    *object,
                  GParamSpec *pspec)
{
257
  if (strcmp (pspec->name, "inverted") == 0)
258 259
    {
      GtkScale *scale = GTK_SCALE (object);
260 261 262 263
      GtkScaleMark *mark;
      GSList *m;
      gint i, n;
      gdouble *values;
264 265 266 267

      scale->priv->marks = g_slist_sort_with_data (scale->priv->marks,
                                                   compare_marks,
                                                   GINT_TO_POINTER (gtk_range_get_inverted (GTK_RANGE (scale))));
268 269 270 271 272 273 274 275 276 277 278 279

      n = g_slist_length (scale->priv->marks);
      values = g_new (gdouble, n);
      for (m = scale->priv->marks, i = 0; m; m = m->next, i++)
        {
          mark = m->data;
          values[i] = mark->value;
        }

      _gtk_range_set_stop_values (GTK_RANGE (scale), values, n);

      g_free (values);
280
    }
281 282

  if (G_OBJECT_CLASS (gtk_scale_parent_class)->notify)
283 284 285
    G_OBJECT_CLASS (gtk_scale_parent_class)->notify (object, pspec);
}

286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
static void
gtk_scale_allocate_value (GtkScale      *scale,
                          GtkAllocation *out_clip)
{
  GtkScalePrivate *priv = scale->priv;
  GtkWidget *widget = GTK_WIDGET (scale);
  GtkRange *range = GTK_RANGE (widget);
  GtkCssGadget *range_gadget, *slider_gadget;
  GtkAllocation range_alloc, slider_alloc, value_alloc;

  range_gadget = gtk_range_get_gadget (range);
  gtk_css_gadget_get_margin_allocation (range_gadget, &range_alloc, NULL);

  slider_gadget = gtk_range_get_slider_gadget (range);
  gtk_css_gadget_get_border_allocation (slider_gadget, &slider_alloc, NULL);

  gtk_css_gadget_get_preferred_size (priv->value_gadget,
                                     GTK_ORIENTATION_HORIZONTAL, -1,
                                     &value_alloc.width, NULL,
                                     NULL, NULL);
  gtk_css_gadget_get_preferred_size (priv->value_gadget,
                                     GTK_ORIENTATION_VERTICAL, -1,
                                     &value_alloc.height, NULL,
                                     NULL, NULL);

  if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL)
    {
      switch (priv->value_pos)
        {
        case GTK_POS_LEFT:
          value_alloc.x = range_alloc.x;
          value_alloc.y = range_alloc.y + (range_alloc.height - value_alloc.height) / 2;
          break;

        case GTK_POS_RIGHT:
          value_alloc.x = range_alloc.x + range_alloc.width - value_alloc.width;
          value_alloc.y = range_alloc.y + (range_alloc.height - value_alloc.height) / 2;
          break;

        case GTK_POS_TOP:
          value_alloc.x = slider_alloc.x + (slider_alloc.width - value_alloc.width) / 2;
          value_alloc.x = CLAMP (value_alloc.x, range_alloc.x, range_alloc.x + range_alloc.width - value_alloc.width);
          value_alloc.y = range_alloc.y;
          break;

        case GTK_POS_BOTTOM:
          value_alloc.x = slider_alloc.x + (slider_alloc.width - value_alloc.width) / 2;
          value_alloc.x = CLAMP (value_alloc.x, range_alloc.x, range_alloc.x + range_alloc.width - value_alloc.width);
          value_alloc.y = range_alloc.y + range_alloc.height - value_alloc.height;
          break;

        default:
          g_return_if_reached ();
          break;
        }
    }
  else
    {
      switch (priv->value_pos)
        {
        case GTK_POS_LEFT:
          value_alloc.x = range_alloc.x;
          value_alloc.y = slider_alloc.y + (slider_alloc.height - value_alloc.height) / 2;
          value_alloc.y = CLAMP (value_alloc.y, range_alloc.y, range_alloc.y + range_alloc.height - value_alloc.height);
          break;

        case GTK_POS_RIGHT:
          value_alloc.x = range_alloc.x + range_alloc.width - value_alloc.width;
          value_alloc.y = slider_alloc.y + (slider_alloc.height - value_alloc.height) / 2;
          value_alloc.y = CLAMP (value_alloc.y, range_alloc.y, range_alloc.y + range_alloc.height - value_alloc.height);
          break;

        case GTK_POS_TOP:
          value_alloc.x = range_alloc.x + (range_alloc.width - value_alloc.width) / 2;
          value_alloc.y = range_alloc.y;
          break;

        case GTK_POS_BOTTOM:
          value_alloc.x = range_alloc.x + (range_alloc.width - value_alloc.width) / 2;
          value_alloc.y = range_alloc.y + range_alloc.height - value_alloc.height;
          break;

        default:
          g_return_if_reached ();
        }
    }

  gtk_css_gadget_allocate (priv->value_gadget,
                           &value_alloc, -1,
                           out_clip);
}

378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
static void
gtk_scale_allocate_mark (GtkCssGadget        *gadget,
                         const GtkAllocation *allocation,
                         int                  baseline,
                         GtkAllocation       *out_clip,
                         gpointer             user_data)
{
  GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
  GtkScaleMark *mark = user_data;
  GtkAllocation indicator_alloc, widget_alloc;
  int indicator_width, indicator_height;
  GtkOrientation orientation;

  orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
  gtk_widget_get_allocation (widget, &widget_alloc);
  gtk_css_gadget_get_preferred_size (mark->indicator_gadget,
                                     GTK_ORIENTATION_HORIZONTAL, -1,
                                     &indicator_width, NULL,
                                     NULL, NULL);
  gtk_css_gadget_get_preferred_size (mark->indicator_gadget,
                                     GTK_ORIENTATION_VERTICAL, -1,
                                     &indicator_height, NULL,
                                     NULL, NULL);

  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    {
      indicator_alloc.x = mark->stop_position + widget_alloc.x - indicator_width / 2;
      if (mark->position == GTK_POS_TOP)
        indicator_alloc.y = allocation->y + allocation->height - indicator_height;
      else
        indicator_alloc.y = allocation->y;
      indicator_alloc.width = indicator_width;
      indicator_alloc.height = indicator_height;
    }
  else
    {
      if (mark->position == GTK_POS_TOP)
        indicator_alloc.x = allocation->x + allocation->width - indicator_width;
      else
        indicator_alloc.x = allocation->x;
      indicator_alloc.y = mark->stop_position + widget_alloc.y - indicator_height / 2;
      indicator_alloc.width = indicator_width;
      indicator_alloc.height = indicator_height;
    }

  gtk_css_gadget_allocate (mark->indicator_gadget,
                           &indicator_alloc, baseline,
                           out_clip);

  if (mark->label_gadget)
    {
      GtkAllocation label_alloc, label_clip;

      label_alloc = *allocation;

      if (orientation == GTK_ORIENTATION_HORIZONTAL)
        {
          label_alloc.height = allocation->height - indicator_alloc.height;
          if (mark->position == GTK_POS_BOTTOM)
            label_alloc.y = indicator_alloc.y + indicator_alloc.height;
        }
      else
        {
          label_alloc.width = allocation->width - indicator_alloc.width;
          if (mark->position == GTK_POS_BOTTOM)
            label_alloc.x = indicator_alloc.x + indicator_alloc.width;
        }

      gtk_css_gadget_allocate (mark->label_gadget,
                               &label_alloc, baseline,
                               &label_clip);
      gdk_rectangle_union (out_clip, &label_clip, out_clip);
    }
}

453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
static gint
find_next_pos (GtkWidget       *widget,
               GSList          *list,
               gint            *marks,
               GtkPositionType  pos)
{
  GtkAllocation allocation;
  GSList *m;
  gint i;

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

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

  gtk_widget_get_allocation (widget, &allocation);
  if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
    return allocation.width;
  else
    return allocation.height;
}

static void
gtk_scale_allocate_marks (GtkCssGadget        *gadget,
                          const GtkAllocation *allocation,
                          int                  baseline,
                          GtkAllocation       *out_clip,
                          gpointer             data)
{
  GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
  GtkScale *scale = GTK_SCALE (widget);
  GtkScalePrivate *priv = scale->priv;
  GtkOrientation orientation;
  int *marks;
  int min_pos_before, min_pos_after;
  int min_sep = 4;
  int i;
  int min_pos, max_pos;
  GSList *m;
  GtkAllocation widget_alloc;

  orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (scale));
  _gtk_range_get_stop_positions (GTK_RANGE (scale), &marks);
  gtk_widget_get_allocation (widget, &widget_alloc);

501 502 503 504 505
  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    min_pos_before = min_pos_after = widget_alloc.x;
  else
    min_pos_before = min_pos_after = widget_alloc.y;

506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
  for (m = priv->marks, i = 0; m; m = m->next, i++)
    {
      GtkScaleMark *mark = m->data;
      GtkAllocation mark_alloc, mark_clip;
      int mark_size;

      if ((mark->position == GTK_POS_TOP && gadget == priv->bottom_marks_gadget) ||
          (mark->position == GTK_POS_BOTTOM && gadget == priv->top_marks_gadget))
        continue;

      gtk_css_gadget_get_preferred_size (mark->gadget,
                                         orientation, -1,
                                         &mark_size, NULL,
                                         NULL, NULL);
      mark->stop_position = marks[i];

      if (orientation == GTK_ORIENTATION_HORIZONTAL)
        {
          mark_alloc.x = mark->stop_position + widget_alloc.x;
          mark_alloc.y = allocation->y;
          mark_alloc.width = mark_size;
          mark_alloc.height = allocation->height;

          if (mark->position == GTK_POS_TOP)
            {
              min_pos = min_pos_before;
              max_pos = find_next_pos (widget, m, marks + i, GTK_POS_TOP) - min_sep + widget_alloc.x;
            }
          else
            {
              min_pos = min_pos_after;
              max_pos = find_next_pos (widget, m, marks + i, GTK_POS_BOTTOM) - min_sep + widget_alloc.x;
            }

          mark_alloc.x -= mark_size / 2;

          if (mark_alloc.x < min_pos)
            mark_alloc.x = min_pos;
          if (mark_alloc.x + mark_size > max_pos)
            mark_alloc.x = max_pos - mark_size;
          if (mark_alloc.x < 0)
            mark_alloc.x = 0;

          if (mark->position == GTK_POS_TOP)
            min_pos_before = mark_alloc.x + mark_size + min_sep;
          else
            min_pos_after = mark_alloc.x + mark_size + min_sep;
        }
      else
        {
          mark_alloc.x = allocation->x;
          mark_alloc.y = mark->stop_position + widget_alloc.y;
          mark_alloc.width = allocation->width;
          mark_alloc.height = mark_size;

          if (mark->position == GTK_POS_TOP)
            {
              min_pos = min_pos_before;
              max_pos = find_next_pos (widget, m, marks + i, GTK_POS_TOP) - min_sep + widget_alloc.y;
            }
          else
            {
              min_pos = min_pos_after;
              max_pos = find_next_pos (widget, m, marks + i, GTK_POS_BOTTOM) - min_sep + widget_alloc.y;
            }

          mark_alloc.y -= mark_size / 2;

          if (mark_alloc.y < min_pos)
            mark_alloc.y = min_pos;
          if (mark_alloc.y + mark_size > max_pos)
            mark_alloc.y = max_pos - mark_size;
          if (mark_alloc.y < 0)
            mark_alloc.y = 0;

          if (mark->position == GTK_POS_TOP)
            min_pos_before = mark_alloc.y + mark_size + min_sep;
          else
            min_pos_after = mark_alloc.y + mark_size + min_sep;
        }

      gtk_css_gadget_allocate (mark->gadget, &mark_alloc, baseline, &mark_clip);
      gdk_rectangle_union (out_clip, &mark_clip, out_clip);
    }

  g_free (marks);
}

594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
static void
gtk_scale_size_allocate (GtkWidget     *widget,
                         GtkAllocation *allocation)
{
  GtkScale *scale = GTK_SCALE (widget);
  GtkScalePrivate *priv = scale->priv;
  GtkAllocation clip, marks_clip, range_rect, marks_rect;
  GtkOrientation orientation;

  GTK_WIDGET_CLASS (gtk_scale_parent_class)->size_allocate (widget, allocation);

  gtk_widget_get_clip (widget, &clip);
  orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
  gtk_range_get_range_rect (GTK_RANGE (scale), &range_rect);

  range_rect.x += allocation->x;
  range_rect.y += allocation->y;

  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    {
      int marks_height = 0;

      if (priv->top_marks_gadget)
        {
          gtk_css_gadget_get_preferred_size (priv->top_marks_gadget,
                                             GTK_ORIENTATION_VERTICAL, -1,
                                             &marks_height, NULL,
                                             NULL, NULL);
          marks_rect = range_rect;
          marks_rect.y -= marks_height;
          marks_rect.height = marks_height;
          gtk_css_gadget_allocate (priv->top_marks_gadget,
                                   &marks_rect,
                                   -1,
                                   &marks_clip);
          gdk_rectangle_union (&clip, &marks_clip, &clip);
        }

      if (priv->bottom_marks_gadget)
        {
          gtk_css_gadget_get_preferred_size (priv->bottom_marks_gadget,
                                             GTK_ORIENTATION_VERTICAL, -1,
                                             &marks_height, NULL,
                                             NULL, NULL);
          marks_rect = range_rect;
          marks_rect.y += range_rect.height;
          marks_rect.height = marks_height;
          gtk_css_gadget_allocate (priv->bottom_marks_gadget,
                                   &marks_rect,
                                   -1,
                                   &marks_clip);
          gdk_rectangle_union (&clip, &marks_clip, &clip);
        }
    }
  else
    {
      int marks_width = 0;

      if (priv->top_marks_gadget)
        {
          gtk_css_gadget_get_preferred_size (priv->top_marks_gadget,
                                             GTK_ORIENTATION_HORIZONTAL, -1,
                                             &marks_width, NULL,
                                             NULL, NULL);
          marks_rect = range_rect;
          marks_rect.x -= marks_width;
          marks_rect.width = marks_width;
          gtk_css_gadget_allocate (priv->top_marks_gadget,
                                   &marks_rect,
                                   -1,
                                   &marks_clip);
          gdk_rectangle_union (&clip, &marks_clip, &clip);
        }

      if (priv->bottom_marks_gadget)
        {
          gtk_css_gadget_get_preferred_size (priv->bottom_marks_gadget,
                                             GTK_ORIENTATION_HORIZONTAL, -1,
                                             &marks_width, NULL,
                                             NULL, NULL);
          marks_rect = range_rect;
          marks_rect.x += range_rect.width;
          marks_rect.width = marks_width;
          gtk_css_gadget_allocate (priv->bottom_marks_gadget,
                                   &marks_rect,
                                   -1,
                                   &marks_clip);
          gdk_rectangle_union (&clip, &marks_clip, &clip);
        }
    }

685 686 687 688 689 690 691 692
  if (priv->value_gadget)
    {
      GtkAllocation value_clip;

      gtk_scale_allocate_value (scale, &value_clip);
      gdk_rectangle_union (&clip, &value_clip, &clip);
    }

693 694 695
  gtk_widget_set_clip (widget, &clip);
}

696 697
#define add_slider_binding(binding_set, keyval, mask, scroll)              \
  gtk_binding_entry_add_signal (binding_set, keyval, mask,                 \
698
                                I_("move-slider"), 1, \
699 700
                                GTK_TYPE_SCROLL_TYPE, scroll)

Elliot Lee's avatar
Elliot Lee committed
701 702 703
static void
gtk_scale_class_init (GtkScaleClass *class)
{
704
  GObjectClass   *gobject_class;
705
  GtkWidgetClass *widget_class;
Manish Singh's avatar
Manish Singh committed
706 707
  GtkRangeClass  *range_class;
  GtkBindingSet  *binding_set;
708
  
709
  gobject_class = G_OBJECT_CLASS (class);
Elliot Lee's avatar
Elliot Lee committed
710
  range_class = (GtkRangeClass*) class;
711
  widget_class = (GtkWidgetClass*) class;
712
  
713 714
  gobject_class->set_property = gtk_scale_set_property;
  gobject_class->get_property = gtk_scale_get_property;
715
  gobject_class->notify = gtk_scale_notify;
716
  gobject_class->finalize = gtk_scale_finalize;
717

718
  widget_class->screen_changed = gtk_scale_screen_changed;
719
  widget_class->draw = gtk_scale_draw;
720
  widget_class->size_allocate = gtk_scale_size_allocate;
721 722
  widget_class->get_preferred_width = gtk_scale_get_preferred_width;
  widget_class->get_preferred_height = gtk_scale_get_preferred_height;
723

724
  range_class->get_range_border = gtk_scale_get_range_border;
725
  range_class->get_range_size_request = gtk_scale_get_range_size_request;
726 727 728

  class->get_layout_offsets = gtk_scale_real_get_layout_offsets;

Matthias Clasen's avatar
Matthias Clasen committed
729
  /**
730
   * GtkScale::format-value:
Matthias Clasen's avatar
Matthias Clasen committed
731 732 733 734 735 736 737
   * @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.
   *
738 739 740
   * If no user-provided handlers are installed, the value will be displayed on
   * its own, rounded according to the value of the #GtkScale:digits property.
   *
Matthias Clasen's avatar
Matthias Clasen committed
741
   * Here's an example signal handler which displays a value 1.0 as
742
   * with "-->1.0<--".
743
   * |[<!-- language="C" -->
Matthias Clasen's avatar
Matthias Clasen committed
744 745 746 747
   * static gchar*
   * format_value_callback (GtkScale *scale,
   *                        gdouble   value)
   * {
748
   *   return g_strdup_printf ("-->\%0.*g<--",
Matthias Clasen's avatar
Matthias Clasen committed
749 750 751 752
   *                           gtk_scale_get_digits (scale), value);
   *  }
   * ]|
   *
753
   * Returns: allocated string representing @value
Matthias Clasen's avatar
Matthias Clasen committed
754
   */
755
  signals[FORMAT_VALUE] =
756
    g_signal_new (I_("format-value"),
Manish Singh's avatar
Manish Singh committed
757
                  G_TYPE_FROM_CLASS (gobject_class),
758 759
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkScaleClass, format_value),
760
                  _gtk_single_string_accumulator, NULL,
761
                  _gtk_marshal_STRING__DOUBLE,
762 763
                  G_TYPE_STRING, 1,
                  G_TYPE_DOUBLE);
764

765 766 767
  properties[PROP_DIGITS] =
      g_param_spec_int ("digits",
                        P_("Digits"),
768
                        P_("The number of decimal places that are displayed in the value"),
769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
                        -1, MAX_DIGITS,
                        1,
                        GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

  properties[PROP_DRAW_VALUE] =
      g_param_spec_boolean ("draw-value",
                            P_("Draw Value"),
                            P_("Whether the current value is displayed as a string next to the slider"),
                            TRUE,
                            GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

  properties[PROP_HAS_ORIGIN] =
      g_param_spec_boolean ("has-origin",
                            P_("Has Origin"),
                            P_("Whether the scale has an origin"),
                            TRUE,
                            GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

  properties[PROP_VALUE_POS] =
      g_param_spec_enum ("value-pos",
                         P_("Value Position"),
                         P_("The position in which the current value is displayed"),
                         GTK_TYPE_POSITION_TYPE,
                         GTK_POS_TOP,
                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

  g_object_class_install_properties (gobject_class, LAST_PROP, properties);
796

797 798 799 800 801 802 803 804
  /**
   * GtkScale:slider-length:
   *
   * Length of scale's slider.
   *
   * Deprecated: 3.20: Use min-height/min-width CSS properties on the slider
   *   element instead. The value of this style property is ignored.
   */
805
  gtk_widget_class_install_style_property (widget_class,
806 807 808 809
                                           g_param_spec_int ("slider-length",
                                                             P_("Slider Length"),
                                                             P_("Length of scale's slider"),
                                                             0, G_MAXINT, 31,
810
                                                             GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
811

812 813 814 815 816 817 818 819
  /**
   * GtkScale:value-spacing:
   *
   * Space between value text and the slider/trough area.
   *
   * Deprecated: 3.20: Use min-height/min-width CSS properties on the value
   *   element instead. The value of this style property is ignored.
   */
820
  gtk_widget_class_install_style_property (widget_class,
821
					   g_param_spec_int ("value-spacing",
822 823
							     P_("Value spacing"),
							     P_("Space between value text and the slider/trough area"),
824 825 826
							     0,
							     G_MAXINT,
							     2,
827
							     GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
828 829 830 831 832 833 834
  
  /* 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);

835
  add_slider_binding (binding_set, GDK_KEY_Left, 0,
836 837
                      GTK_SCROLL_STEP_LEFT);

838
  add_slider_binding (binding_set, GDK_KEY_Left, GDK_CONTROL_MASK,
839 840
                      GTK_SCROLL_PAGE_LEFT);

841
  add_slider_binding (binding_set, GDK_KEY_KP_Left, 0,
842 843
                      GTK_SCROLL_STEP_LEFT);

844
  add_slider_binding (binding_set, GDK_KEY_KP_Left, GDK_CONTROL_MASK,
845 846
                      GTK_SCROLL_PAGE_LEFT);

847
  add_slider_binding (binding_set, GDK_KEY_Right, 0,
848 849
                      GTK_SCROLL_STEP_RIGHT);

850
  add_slider_binding (binding_set, GDK_KEY_Right, GDK_CONTROL_MASK,
851 852
                      GTK_SCROLL_PAGE_RIGHT);

853
  add_slider_binding (binding_set, GDK_KEY_KP_Right, 0,
854 855
                      GTK_SCROLL_STEP_RIGHT);

856
  add_slider_binding (binding_set, GDK_KEY_KP_Right, GDK_CONTROL_MASK,
857 858
                      GTK_SCROLL_PAGE_RIGHT);

859
  add_slider_binding (binding_set, GDK_KEY_Up, 0,
860 861
                      GTK_SCROLL_STEP_UP);

862
  add_slider_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK,
863 864
                      GTK_SCROLL_PAGE_UP);

865
  add_slider_binding (binding_set, GDK_KEY_KP_Up, 0,
866 867
                      GTK_SCROLL_STEP_UP);

868
  add_slider_binding (binding_set, GDK_KEY_KP_Up, GDK_CONTROL_MASK,
869 870
                      GTK_SCROLL_PAGE_UP);

871
  add_slider_binding (binding_set, GDK_KEY_Down, 0,
872 873
                      GTK_SCROLL_STEP_DOWN);

874
  add_slider_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK,
875 876
                      GTK_SCROLL_PAGE_DOWN);

877
  add_slider_binding (binding_set, GDK_KEY_KP_Down, 0,
878 879
                      GTK_SCROLL_STEP_DOWN);

880
  add_slider_binding (binding_set, GDK_KEY_KP_Down, GDK_CONTROL_MASK,
881 882
                      GTK_SCROLL_PAGE_DOWN);
   
883
  add_slider_binding (binding_set, GDK_KEY_Page_Up, GDK_CONTROL_MASK,
884
                      GTK_SCROLL_PAGE_LEFT);
885

886
  add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, GDK_CONTROL_MASK,
887 888
                      GTK_SCROLL_PAGE_LEFT);  

889
  add_slider_binding (binding_set, GDK_KEY_Page_Up, 0,
890
                      GTK_SCROLL_PAGE_UP);
891

892
  add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, 0,
893 894
                      GTK_SCROLL_PAGE_UP);
  
895
  add_slider_binding (binding_set, GDK_KEY_Page_Down, GDK_CONTROL_MASK,
896
                      GTK_SCROLL_PAGE_RIGHT);
897

898
  add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, GDK_CONTROL_MASK,
899 900
                      GTK_SCROLL_PAGE_RIGHT);

901
  add_slider_binding (binding_set, GDK_KEY_Page_Down, 0,
902
                      GTK_SCROLL_PAGE_DOWN);
903

904
  add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, 0,
905 906 907 908
                      GTK_SCROLL_PAGE_DOWN);

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

909
  add_slider_binding (binding_set, GDK_KEY_plus, 0,
910 911
                      GTK_SCROLL_STEP_FORWARD);  

912
  add_slider_binding (binding_set, GDK_KEY_minus, 0,
913 914
                      GTK_SCROLL_STEP_BACKWARD);  

915
  add_slider_binding (binding_set, GDK_KEY_plus, GDK_CONTROL_MASK,
916 917
                      GTK_SCROLL_PAGE_FORWARD);  

918
  add_slider_binding (binding_set, GDK_KEY_minus, GDK_CONTROL_MASK,
919 920 921
                      GTK_SCROLL_PAGE_BACKWARD);


922
  add_slider_binding (binding_set, GDK_KEY_KP_Add, 0,
923 924
                      GTK_SCROLL_STEP_FORWARD);  

925
  add_slider_binding (binding_set, GDK_KEY_KP_Subtract, 0,
926 927
                      GTK_SCROLL_STEP_BACKWARD);  

928
  add_slider_binding (binding_set, GDK_KEY_KP_Add, GDK_CONTROL_MASK,
929 930
                      GTK_SCROLL_PAGE_FORWARD);  

931
  add_slider_binding (binding_set, GDK_KEY_KP_Subtract, GDK_CONTROL_MASK,
932 933 934
                      GTK_SCROLL_PAGE_BACKWARD);
  
  
935
  add_slider_binding (binding_set, GDK_KEY_Home, 0,
936 937
                      GTK_SCROLL_START);

938
  add_slider_binding (binding_set, GDK_KEY_KP_Home, 0,
939 940
                      GTK_SCROLL_START);

941
  add_slider_binding (binding_set, GDK_KEY_End, 0,
942 943
                      GTK_SCROLL_END);

944
  add_slider_binding (binding_set, GDK_KEY_KP_End, 0,
945
                      GTK_SCROLL_END);
946

947
  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SCALE_ACCESSIBLE);
948
  gtk_widget_class_set_css_name (widget_class, "scale");
Elliot Lee's avatar
Elliot Lee committed
949 950
}

951 952 953
static void
gtk_scale_init (GtkScale *scale)
{
954
  GtkScalePrivate *priv;
955 956
  GtkRange *range = GTK_RANGE (scale);

957
  scale->priv = gtk_scale_get_instance_private (scale);
958 959
  priv = scale->priv;

960 961 962
  priv->value_pos = GTK_POS_TOP;
  priv->digits = 1;

963
  gtk_widget_set_can_focus (GTK_WIDGET (scale), TRUE);
964

965
  gtk_range_set_slider_size_fixed (range, TRUE);
966
  gtk_range_set_slider_use_min_size (range, TRUE);
967

968 969
  _gtk_range_set_has_origin (range, TRUE);

970
  gtk_scale_set_draw_value (scale, TRUE);
971
  gtk_range_set_round_digits (range, priv->digits);
972

973
  gtk_range_set_flippable (range, TRUE);
974 975
}

976
static void
977 978 979 980
gtk_scale_set_property (GObject      *object,
			guint         prop_id,
			const GValue *value,
			GParamSpec   *pspec)
981 982 983 984 985
{
  GtkScale *scale;

  scale = GTK_SCALE (object);

986
  switch (prop_id)
987
    {
988 989
    case PROP_DIGITS:
      gtk_scale_set_digits (scale, g_value_get_int (value));
990
      break;
991 992
    case PROP_DRAW_VALUE:
      gtk_scale_set_draw_value (scale, g_value_get_boolean (value));
993
      break;
994 995 996
    case PROP_HAS_ORIGIN:
      gtk_scale_set_has_origin (scale, g_value_get_boolean (value));
      break;
997 998
    case PROP_VALUE_POS:
      gtk_scale_set_value_pos (scale, g_value_get_enum (value));
999 1000
      break;
    default:
1001
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1002 1003 1004 1005 1006
      break;
    }
}

static void
1007 1008 1009 1010
gtk_scale_get_property (GObject      *object,
			guint         prop_id,
			GValue       *value,
			GParamSpec   *pspec)
1011
{
1012
  GtkScale *scale = GTK_SCALE (object);
1013
  GtkScalePrivate *priv = scale->priv;
1014

1015
  switch (prop_id)
1016
    {
1017
    case PROP_DIGITS:
1018
      g_value_set_int (value, priv->digits);
1019
      break;
1020
    case PROP_DRAW_VALUE:
1021
      g_value_set_boolean (value, priv->draw_value);
1022
      break;
1023 1024 1025
    case PROP_HAS_ORIGIN:
      g_value_set_boolean (value, gtk_scale_get_has_origin (scale));
      break;
1026
    case PROP_VALUE_POS:
1027
      g_value_set_enum (value, priv->value_pos);
1028 1029
      break;
    default:
1030
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1031 1032 1033 1034
      break;
    }
}

1035 1036
/**
 * gtk_scale_new:
1037
 * @orientation: the scale’s orientation.
1038
 * @adjustment: (nullable): the #GtkAdjustment which sets the range
1039
 *              of the scale, or %NULL to create a new adjustment.
1040 1041 1042
 *
 * Creates a new #GtkScale.
 *
1043
 * Returns: a new #GtkScale
1044
 *
1045
 * Since: 3.0
1046 1047 1048 1049
 **/
GtkWidget *
gtk_scale_new (GtkOrientation  orientation,
               GtkAdjustment  *adjustment)
1050
{
1051 1052
  g_return_val_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment),
                        NULL);
1053

1054 1055 1056 1057 1058
  return g_object_new (GTK_TYPE_SCALE,
                       "orientation", orientation,
                       "adjustment",  adjustment,
                       NULL);
}
1059

1060 1061
/**
 * gtk_scale_new_with_range:
1062
 * @orientation: the scale’s orientation.
1063 1064 1065 1066 1067 1068
 * @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)
1069
 * with the increment @step.  @step must be nonzero; it’s the distance
1070 1071 1072 1073 1074 1075 1076
 * 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.
 *
1077
 * Returns: a new #GtkScale
1078
 *
1079 1080
 * Since: 3.0
 */
1081 1082 1083 1084 1085 1086
GtkWidget *
gtk_scale_new_with_range (GtkOrientation orientation,
                          gdouble        min,
                          gdouble        max,
                          gdouble        step)
{
1087
  GtkAdjustment *adj;
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110
  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);
1111 1112
}

Matthias Clasen's avatar
Matthias Clasen committed
1113 1114 1115
/**
 * gtk_scale_set_digits:
 * @scale: a #GtkScale
1116 1117
 * @digits: the number of decimal places to display,
 *     e.g. use 1 to display 1.0, 2 to display 1.00, etc
1118
 *
1119
 * Sets the number of decimal places that are displayed in the value. Also
1120 1121 1122 1123
 * causes the value of the adjustment to be rounded to this number of digits,
 * so the retrieved value matches the displayed one, if #GtkScale:draw-value is
 * %TRUE when the value changes. If you want to enforce rounding the value when
 * #GtkScale:draw-value is %FALSE, you can set #GtkRange:round-digits instead.
1124 1125 1126
 *
 * Note that rounding to a small number of digits can interfere with
 * the smooth autoscrolling that is built into #GtkScale. As an alternative,
1127
 * you can use the #GtkScale::format-value signal to format the displayed
1128
 * value yourself.
Matthias Clasen's avatar
Matthias Clasen committed
1129
 */
Elliot Lee's avatar
Elliot Lee committed
1130 1131 1132 1133
void
gtk_scale_set_digits (GtkScale *scale,
		      gint      digits)
{
1134
  GtkScalePrivate *priv;
1135
  GtkRange *range;
1136

Elliot Lee's avatar
Elliot Lee committed
1137 1138
  g_return_if_fail (GTK_IS_SCALE (scale));

1139
  priv = scale->priv;
1140 1141
  range = GTK_RANGE (scale);
  
1142
  digits = CLAMP (digits, -1, MAX_DIGITS);
1143

1144
  if (priv->digits != digits)
Elliot Lee's avatar
Elliot Lee committed
1145
    {
1146
      priv->digits = digits;
1147 1148
      if (priv->draw_value)
        gtk_range_set_round_digits (range, digits);
1149

1150
      gtk_scale_clear_value_layout (scale);
1151
      gtk_widget_queue_resize (GTK_WIDGET (scale));
1152

1153
      g_object_notify_by_pspec (G_OBJECT (scale), properties[PROP_DIGITS]);
Elliot Lee's avatar
Elliot Lee committed
1154 1155 1156
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
1157 1158 1159 1160
/**
 * gtk_scale_get_digits:
 * @scale: a #GtkScale
 *
1161
 * Gets the number of decimal places that are displayed in the value.
Matthias Clasen's avatar
Matthias Clasen committed
1162
 *
1163
 * Returns: the number of decimal places that are displayed
Matthias Clasen's avatar
Matthias Clasen committed
1164
 */
1165 1166 1167 1168 1169
gint
gtk_scale_get_digits (GtkScale *scale)
{
  g_return_val_if_fail (GTK_IS_SCALE (scale), -1);

1170
  return scale->priv->digits;
1171 1172
}

1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197
static gboolean
gtk_scale_render_value (GtkCssGadget *gadget,
                        cairo_t      *cr,
                        int           x,
                        int           y,
                        int           width,
                        int           height,
                        gpointer      user_data)
{
  GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
  GtkScale *scale = GTK_SCALE (widget);
  GtkStyleContext *context;
  PangoLayout *layout;

  context = gtk_widget_get_style_context (widget);
  gtk_style_context_save_to_node (context, gtk_css_gadget_get_node (gadget));

  layout = gtk_scale_get_layout (scale);
  gtk_render_layout (context, cr, x, y, layout);

  gtk_style_context_restore (context);

  return FALSE;
}

1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218
static void
gtk_css_node_update_layout_attributes (GtkCssNode  *node,
                                       PangoLayout *layout)
{
  GtkCssStyle *style;
  PangoAttrList *attrs;
  PangoFontDescription *desc;

  style = gtk_css_node_get_style (node);

  attrs = gtk_css_style_get_pango_attributes (style);
  desc = gtk_css_style_get_pango_font (style);

  pango_layout_set_attributes (layout, attrs);
  pango_layout_set_font_description (layout, desc);

  if (attrs)
    pango_attr_list_unref (attrs);
  pango_font_description_free (desc);
}

1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243
static void
gtk_scale_measure_value (GtkCssGadget   *gadget,
                         GtkOrientation  orientation,
                         gint            for_size,
                         gint           *minimum,
                         gint           *natural,
                         gint           *minimum_baseline,
                         gint           *natural_baseline,
                         gpointer        user_data)
{
  GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
  GtkScale *scale = GTK_SCALE (widget);
  GtkScalePrivate *priv = scale->priv;
  int width, height;

  width = height = 0;

  if (priv->draw_value)
    {
      GtkAdjustment *adjustment;
      PangoLayout *layout;
      PangoRectangle logical_rect;
      gchar *txt;

      layout = gtk_widget_create_pango_layout (widget, NULL);
1244 1245
      gtk_css_node_update_layout_attributes (gtk_css_gadget_get_node (priv->value_gadget), layout);

1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268
      adjustment = gtk_range_get_adjustment (GTK_RANGE (scale));

      txt = gtk_scale_format_value (scale, gtk_adjustment_get_lower (adjustment));
      pango_layout_set_text (layout, txt, -1);
      g_free (txt);

      pango_layout_get_pixel_extents (layout, NULL, &logical_rect);

      width = logical_rect.width;
      height = logical_rect.height;

      txt = gtk_scale_format_value (scale, gtk_adjustment_get_upper (adjustment));
      pango_layout_set_text (layout, txt, -1);
      g_free (txt);

      pango_layout_get_pixel_extents (layout, NULL, &logical_rect);

      width = MAX (width, logical_rect.width);
      height = MAX (height, logical_rect.height);

      g_object_unref (layout);
    }

1269
  if (orientation == GTK_ORIENTATION_HORIZONTAL)
1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294
    *minimum = *natural = width;
  else
    *minimum = *natural = height;
}

static void
update_value_position (GtkScale *scale)
{
  GtkScalePrivate *priv = scale->priv;

  if (!priv->value_gadget)
    return;

  if (priv->value_pos == GTK_POS_TOP || priv->value_pos == GTK_POS_LEFT)
    {
      gtk_css_gadget_remove_class (priv->value_gadget, GTK_STYLE_CLASS_BOTTOM);
      gtk_css_gadget_add_class (priv->value_gadget, GTK_STYLE_CLASS_TOP);
    }
  else
    {
      gtk_css_gadget_remove_class (priv->value_gadget, GTK_STYLE_CLASS_TOP);
      gtk_css_gadget_add_class (priv->value_gadget, GTK_STYLE_CLASS_BOTTOM);
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
1295 1296 1297
/**
 * gtk_scale_set_draw_value:
 * @scale: a #GtkScale
Matthias Clasen's avatar
Matthias Clasen committed
1298
 * @draw_value: %TRUE to draw the value
Matthias Clasen's avatar
Matthias Clasen committed
1299 1300 1301 1302
 * 
 * Specifies whether the current value is displayed as a string next 
 * to the slider.
 */
Elliot Lee's avatar
Elliot Lee committed
1303 1304
void
gtk_scale_set_draw_value (GtkScale *scale,
1305
			  gboolean  draw_value)
Elliot Lee's avatar
Elliot Lee committed
1306
{
1307
  GtkScalePrivate *priv;
1308
  GtkWidget *widget;
1309

Elliot Lee's avatar
Elliot Lee committed
1310 1311
  g_return_if_fail (GTK_IS_SCALE (scale));

1312
  priv = scale->priv;
1313
  widget = GTK_WIDGET (scale);
1314

1315 1316
  draw_value = draw_value != FALSE;

1317
  if (priv->draw_value != draw_value)
Elliot Lee's avatar
Elliot Lee committed
1318
    {
1319
      priv->draw_value = draw_value;
1320
      if (draw_value)
1321 1322 1323
        {
          GtkCssNode *widget_node;

1324
          widget_node = gtk_widget_get_css_node (widget);
1325
          priv->value_gadget = gtk_css_custom_gadget_new ("value",
1326
                                                          widget, NULL, NULL,
1327 1328 1329 1330
                                                          gtk_scale_measure_value,
                                                          NULL,
                                                          gtk_scale_render_value,
                                                          NULL, NULL);
1331 1332
          g_signal_connect (gtk_css_gadget_get_node (priv->value_gadget), "style-changed",
                            G_CALLBACK (gtk_scale_value_style_changed), scale);
1333 1334 1335 1336 1337 1338

          if (priv->value_pos == GTK_POS_TOP || priv->value_pos == GTK_POS_LEFT)
            gtk_css_node_insert_after (widget_node, gtk_css_gadget_get_node (priv->value_gadget), NULL);
          else
            gtk_css_node_insert_before (widget_node, gtk_css_gadget_get_node (priv->value_gadget), NULL);

1339
          gtk_range_set_round_digits (GTK_RANGE (scale), priv->digits);
1340 1341
          update_value_position (scale);
        }
1342
      else
1343 1344 1345 1346
        {
          if (priv->value_gadget)
            gtk_css_node_set_parent (gtk_css_gadget_get_node (priv->value_gadget), NULL);
          g_clear_object (&priv->value_gadget);
1347 1348

          gtk_range_set_round_digits (GTK_RANGE (scale), -1);
1349
        }
Elliot Lee's avatar
Elliot Lee committed
1350

1351
      gtk_scale_clear_value_layout (scale);
1352

1353
      gtk_widget_queue_resize (widget);
1354

1355
      g_object_notify_by_pspec (G_OBJECT (scale), properties[PROP_DRAW_VALUE]);
Elliot Lee's avatar
Elliot Lee committed
1356 1357 1358
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
1359 1360 1361 1362 1363 1364 1365 1366 1367
/**
 * 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
 */
1368 1369 1370 1371 1372
gboolean
gtk_scale_get_draw_value (GtkScale *scale)
{
  g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE);

1373
  return scale->priv->draw_value;
1374 1375
}

1376 1377 1378 1379 1380
/**
 * gtk_scale_set_has_origin:
 * @scale: a #GtkScale
 * @has_origin: %TRUE if the scale has an origin
 * 
1381 1382
 * If #GtkScale:has-origin is set to %TRUE (the default), the scale will
 * highlight the part of the trough between the origin (bottom or left side)
1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400
 * and the current value.
 *
 * Since: 3.4
 */
void
gtk_scale_set_has_origin (GtkScale *scale,
                          gboolean  has_origin)
{
  g_return_if_fail (GTK_IS_SCALE (scale));

  has_origin = has_origin != FALSE;

  if (_gtk_range_get_has_origin (GTK_RANGE (scale)) != has_origin)
    {
      _gtk_range_set_has_origin (GTK_RANGE (scale), has_origin);

      gtk_widget_queue_draw (GTK_WIDGET (scale));

1401
      g_object_notify_by_pspec (G_OBJECT (scale), properties[PROP_HAS_ORIGIN]);
1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422
    }
}

/**
 * gtk_scale_get_has_origin:
 * @scale: a #GtkScale
 *
 * Returns whether the scale has an origin.
 *
 * Returns: %TRUE if the scale has an origin.
 * 
 * Since: 3.4
 */
gboolean
gtk_scale_get_has_origin (GtkScale *scale)
{
  g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE);

  return _gtk_range_get_has_origin (GTK_RANGE (scale));
}

Matthias Clasen's avatar
Matthias Clasen committed
1423 1424 1425 1426 1427 1428 1429
/**
 * 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
1430 1431 1432 1433
void
gtk_scale_set_value_pos (GtkScale        *scale,
			 GtkPositionType  pos)
{
1434
  GtkScalePrivate *priv;
1435 1436
  GtkWidget *widget;

Elliot Lee's avatar
Elliot Lee committed
1437 1438
  g_return_if_fail (GTK_IS_SCALE (scale));

1439 1440 1441
  priv = scale->priv;

  if (priv->value_pos != pos)
Elliot Lee's avatar
Elliot Lee committed
1442
    {
1443
      priv->value_pos = pos;
1444
      widget = GTK_WIDGET (scale);
Elliot Lee's avatar
Elliot Lee committed
1445

1446
      gtk_scale_clear_value_layout (scale);
1447 1448
      update_value_position (scale);

1449
      if (gtk_widget_get_visible (widget) && gtk_widget_get_mapped (widget))
1450
	gtk_widget_queue_resize (widget);
1451

1452
      g_object_notify_by_pspec (G_OBJECT (scale), properties[PROP_VALUE_POS]);
Elliot Lee's avatar
Elliot Lee committed
1453 1454 1455
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
1456 1457 1458 1459 1460 1461 1462 1463
/**
 * 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
 */
1464 1465 1466 1467 1468
GtkPositionType
gtk_scale_get_value_pos (GtkScale *scale)
{
  g_return_val_if_fail (GTK_IS_SCALE (scale), 0);

1469
  return scale->priv->value_pos;
1470 1471 1472 1473 1474 1475
}

static void
gtk_scale_get_range_border (GtkRange  *range,
                            GtkBorder *border)
{
1476
  GtkScalePrivate *priv;
1477
  GtkScale *scale;
1478

1479
  scale = GTK_SCALE (range);
1480
  priv = scale->priv;
1481 1482 1483 1484 1485 1486

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

1487
  if (priv->value_gadget)
1488
    {
1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501
      int value_size;
      GtkOrientation value_orientation;

      if (priv->value_pos == GTK_POS_LEFT || priv->value_pos == GTK_POS_RIGHT)
        value_orientation = GTK_ORIENTATION_HORIZONTAL;
      else
        value_orientation = GTK_ORIENTATION_VERTICAL;

      gtk_css_gadget_get_preferred_size (priv->value_gadget,
                                         value_orientation, -1,
                                         &value_size, NULL,
                                         NULL, NULL);

1502
      switch (priv->value_pos)
1503 1504
        {
        case GTK_POS_LEFT:
1505
          border->left += value_size;
1506 1507
          break;
        case GTK_POS_RIGHT:
1508
          border->right += value_size;
1509 1510
          break;
        case GTK_POS_TOP:
1511
          border->top += value_size;
1512 1513
          break;
        case GTK_POS_BOTTOM:
1514
          border->bottom += value_size;
1515 1516 1517
          break;
        }
    }
1518

1519
  if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL)
1520
    {
1521
      int height;
1522

1523 1524 1525 1526 1527 1528 1529
      if (priv->top_marks_gadget)
        {
          gtk_css_gadget_get_preferred_size (priv->top_marks_gadget,
                                             GTK_ORIENTATION_VERTICAL, -1,
                                             &height, NULL,
                                             NULL, NULL);
          if (height > 0)
1530
            border->top += height;