]> Pileus Git - ~andy/gtk/blob - gtk/gtkscale.c
GtkScale: Always chain up in notify
[~andy/gtk] / gtk / gtkscale.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  * Copyright (C) 2001 Red Hat, Inc.
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
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 /*
22  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
23  * file for a list of people on the GTK+ Team.  See the ChangeLog
24  * 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
30 #include <math.h>
31 #include <stdlib.h>
32
33 #include "gtkscaleprivate.h"
34 #include "gtkiconfactory.h"
35 #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 #include "gtkbuildable.h"
43 #include "gtkbuilderprivate.h"
44
45 #include "a11y/gtkscaleaccessible.h"
46
47
48 /**
49  * SECTION:gtkscale
50  * @Short_description: A slider widget for selecting a value from a range
51  * @Title: GtkScale
52  *
53  * A GtkScale is a slider control used to select a numeric value. 
54  * To use it, you'll probably want to investigate the methods on
55  * its base class, #GtkRange, in addition to the methods for GtkScale itself.
56  * To set the value of a scale, you would normally use gtk_range_set_value().
57  * To detect changes to the value, you would normally use the
58  * #GtkRange::value-changed signal.
59  *
60  * Note that using the same upper and lower bounds for the #GtkScale (through
61  * the #GtkRange methods) will hide the slider itself. This is useful for
62  * applications that want to show an undeterminate value on the scale, without
63  * changing the layout of the application (such as movie or music players).
64  *
65  * <refsect2 id="GtkScale-BUILDER-UI"><title>GtkScale as GtkBuildable</title>
66  * GtkScale supports a custom &lt;marks&gt; element, which
67  * can contain multiple &lt;mark&gt; elements. The "value" and "position"
68  * attributes have the same meaning as gtk_scale_add_mark() parameters of the
69  * same name. If the element is not empty, its content is taken as the markup
70  * to show at the mark. It can be translated with the usual "translatable and
71  * "context" attributes.
72  * </refsect2>
73  */
74
75
76 #define MAX_DIGITS      (64)    /* don't change this,
77                                  * a) you don't need to and
78                                  * b) you might cause buffer owerflows in
79                                  *    unrelated code portions otherwise
80                                  */
81
82 typedef struct _GtkScaleMark GtkScaleMark;
83
84 struct _GtkScalePrivate
85 {
86   PangoLayout  *layout;
87
88   GSList       *marks;
89
90   gint          digits;
91
92   guint         draw_value : 1;
93   guint         value_pos  : 2;
94 };
95
96 struct _GtkScaleMark
97 {
98   gdouble          value;
99   gchar           *markup;
100   GtkPositionType  position; /* always GTK_POS_TOP or GTK_POS_BOTTOM */
101 };
102
103 enum {
104   PROP_0,
105   PROP_DIGITS,
106   PROP_DRAW_VALUE,
107   PROP_HAS_ORIGIN,
108   PROP_VALUE_POS
109 };
110
111 enum {
112   FORMAT_VALUE,
113   LAST_SIGNAL
114 };
115
116 static guint signals[LAST_SIGNAL];
117
118 static void     gtk_scale_set_property            (GObject        *object,
119                                                    guint           prop_id,
120                                                    const GValue   *value,
121                                                    GParamSpec     *pspec);
122 static void     gtk_scale_get_property            (GObject        *object,
123                                                    guint           prop_id,
124                                                    GValue         *value,
125                                                    GParamSpec     *pspec);
126 static void     gtk_scale_get_preferred_width     (GtkWidget      *widget,
127                                                    gint           *minimum,
128                                                    gint           *natural);
129 static void     gtk_scale_get_preferred_height    (GtkWidget      *widget,
130                                                    gint           *minimum,
131                                                    gint           *natural);
132 static void     gtk_scale_style_updated           (GtkWidget      *widget);
133 static void     gtk_scale_get_range_border        (GtkRange       *range,
134                                                    GtkBorder      *border);
135 static void     gtk_scale_get_mark_label_size     (GtkScale        *scale,
136                                                    GtkPositionType  position,
137                                                    gint            *count1,
138                                                    gint            *width1,
139                                                    gint            *height1,
140                                                    gint            *count2,
141                                                    gint            *width2,
142                                                    gint            *height2);
143 static void     gtk_scale_finalize                (GObject        *object);
144 static void     gtk_scale_screen_changed          (GtkWidget      *widget,
145                                                    GdkScreen      *old_screen);
146 static gboolean gtk_scale_draw                    (GtkWidget      *widget,
147                                                    cairo_t        *cr);
148 static void     gtk_scale_real_get_layout_offsets (GtkScale       *scale,
149                                                    gint           *x,
150                                                    gint           *y);
151 static void     gtk_scale_buildable_interface_init   (GtkBuildableIface *iface);
152 static gboolean gtk_scale_buildable_custom_tag_start (GtkBuildable  *buildable,
153                                                       GtkBuilder    *builder,
154                                                       GObject       *child,
155                                                       const gchar   *tagname,
156                                                       GMarkupParser *parser,
157                                                       gpointer      *data);
158 static void     gtk_scale_buildable_custom_finished  (GtkBuildable  *buildable,
159                                                       GtkBuilder    *builder,
160                                                       GObject       *child,
161                                                       const gchar   *tagname,
162                                                       gpointer       user_data);
163
164
165 G_DEFINE_TYPE_WITH_CODE (GtkScale, gtk_scale, GTK_TYPE_RANGE,
166                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
167                                                 gtk_scale_buildable_interface_init))
168
169 static gint
170 compare_marks (gconstpointer a, gconstpointer b, gpointer data)
171 {
172   gboolean inverted = GPOINTER_TO_INT (data);
173   gint val;
174   const GtkScaleMark *ma, *mb;
175
176   val = inverted ? -1 : 1;
177
178   ma = a; mb = b;
179
180   return (ma->value > mb->value) ? val : ((ma->value < mb->value) ? -val : 0);
181 }
182
183 static void
184 gtk_scale_notify (GObject    *object,
185                   GParamSpec *pspec)
186 {
187   if (strcmp (pspec->name, "orientation") == 0)
188     {
189       GtkOrientation orientation;
190
191       orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (object));
192       gtk_range_set_flippable (GTK_RANGE (object),
193                                orientation == GTK_ORIENTATION_HORIZONTAL);
194     }
195   else if (strcmp (pspec->name, "inverted") == 0)
196     {
197       GtkScale *scale = GTK_SCALE (object);
198
199       scale->priv->marks = g_slist_sort_with_data (scale->priv->marks,
200                                                    compare_marks,
201                                                    GINT_TO_POINTER (gtk_range_get_inverted (GTK_RANGE (scale))));
202     }
203
204   if (G_OBJECT_CLASS (gtk_scale_parent_class)->notify)
205     G_OBJECT_CLASS (gtk_scale_parent_class)->notify (object, pspec);
206 }
207
208
209 #define add_slider_binding(binding_set, keyval, mask, scroll)              \
210   gtk_binding_entry_add_signal (binding_set, keyval, mask,                 \
211                                 I_("move-slider"), 1, \
212                                 GTK_TYPE_SCROLL_TYPE, scroll)
213
214 static void
215 gtk_scale_class_init (GtkScaleClass *class)
216 {
217   GObjectClass   *gobject_class;
218   GtkWidgetClass *widget_class;
219   GtkRangeClass  *range_class;
220   GtkBindingSet  *binding_set;
221   
222   gobject_class = G_OBJECT_CLASS (class);
223   range_class = (GtkRangeClass*) class;
224   widget_class = (GtkWidgetClass*) class;
225   
226   gobject_class->set_property = gtk_scale_set_property;
227   gobject_class->get_property = gtk_scale_get_property;
228   gobject_class->notify = gtk_scale_notify;
229   gobject_class->finalize = gtk_scale_finalize;
230
231   widget_class->style_updated = gtk_scale_style_updated;
232   widget_class->screen_changed = gtk_scale_screen_changed;
233   widget_class->draw = gtk_scale_draw;
234   widget_class->get_preferred_width = gtk_scale_get_preferred_width;
235   widget_class->get_preferred_height = gtk_scale_get_preferred_height;
236
237   range_class->slider_detail = "Xscale";
238   range_class->get_range_border = gtk_scale_get_range_border;
239
240   class->get_layout_offsets = gtk_scale_real_get_layout_offsets;
241
242   /**
243    * GtkScale::format-value:
244    * @scale: the object which received the signal
245    * @value: the value to format
246    *
247    * Signal which allows you to change how the scale value is displayed.
248    * Connect a signal handler which returns an allocated string representing 
249    * @value. That string will then be used to display the scale's value.
250    *
251    * Here's an example signal handler which displays a value 1.0 as
252    * with "--&gt;1.0&lt;--".
253    * |[
254    * static gchar*
255    * format_value_callback (GtkScale *scale,
256    *                        gdouble   value)
257    * {
258    *   return g_strdup_printf ("--&gt;&percnt;0.*g&lt;--",
259    *                           gtk_scale_get_digits (scale), value);
260    *  }
261    * ]|
262    *
263    * Return value: allocated string representing @value
264    */
265   signals[FORMAT_VALUE] =
266     g_signal_new (I_("format-value"),
267                   G_TYPE_FROM_CLASS (gobject_class),
268                   G_SIGNAL_RUN_LAST,
269                   G_STRUCT_OFFSET (GtkScaleClass, format_value),
270                   _gtk_single_string_accumulator, NULL,
271                   _gtk_marshal_STRING__DOUBLE,
272                   G_TYPE_STRING, 1,
273                   G_TYPE_DOUBLE);
274
275   g_object_class_install_property (gobject_class,
276                                    PROP_DIGITS,
277                                    g_param_spec_int ("digits",
278                                                      P_("Digits"),
279                                                      P_("The number of decimal places that are displayed in the value"),
280                                                      -1,
281                                                      MAX_DIGITS,
282                                                      1,
283                                                      GTK_PARAM_READWRITE));
284   
285   g_object_class_install_property (gobject_class,
286                                    PROP_DRAW_VALUE,
287                                    g_param_spec_boolean ("draw-value",
288                                                          P_("Draw Value"),
289                                                          P_("Whether the current value is displayed as a string next to the slider"),
290                                                          TRUE,
291                                                          GTK_PARAM_READWRITE));
292
293   g_object_class_install_property (gobject_class,
294                                    PROP_HAS_ORIGIN,
295                                    g_param_spec_boolean ("has-origin",
296                                                          P_("Has Origin"),
297                                                          P_("Whether the scale has an origin"),
298                                                          TRUE,
299                                                          GTK_PARAM_READWRITE));
300
301   g_object_class_install_property (gobject_class,
302                                    PROP_VALUE_POS,
303                                    g_param_spec_enum ("value-pos",
304                                                       P_("Value Position"),
305                                                       P_("The position in which the current value is displayed"),
306                                                       GTK_TYPE_POSITION_TYPE,
307                                                       GTK_POS_TOP,
308                                                       GTK_PARAM_READWRITE));
309
310   gtk_widget_class_install_style_property (widget_class,
311                                            g_param_spec_int ("slider-length",
312                                                              P_("Slider Length"),
313                                                              P_("Length of scale's slider"),
314                                                              0,
315                                                              G_MAXINT,
316                                                              31,
317                                                              GTK_PARAM_READABLE));
318
319   gtk_widget_class_install_style_property (widget_class,
320                                            g_param_spec_int ("value-spacing",
321                                                              P_("Value spacing"),
322                                                              P_("Space between value text and the slider/trough area"),
323                                                              0,
324                                                              G_MAXINT,
325                                                              2,
326                                                              GTK_PARAM_READABLE));
327   
328   /* All bindings (even arrow keys) are on both h/v scale, because
329    * blind users etc. don't care about scale orientation.
330    */
331   
332   binding_set = gtk_binding_set_by_class (class);
333
334   add_slider_binding (binding_set, GDK_KEY_Left, 0,
335                       GTK_SCROLL_STEP_LEFT);
336
337   add_slider_binding (binding_set, GDK_KEY_Left, GDK_CONTROL_MASK,
338                       GTK_SCROLL_PAGE_LEFT);
339
340   add_slider_binding (binding_set, GDK_KEY_KP_Left, 0,
341                       GTK_SCROLL_STEP_LEFT);
342
343   add_slider_binding (binding_set, GDK_KEY_KP_Left, GDK_CONTROL_MASK,
344                       GTK_SCROLL_PAGE_LEFT);
345
346   add_slider_binding (binding_set, GDK_KEY_Right, 0,
347                       GTK_SCROLL_STEP_RIGHT);
348
349   add_slider_binding (binding_set, GDK_KEY_Right, GDK_CONTROL_MASK,
350                       GTK_SCROLL_PAGE_RIGHT);
351
352   add_slider_binding (binding_set, GDK_KEY_KP_Right, 0,
353                       GTK_SCROLL_STEP_RIGHT);
354
355   add_slider_binding (binding_set, GDK_KEY_KP_Right, GDK_CONTROL_MASK,
356                       GTK_SCROLL_PAGE_RIGHT);
357
358   add_slider_binding (binding_set, GDK_KEY_Up, 0,
359                       GTK_SCROLL_STEP_UP);
360
361   add_slider_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK,
362                       GTK_SCROLL_PAGE_UP);
363
364   add_slider_binding (binding_set, GDK_KEY_KP_Up, 0,
365                       GTK_SCROLL_STEP_UP);
366
367   add_slider_binding (binding_set, GDK_KEY_KP_Up, GDK_CONTROL_MASK,
368                       GTK_SCROLL_PAGE_UP);
369
370   add_slider_binding (binding_set, GDK_KEY_Down, 0,
371                       GTK_SCROLL_STEP_DOWN);
372
373   add_slider_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK,
374                       GTK_SCROLL_PAGE_DOWN);
375
376   add_slider_binding (binding_set, GDK_KEY_KP_Down, 0,
377                       GTK_SCROLL_STEP_DOWN);
378
379   add_slider_binding (binding_set, GDK_KEY_KP_Down, GDK_CONTROL_MASK,
380                       GTK_SCROLL_PAGE_DOWN);
381    
382   add_slider_binding (binding_set, GDK_KEY_Page_Up, GDK_CONTROL_MASK,
383                       GTK_SCROLL_PAGE_LEFT);
384
385   add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, GDK_CONTROL_MASK,
386                       GTK_SCROLL_PAGE_LEFT);  
387
388   add_slider_binding (binding_set, GDK_KEY_Page_Up, 0,
389                       GTK_SCROLL_PAGE_UP);
390
391   add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, 0,
392                       GTK_SCROLL_PAGE_UP);
393   
394   add_slider_binding (binding_set, GDK_KEY_Page_Down, GDK_CONTROL_MASK,
395                       GTK_SCROLL_PAGE_RIGHT);
396
397   add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, GDK_CONTROL_MASK,
398                       GTK_SCROLL_PAGE_RIGHT);
399
400   add_slider_binding (binding_set, GDK_KEY_Page_Down, 0,
401                       GTK_SCROLL_PAGE_DOWN);
402
403   add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, 0,
404                       GTK_SCROLL_PAGE_DOWN);
405
406   /* Logical bindings (vs. visual bindings above) */
407
408   add_slider_binding (binding_set, GDK_KEY_plus, 0,
409                       GTK_SCROLL_STEP_FORWARD);  
410
411   add_slider_binding (binding_set, GDK_KEY_minus, 0,
412                       GTK_SCROLL_STEP_BACKWARD);  
413
414   add_slider_binding (binding_set, GDK_KEY_plus, GDK_CONTROL_MASK,
415                       GTK_SCROLL_PAGE_FORWARD);  
416
417   add_slider_binding (binding_set, GDK_KEY_minus, GDK_CONTROL_MASK,
418                       GTK_SCROLL_PAGE_BACKWARD);
419
420
421   add_slider_binding (binding_set, GDK_KEY_KP_Add, 0,
422                       GTK_SCROLL_STEP_FORWARD);  
423
424   add_slider_binding (binding_set, GDK_KEY_KP_Subtract, 0,
425                       GTK_SCROLL_STEP_BACKWARD);  
426
427   add_slider_binding (binding_set, GDK_KEY_KP_Add, GDK_CONTROL_MASK,
428                       GTK_SCROLL_PAGE_FORWARD);  
429
430   add_slider_binding (binding_set, GDK_KEY_KP_Subtract, GDK_CONTROL_MASK,
431                       GTK_SCROLL_PAGE_BACKWARD);
432   
433   
434   add_slider_binding (binding_set, GDK_KEY_Home, 0,
435                       GTK_SCROLL_START);
436
437   add_slider_binding (binding_set, GDK_KEY_KP_Home, 0,
438                       GTK_SCROLL_START);
439
440   add_slider_binding (binding_set, GDK_KEY_End, 0,
441                       GTK_SCROLL_END);
442
443   add_slider_binding (binding_set, GDK_KEY_KP_End, 0,
444                       GTK_SCROLL_END);
445
446   g_type_class_add_private (gobject_class, sizeof (GtkScalePrivate));
447
448   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SCALE_ACCESSIBLE);
449 }
450
451 static void
452 gtk_scale_init (GtkScale *scale)
453 {
454   GtkScalePrivate *priv;
455   GtkRange *range = GTK_RANGE (scale);
456   GtkStyleContext *context;
457
458   scale->priv = G_TYPE_INSTANCE_GET_PRIVATE (scale,
459                                              GTK_TYPE_SCALE,
460                                              GtkScalePrivate);
461   priv = scale->priv;
462
463   gtk_widget_set_can_focus (GTK_WIDGET (scale), TRUE);
464
465   gtk_range_set_slider_size_fixed (range, TRUE);
466
467   _gtk_range_set_has_origin (range, TRUE);
468
469   priv->draw_value = TRUE;
470   priv->value_pos = GTK_POS_TOP;
471   priv->digits = 1;
472   gtk_range_set_round_digits (range, priv->digits);
473
474   gtk_range_set_flippable (range,
475                            gtk_orientable_get_orientation (GTK_ORIENTABLE (range))== GTK_ORIENTATION_HORIZONTAL);
476
477   context = gtk_widget_get_style_context (GTK_WIDGET (scale));
478   gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE);
479 }
480
481 static void
482 gtk_scale_set_property (GObject      *object,
483                         guint         prop_id,
484                         const GValue *value,
485                         GParamSpec   *pspec)
486 {
487   GtkScale *scale;
488
489   scale = GTK_SCALE (object);
490
491   switch (prop_id)
492     {
493     case PROP_DIGITS:
494       gtk_scale_set_digits (scale, g_value_get_int (value));
495       break;
496     case PROP_DRAW_VALUE:
497       gtk_scale_set_draw_value (scale, g_value_get_boolean (value));
498       break;
499     case PROP_HAS_ORIGIN:
500       gtk_scale_set_has_origin (scale, g_value_get_boolean (value));
501       break;
502     case PROP_VALUE_POS:
503       gtk_scale_set_value_pos (scale, g_value_get_enum (value));
504       break;
505     default:
506       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
507       break;
508     }
509 }
510
511 static void
512 gtk_scale_get_property (GObject      *object,
513                         guint         prop_id,
514                         GValue       *value,
515                         GParamSpec   *pspec)
516 {
517   GtkScale *scale = GTK_SCALE (object);
518   GtkScalePrivate *priv = scale->priv;
519
520   switch (prop_id)
521     {
522     case PROP_DIGITS:
523       g_value_set_int (value, priv->digits);
524       break;
525     case PROP_DRAW_VALUE:
526       g_value_set_boolean (value, priv->draw_value);
527       break;
528     case PROP_HAS_ORIGIN:
529       g_value_set_boolean (value, gtk_scale_get_has_origin (scale));
530       break;
531     case PROP_VALUE_POS:
532       g_value_set_enum (value, priv->value_pos);
533       break;
534     default:
535       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
536       break;
537     }
538 }
539
540 /**
541  * gtk_scale_new:
542  * @orientation: the scale's orientation.
543  * @adjustment: (allow-none): the #GtkAdjustment which sets the range
544  *              of the scale, or %NULL to create a new adjustment.
545  *
546  * Creates a new #GtkScale.
547  *
548  * Return value: a new #GtkScale
549  *
550  * Since: 3.0
551  **/
552 GtkWidget *
553 gtk_scale_new (GtkOrientation  orientation,
554                GtkAdjustment  *adjustment)
555 {
556   g_return_val_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment),
557                         NULL);
558
559   return g_object_new (GTK_TYPE_SCALE,
560                        "orientation", orientation,
561                        "adjustment",  adjustment,
562                        NULL);
563 }
564
565 /**
566  * gtk_scale_new_with_range:
567  * @orientation: the scale's orientation.
568  * @min: minimum value
569  * @max: maximum value
570  * @step: step increment (tick size) used with keyboard shortcuts
571  *
572  * Creates a new scale widget with the given orientation that lets the
573  * user input a number between @min and @max (including @min and @max)
574  * with the increment @step.  @step must be nonzero; it's the distance
575  * the slider moves when using the arrow keys to adjust the scale
576  * value.
577  *
578  * Note that the way in which the precision is derived works best if @step
579  * is a power of ten. If the resulting precision is not suitable for your
580  * needs, use gtk_scale_set_digits() to correct it.
581  *
582  * Return value: a new #GtkScale
583  *
584  * Since: 3.0
585  */
586 GtkWidget *
587 gtk_scale_new_with_range (GtkOrientation orientation,
588                           gdouble        min,
589                           gdouble        max,
590                           gdouble        step)
591 {
592   GtkAdjustment *adj;
593   gint digits;
594
595   g_return_val_if_fail (min < max, NULL);
596   g_return_val_if_fail (step != 0.0, NULL);
597
598   adj = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
599
600   if (fabs (step) >= 1.0 || step == 0.0)
601     {
602       digits = 0;
603     }
604   else
605     {
606       digits = abs ((gint) floor (log10 (fabs (step))));
607       if (digits > 5)
608         digits = 5;
609     }
610
611   return g_object_new (GTK_TYPE_SCALE,
612                        "orientation", orientation,
613                        "adjustment",  adj,
614                        "digits",      digits,
615                        NULL);
616 }
617
618 /**
619  * gtk_scale_set_digits:
620  * @scale: a #GtkScale
621  * @digits: the number of decimal places to display,
622  *     e.g. use 1 to display 1.0, 2 to display 1.00, etc
623  *
624  * Sets the number of decimal places that are displayed in the value.
625  * Also causes the value of the adjustment to be rounded off to this
626  * number of digits, so the retrieved value matches the value the user saw.
627  */
628 void
629 gtk_scale_set_digits (GtkScale *scale,
630                       gint      digits)
631 {
632   GtkScalePrivate *priv;
633   GtkRange *range;
634
635   g_return_if_fail (GTK_IS_SCALE (scale));
636
637   priv = scale->priv;
638   range = GTK_RANGE (scale);
639   
640   digits = CLAMP (digits, -1, MAX_DIGITS);
641
642   if (priv->digits != digits)
643     {
644       priv->digits = digits;
645       if (priv->draw_value)
646         gtk_range_set_round_digits (range, digits);
647
648       _gtk_scale_clear_layout (scale);
649       gtk_widget_queue_resize (GTK_WIDGET (scale));
650
651       g_object_notify (G_OBJECT (scale), "digits");
652     }
653 }
654
655 /**
656  * gtk_scale_get_digits:
657  * @scale: a #GtkScale
658  *
659  * Gets the number of decimal places that are displayed in the value.
660  *
661  * Returns: the number of decimal places that are displayed
662  */
663 gint
664 gtk_scale_get_digits (GtkScale *scale)
665 {
666   g_return_val_if_fail (GTK_IS_SCALE (scale), -1);
667
668   return scale->priv->digits;
669 }
670
671 /**
672  * gtk_scale_set_draw_value:
673  * @scale: a #GtkScale
674  * @draw_value: %TRUE to draw the value
675  * 
676  * Specifies whether the current value is displayed as a string next 
677  * to the slider.
678  */
679 void
680 gtk_scale_set_draw_value (GtkScale *scale,
681                           gboolean  draw_value)
682 {
683   GtkScalePrivate *priv;
684
685   g_return_if_fail (GTK_IS_SCALE (scale));
686
687   priv = scale->priv;
688
689   draw_value = draw_value != FALSE;
690
691   if (priv->draw_value != draw_value)
692     {
693       priv->draw_value = draw_value;
694       if (draw_value)
695         gtk_range_set_round_digits (GTK_RANGE (scale), priv->digits);
696       else
697         gtk_range_set_round_digits (GTK_RANGE (scale), -1);
698
699       _gtk_scale_clear_layout (scale);
700
701       gtk_widget_queue_resize (GTK_WIDGET (scale));
702
703       g_object_notify (G_OBJECT (scale), "draw-value");
704     }
705 }
706
707 /**
708  * gtk_scale_get_draw_value:
709  * @scale: a #GtkScale
710  *
711  * Returns whether the current value is displayed as a string 
712  * next to the slider.
713  *
714  * Returns: whether the current value is displayed as a string
715  */
716 gboolean
717 gtk_scale_get_draw_value (GtkScale *scale)
718 {
719   g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE);
720
721   return scale->priv->draw_value;
722 }
723
724 /**
725  * gtk_scale_set_has_origin:
726  * @scale: a #GtkScale
727  * @has_origin: %TRUE if the scale has an origin
728  * 
729  * If @has_origin is set to %TRUE (the default),
730  * the scale will highlight the part of the scale
731  * between the origin (bottom or left side) of the scale
732  * and the current value.
733  *
734  * Since: 3.4
735  */
736 void
737 gtk_scale_set_has_origin (GtkScale *scale,
738                           gboolean  has_origin)
739 {
740   g_return_if_fail (GTK_IS_SCALE (scale));
741
742   has_origin = has_origin != FALSE;
743
744   if (_gtk_range_get_has_origin (GTK_RANGE (scale)) != has_origin)
745     {
746       _gtk_range_set_has_origin (GTK_RANGE (scale), has_origin);
747
748       gtk_widget_queue_draw (GTK_WIDGET (scale));
749
750       g_object_notify (G_OBJECT (scale), "has-origin");
751     }
752 }
753
754 /**
755  * gtk_scale_get_has_origin:
756  * @scale: a #GtkScale
757  *
758  * Returns whether the scale has an origin.
759  *
760  * Returns: %TRUE if the scale has an origin.
761  * 
762  * Since: 3.4
763  */
764 gboolean
765 gtk_scale_get_has_origin (GtkScale *scale)
766 {
767   g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE);
768
769   return _gtk_range_get_has_origin (GTK_RANGE (scale));
770 }
771
772 /**
773  * gtk_scale_set_value_pos:
774  * @scale: a #GtkScale
775  * @pos: the position in which the current value is displayed
776  * 
777  * Sets the position in which the current value is displayed.
778  */
779 void
780 gtk_scale_set_value_pos (GtkScale        *scale,
781                          GtkPositionType  pos)
782 {
783   GtkScalePrivate *priv;
784   GtkWidget *widget;
785
786   g_return_if_fail (GTK_IS_SCALE (scale));
787
788   priv = scale->priv;
789
790   if (priv->value_pos != pos)
791     {
792       priv->value_pos = pos;
793       widget = GTK_WIDGET (scale);
794
795       _gtk_scale_clear_layout (scale);
796       if (gtk_widget_get_visible (widget) && gtk_widget_get_mapped (widget))
797         gtk_widget_queue_resize (widget);
798
799       g_object_notify (G_OBJECT (scale), "value-pos");
800     }
801 }
802
803 /**
804  * gtk_scale_get_value_pos:
805  * @scale: a #GtkScale
806  *
807  * Gets the position in which the current value is displayed.
808  *
809  * Returns: the position in which the current value is displayed
810  */
811 GtkPositionType
812 gtk_scale_get_value_pos (GtkScale *scale)
813 {
814   g_return_val_if_fail (GTK_IS_SCALE (scale), 0);
815
816   return scale->priv->value_pos;
817 }
818
819 static void
820 gtk_scale_get_range_border (GtkRange  *range,
821                             GtkBorder *border)
822 {
823   GtkScalePrivate *priv;
824   GtkWidget *widget;
825   GtkScale *scale;
826   gint w, h;
827   
828   widget = GTK_WIDGET (range);
829   scale = GTK_SCALE (range);
830   priv = scale->priv;
831
832   _gtk_scale_get_value_size (scale, &w, &h);
833
834   border->left = 0;
835   border->right = 0;
836   border->top = 0;
837   border->bottom = 0;
838
839   if (priv->draw_value)
840     {
841       gint value_spacing;
842       gtk_widget_style_get (widget, "value-spacing", &value_spacing, NULL);
843
844       switch (priv->value_pos)
845         {
846         case GTK_POS_LEFT:
847           border->left += w + value_spacing;
848           break;
849         case GTK_POS_RIGHT:
850           border->right += w + value_spacing;
851           break;
852         case GTK_POS_TOP:
853           border->top += h + value_spacing;
854           break;
855         case GTK_POS_BOTTOM:
856           border->bottom += h + value_spacing;
857           break;
858         }
859     }
860
861   if (priv->marks)
862     {
863       gint slider_width;
864       gint value_spacing;
865       gint n1, w1, h1, n2, w2, h2;
866   
867       gtk_widget_style_get (widget, 
868                             "slider-width", &slider_width,
869                             "value-spacing", &value_spacing, 
870                             NULL);
871
872
873       gtk_scale_get_mark_label_size (scale, GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
874
875       if (gtk_orientable_get_orientation (GTK_ORIENTABLE (scale)) == GTK_ORIENTATION_HORIZONTAL)
876         {
877           if (n1 > 0)
878             border->top += h1 + value_spacing + slider_width / 2;
879           if (n2 > 0)
880             border->bottom += h2 + value_spacing + slider_width / 2;
881         }
882       else
883         {
884           if (n1 > 0)
885             border->left += w1 + value_spacing + slider_width / 2;
886           if (n2 > 0)
887             border->right += w2 + value_spacing + slider_width / 2;
888         }
889     }
890 }
891
892 /* FIXME this could actually be static at the moment. */
893 void
894 _gtk_scale_get_value_size (GtkScale *scale,
895                            gint     *width,
896                            gint     *height)
897 {
898   GtkScalePrivate *priv = scale->priv;
899   GtkRange *range;
900
901   if (priv->draw_value)
902     {
903       GtkAdjustment *adjustment;
904       PangoLayout *layout;
905       PangoRectangle logical_rect;
906       gchar *txt;
907       
908       range = GTK_RANGE (scale);
909
910       layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
911       adjustment = gtk_range_get_adjustment (range);
912
913       txt = _gtk_scale_format_value (scale, gtk_adjustment_get_lower (adjustment));
914       pango_layout_set_text (layout, txt, -1);
915       g_free (txt);
916       
917       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
918
919       if (width)
920         *width = logical_rect.width;
921       if (height)
922         *height = logical_rect.height;
923
924       txt = _gtk_scale_format_value (scale, gtk_adjustment_get_upper (adjustment));
925       pango_layout_set_text (layout, txt, -1);
926       g_free (txt);
927       
928       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
929
930       if (width)
931         *width = MAX (*width, logical_rect.width);
932       if (height)
933         *height = MAX (*height, logical_rect.height);
934
935       g_object_unref (layout);
936     }
937   else
938     {
939       if (width)
940         *width = 0;
941       if (height)
942         *height = 0;
943     }
944
945 }
946
947 static void
948 gtk_scale_get_mark_label_size (GtkScale        *scale,
949                                GtkPositionType  position,
950                                gint            *count1,
951                                gint            *width1,
952                                gint            *height1,
953                                gint            *count2,
954                                gint            *width2,
955                                gint            *height2)
956 {
957   GtkScalePrivate *priv = scale->priv;
958   PangoLayout *layout;
959   PangoRectangle logical_rect;
960   GSList *m;
961   gint w, h;
962
963   *count1 = *count2 = 0;
964   *width1 = *width2 = 0;
965   *height1 = *height2 = 0;
966
967   layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
968
969   for (m = priv->marks; m; m = m->next)
970     {
971       GtkScaleMark *mark = m->data;
972
973       if (mark->markup && *mark->markup)
974         {
975           pango_layout_set_markup (layout, mark->markup, -1);
976           pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
977
978           w = logical_rect.width;
979           h = logical_rect.height;
980         }
981       else
982         {
983           w = 0;
984           h = 0;
985         }
986
987       if (mark->position == position)
988         {
989           (*count1)++;
990           *width1 = MAX (*width1, w);
991           *height1 = MAX (*height1, h);
992         }
993       else
994         {
995           (*count2)++;
996           *width2 = MAX (*width2, w);
997           *height2 = MAX (*height2, h);
998         }
999     }
1000
1001   g_object_unref (layout);
1002 }
1003
1004 static void
1005 gtk_scale_style_updated (GtkWidget *widget)
1006 {
1007   gint slider_length;
1008   GtkRange *range;
1009
1010   range = GTK_RANGE (widget);
1011   
1012   gtk_widget_style_get (widget,
1013                         "slider-length", &slider_length,
1014                         NULL);
1015
1016   gtk_range_set_min_slider_size (range, slider_length);
1017
1018   _gtk_scale_clear_layout (GTK_SCALE (widget));
1019
1020   GTK_WIDGET_CLASS (gtk_scale_parent_class)->style_updated (widget);
1021 }
1022
1023 static void
1024 gtk_scale_screen_changed (GtkWidget *widget,
1025                           GdkScreen *old_screen)
1026 {
1027   _gtk_scale_clear_layout (GTK_SCALE (widget));
1028 }
1029
1030 static void
1031 gtk_scale_get_preferred_width (GtkWidget *widget,
1032                                gint      *minimum,
1033                                gint      *natural)
1034 {
1035   GTK_WIDGET_CLASS (gtk_scale_parent_class)->get_preferred_width (widget, minimum, natural);
1036   
1037   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
1038     {
1039       gint n1, w1, h1, n2, w2, h2;
1040       gint slider_length;
1041       gint w;
1042
1043       gtk_widget_style_get (widget, "slider-length", &slider_length, NULL);
1044
1045       gtk_scale_get_mark_label_size (GTK_SCALE (widget), GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
1046
1047       w1 = (n1 - 1) * w1 + MAX (w1, slider_length);
1048       w2 = (n2 - 1) * w2 + MAX (w2, slider_length);
1049       w = MAX (w1, w2);
1050
1051       *minimum = MAX (*minimum, w);
1052       *natural = MAX (*natural, w);
1053     }
1054 }
1055
1056 static void
1057 gtk_scale_get_preferred_height (GtkWidget *widget,
1058                                 gint      *minimum,
1059                                 gint      *natural)
1060 {
1061   GTK_WIDGET_CLASS (gtk_scale_parent_class)->get_preferred_height (widget, minimum, natural);
1062
1063
1064   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_VERTICAL)
1065     {
1066       gint n1, w1, h1, n2, w2, h2;
1067       gint slider_length;
1068       gint h;
1069
1070       gtk_widget_style_get (widget, "slider-length", &slider_length, NULL);
1071
1072       gtk_scale_get_mark_label_size (GTK_SCALE (widget), GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
1073       h1 = (n1 - 1) * h1 + MAX (h1, slider_length);
1074       h2 = (n2 - 1) * h1 + MAX (h2, slider_length);
1075       h = MAX (h1, h2);
1076
1077       *minimum = MAX (*minimum, h);
1078       *natural = MAX (*natural, h);
1079     }
1080 }
1081
1082 static gint
1083 find_next_pos (GtkWidget       *widget,
1084                GSList          *list,
1085                gint            *marks,
1086                GtkPositionType  pos)
1087 {
1088   GtkAllocation allocation;
1089   GSList *m;
1090   gint i;
1091
1092   for (m = list->next, i = 1; m; m = m->next, i++)
1093     {
1094       GtkScaleMark *mark = m->data;
1095
1096       if (mark->position == pos)
1097         return marks[i];
1098     }
1099
1100   gtk_widget_get_allocation (widget, &allocation);
1101   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
1102     return allocation.width;
1103   else
1104     return allocation.height;
1105 }
1106
1107 static gboolean
1108 gtk_scale_draw (GtkWidget *widget,
1109                 cairo_t   *cr)
1110 {
1111   GtkScale *scale = GTK_SCALE (widget);
1112   GtkScalePrivate *priv = scale->priv;
1113   GtkRange *range = GTK_RANGE (scale);
1114   GtkStyleContext *context;
1115   gint *marks;
1116   gint focus_padding;
1117   gint slider_width;
1118   gint value_spacing;
1119   gint min_sep = 4;
1120
1121   context = gtk_widget_get_style_context (widget);
1122   gtk_widget_style_get (widget,
1123                         "focus-padding", &focus_padding,
1124                         "slider-width", &slider_width,
1125                         "value-spacing", &value_spacing,
1126                         NULL);
1127
1128   /* We need to chain up _first_ so the various geometry members of
1129    * GtkRange struct are updated.
1130    */
1131   GTK_WIDGET_CLASS (gtk_scale_parent_class)->draw (widget, cr);
1132
1133   if (priv->marks)
1134     {
1135       GtkOrientation orientation;
1136       GdkRectangle range_rect;
1137       gint i;
1138       gint x1, x2, x3, y1, y2, y3;
1139       PangoLayout *layout;
1140       PangoRectangle logical_rect;
1141       GSList *m;
1142       gint min_pos_before, min_pos_after;
1143       gint min_pos, max_pos;
1144       gint n_marks;
1145
1146       orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (range));
1147       n_marks = _gtk_range_get_stop_positions (range, &marks);
1148       /* We always draw the marks in increasing direction, so flip
1149        * the stop positions to match the marks (which we flip in
1150        * gtk_scale_notify)
1151        */
1152       if (gtk_range_get_inverted (range))
1153         {
1154           for (i = 0; i < n_marks / 2; i++)
1155             {
1156               x1 = marks[i];
1157               marks[i] = marks[n_marks - 1 - i];
1158               marks[n_marks - 1 - i] = x1;
1159             }
1160         }
1161
1162       layout = gtk_widget_create_pango_layout (widget, NULL);
1163       gtk_range_get_range_rect (range, &range_rect);
1164
1165       min_pos_before = min_pos_after = 0;
1166
1167       for (m = priv->marks, i = 0; m; m = m->next, i++)
1168         {
1169           GtkScaleMark *mark = m->data;
1170
1171           if (orientation == GTK_ORIENTATION_HORIZONTAL)
1172             {
1173               x1 = marks[i];
1174               if (mark->position == GTK_POS_TOP)
1175                 {
1176                   y1 = range_rect.y;
1177                   y2 = y1 - slider_width / 2;
1178                   min_pos = min_pos_before;
1179                   max_pos = find_next_pos (widget, m, marks + i, GTK_POS_TOP) - min_sep;
1180                 }
1181               else
1182                 {
1183                   y1 = range_rect.y + range_rect.height;
1184                   y2 = y1 + slider_width / 2;
1185                   min_pos = min_pos_after;
1186                   max_pos = find_next_pos (widget, m, marks + i, GTK_POS_BOTTOM) - min_sep;
1187                 }
1188
1189               gtk_style_context_save (context);
1190               gtk_style_context_add_class (context, GTK_STYLE_CLASS_MARK);
1191
1192               gtk_render_line (context, cr, x1, y1, x1, y2);
1193
1194               if (mark->markup)
1195                 {
1196                   pango_layout_set_markup (layout, mark->markup, -1);
1197                   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1198
1199                   x3 = x1 - logical_rect.width / 2;
1200                   if (x3 < min_pos)
1201                     x3 = min_pos;
1202                   if (x3 + logical_rect.width > max_pos)
1203                         x3 = max_pos - logical_rect.width;
1204                   if (x3 < 0)
1205                      x3 = 0;
1206                   if (mark->position == GTK_POS_TOP)
1207                     {
1208                       y3 = y2 - value_spacing - logical_rect.height;
1209                       min_pos_before = x3 + logical_rect.width + min_sep;
1210                     }
1211                   else
1212                     {
1213                       y3 = y2 + value_spacing;
1214                       min_pos_after = x3 + logical_rect.width + min_sep;
1215                     }
1216
1217                   gtk_render_layout (context, cr, x3, y3, layout);
1218                 }
1219
1220               gtk_style_context_restore (context);
1221             }
1222           else
1223             {
1224               if (mark->position == GTK_POS_TOP)
1225                 {
1226                   x1 = range_rect.x;
1227                   x2 = range_rect.x - slider_width / 2;
1228                   min_pos = min_pos_before;
1229                   max_pos = find_next_pos (widget, m, marks + i, GTK_POS_TOP) - min_sep;
1230                 }
1231               else
1232                 {
1233                   x1 = range_rect.x + range_rect.width;
1234                   x2 = range_rect.x + range_rect.width + slider_width / 2;
1235                   min_pos = min_pos_after;
1236                   max_pos = find_next_pos (widget, m, marks + i, GTK_POS_BOTTOM) - min_sep;
1237                 }
1238               y1 = marks[i];
1239
1240               gtk_style_context_save (context);
1241               gtk_style_context_add_class (context, GTK_STYLE_CLASS_MARK);
1242
1243               gtk_render_line (context, cr, x1, y1, x2, y1);
1244
1245               if (mark->markup)
1246                 {
1247                   pango_layout_set_markup (layout, mark->markup, -1);
1248                   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1249
1250                   y3 = y1 - logical_rect.height / 2;
1251                   if (y3 < min_pos)
1252                     y3 = min_pos;
1253                   if (y3 + logical_rect.height > max_pos)
1254                     y3 = max_pos - logical_rect.height;
1255                   if (y3 < 0)
1256                     y3 = 0;
1257                   if (mark->position == GTK_POS_TOP)
1258                     {
1259                       x3 = x2 - value_spacing - logical_rect.width;
1260                       min_pos_before = y3 + logical_rect.height + min_sep;
1261                     }
1262                   else
1263                     {
1264                       x3 = x2 + value_spacing;
1265                       min_pos_after = y3 + logical_rect.height + min_sep;
1266                     }
1267
1268                   gtk_render_layout (context, cr, x3, y3, layout);
1269                 }
1270
1271               gtk_style_context_restore (context);
1272             }
1273         }
1274
1275       g_object_unref (layout);
1276       g_free (marks);
1277     }
1278
1279   if (priv->draw_value)
1280     {
1281       GtkAllocation allocation;
1282
1283       PangoLayout *layout;
1284       gint x, y;
1285
1286       layout = gtk_scale_get_layout (scale);
1287       gtk_scale_get_layout_offsets (scale, &x, &y);
1288       gtk_widget_get_allocation (widget, &allocation);
1289
1290       gtk_render_layout (context, cr,
1291                          x - allocation.x,
1292                          y - allocation.y,
1293                          layout);
1294     }
1295
1296   return FALSE;
1297 }
1298
1299 static void
1300 gtk_scale_real_get_layout_offsets (GtkScale *scale,
1301                                    gint     *x,
1302                                    gint     *y)
1303 {
1304   GtkScalePrivate *priv = scale->priv;
1305   GtkAllocation allocation;
1306   GtkWidget *widget = GTK_WIDGET (scale);
1307   GtkRange *range = GTK_RANGE (widget);
1308   GdkRectangle range_rect;
1309   PangoLayout *layout = gtk_scale_get_layout (scale);
1310   PangoRectangle logical_rect;
1311   gint slider_start, slider_end;
1312   gint value_spacing;
1313
1314   if (!layout)
1315     {
1316       *x = 0;
1317       *y = 0;
1318
1319       return;
1320     }
1321
1322   gtk_widget_style_get (widget, "value-spacing", &value_spacing, NULL);
1323
1324   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1325
1326   gtk_widget_get_allocation (widget, &allocation);
1327   gtk_range_get_range_rect (range, &range_rect);
1328   gtk_range_get_slider_range (range,
1329                               &slider_start,
1330                               &slider_end);
1331
1332   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL)
1333     {
1334       switch (priv->value_pos)
1335         {
1336         case GTK_POS_LEFT:
1337           *x = range_rect.x - value_spacing - logical_rect.width;
1338           *y = range_rect.y + (range_rect.height - logical_rect.height) / 2;
1339           break;
1340
1341         case GTK_POS_RIGHT:
1342           *x = range_rect.x + range_rect.width + value_spacing;
1343           *y = range_rect.y + (range_rect.height - logical_rect.height) / 2;
1344           break;
1345
1346         case GTK_POS_TOP:
1347           *x = slider_start + (slider_end - slider_start - logical_rect.width) / 2;
1348           *x = CLAMP (*x, 0, allocation.width - logical_rect.width);
1349           *y = range_rect.y - logical_rect.height - value_spacing;
1350           break;
1351
1352         case GTK_POS_BOTTOM:
1353           *x = slider_start + (slider_end - slider_start - logical_rect.width) / 2;
1354           *x = CLAMP (*x, 0, allocation.width - logical_rect.width);
1355           *y = range_rect.y + range_rect.height + value_spacing;
1356           break;
1357
1358         default:
1359           g_return_if_reached ();
1360           break;
1361         }
1362     }
1363   else
1364     {
1365       switch (priv->value_pos)
1366         {
1367         case GTK_POS_LEFT:
1368           *x = range_rect.x - logical_rect.width - value_spacing;
1369           *y = slider_start + (slider_end - slider_start - logical_rect.height) / 2;
1370           *y = CLAMP (*y, 0, allocation.height - logical_rect.height);
1371           break;
1372
1373         case GTK_POS_RIGHT:
1374           *x = range_rect.x + range_rect.width + value_spacing;
1375           *y = slider_start + (slider_end - slider_start - logical_rect.height) / 2;
1376           *y = CLAMP (*y, 0, allocation.height - logical_rect.height);
1377           break;
1378
1379         case GTK_POS_TOP:
1380           *x = range_rect.x + (range_rect.width - logical_rect.width) / 2;
1381           *y = range_rect.y - logical_rect.height - value_spacing;
1382           break;
1383
1384         case GTK_POS_BOTTOM:
1385           *x = range_rect.x + (range_rect.width - logical_rect.width) / 2;
1386           *y = range_rect.y + range_rect.height + value_spacing;
1387           break;
1388
1389         default:
1390           g_return_if_reached ();
1391         }
1392     }
1393
1394   *x += allocation.x;
1395   *y += allocation.y;
1396 }
1397
1398 /**
1399  * _gtk_scale_format_value:
1400  * @scale: a #GtkScale
1401  * @value: adjustment value
1402  * 
1403  * Emits #GtkScale::format-value signal to format the value, 
1404  * if no user signal handlers, falls back to a default format.
1405  * 
1406  * Return value: formatted value
1407  */
1408 gchar*
1409 _gtk_scale_format_value (GtkScale *scale,
1410                          gdouble   value)
1411 {
1412   GtkScalePrivate *priv = scale->priv;
1413   gchar *fmt = NULL;
1414
1415   g_signal_emit (scale,
1416                  signals[FORMAT_VALUE],
1417                  0,
1418                  value,
1419                  &fmt);
1420
1421   if (fmt)
1422     return fmt;
1423   else
1424     return g_strdup_printf ("%0.*f", priv->digits, value);
1425 }
1426
1427 static void
1428 gtk_scale_finalize (GObject *object)
1429 {
1430   GtkScale *scale = GTK_SCALE (object);
1431
1432   _gtk_scale_clear_layout (scale);
1433   gtk_scale_clear_marks (scale);
1434
1435   G_OBJECT_CLASS (gtk_scale_parent_class)->finalize (object);
1436 }
1437
1438 /**
1439  * gtk_scale_get_layout:
1440  * @scale: A #GtkScale
1441  *
1442  * Gets the #PangoLayout used to display the scale. The returned
1443  * object is owned by the scale so does not need to be freed by
1444  * the caller.
1445  *
1446  * Return value: (transfer none): the #PangoLayout for this scale,
1447  *     or %NULL if the #GtkScale:draw-value property is %FALSE.
1448  *
1449  * Since: 2.4
1450  */
1451 PangoLayout *
1452 gtk_scale_get_layout (GtkScale *scale)
1453 {
1454   GtkScalePrivate *priv;
1455   gchar *txt;
1456
1457   g_return_val_if_fail (GTK_IS_SCALE (scale), NULL);
1458
1459   priv = scale->priv;
1460
1461   if (!priv->layout)
1462     {
1463       if (priv->draw_value)
1464         priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
1465     }
1466
1467   if (priv->draw_value)
1468     {
1469       txt = _gtk_scale_format_value (scale,
1470                                      gtk_adjustment_get_value (gtk_range_get_adjustment (GTK_RANGE (scale))));
1471       pango_layout_set_text (priv->layout, txt, -1);
1472       g_free (txt);
1473     }
1474
1475   return priv->layout;
1476 }
1477
1478 /**
1479  * gtk_scale_get_layout_offsets:
1480  * @scale: a #GtkScale
1481  * @x: (out) (allow-none): location to store X offset of layout, or %NULL
1482  * @y: (out) (allow-none): location to store Y offset of layout, or %NULL
1483  *
1484  * Obtains the coordinates where the scale will draw the 
1485  * #PangoLayout representing the text in the scale. Remember
1486  * when using the #PangoLayout function you need to convert to
1487  * and from pixels using PANGO_PIXELS() or #PANGO_SCALE. 
1488  *
1489  * If the #GtkScale:draw-value property is %FALSE, the return 
1490  * values are undefined.
1491  *
1492  * Since: 2.4
1493  */
1494 void 
1495 gtk_scale_get_layout_offsets (GtkScale *scale,
1496                               gint     *x,
1497                               gint     *y)
1498 {
1499   gint local_x = 0; 
1500   gint local_y = 0;
1501
1502   g_return_if_fail (GTK_IS_SCALE (scale));
1503
1504   if (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets)
1505     (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets) (scale, &local_x, &local_y);
1506
1507   if (x)
1508     *x = local_x;
1509   
1510   if (y)
1511     *y = local_y;
1512 }
1513
1514 void
1515 _gtk_scale_clear_layout (GtkScale *scale)
1516 {
1517   GtkScalePrivate *priv = scale->priv;
1518
1519   g_return_if_fail (GTK_IS_SCALE (scale));
1520
1521   if (priv->layout)
1522     {
1523       g_object_unref (priv->layout);
1524       priv->layout = NULL;
1525     }
1526 }
1527
1528 static void
1529 gtk_scale_mark_free (gpointer data)
1530 {
1531   GtkScaleMark *mark = data;
1532
1533   g_free (mark->markup);
1534   g_free (mark);
1535 }
1536
1537 /**
1538  * gtk_scale_clear_marks:
1539  * @scale: a #GtkScale
1540  * 
1541  * Removes any marks that have been added with gtk_scale_add_mark().
1542  *
1543  * Since: 2.16
1544  */
1545 void
1546 gtk_scale_clear_marks (GtkScale *scale)
1547 {
1548   GtkScalePrivate *priv;
1549   GtkStyleContext *context;
1550
1551   g_return_if_fail (GTK_IS_SCALE (scale));
1552
1553   priv = scale->priv;
1554
1555   g_slist_free_full (priv->marks, gtk_scale_mark_free);
1556   priv->marks = NULL;
1557
1558   context = gtk_widget_get_style_context (GTK_WIDGET (scale));
1559   gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
1560   gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
1561
1562   _gtk_range_set_stop_values (GTK_RANGE (scale), NULL, 0);
1563
1564   gtk_widget_queue_resize (GTK_WIDGET (scale));
1565 }
1566
1567 /**
1568  * gtk_scale_add_mark:
1569  * @scale: a #GtkScale
1570  * @value: the value at which the mark is placed, must be between
1571  *   the lower and upper limits of the scales' adjustment
1572  * @position: where to draw the mark. For a horizontal scale, #GTK_POS_TOP
1573  *   and %GTK_POS_LEFT are drawn above the scale, anything else below.
1574  *   For a vertical scale, #GTK_POS_LEFT and %GTK_POS_TOP are drawn to
1575  *   the left of the scale, anything else to the right.
1576  * @markup: (allow-none): Text to be shown at the mark, using <link linkend="PangoMarkupFormat">Pango markup</link>, or %NULL
1577  *
1578  *
1579  * Adds a mark at @value.
1580  *
1581  * A mark is indicated visually by drawing a tick mark next to the scale,
1582  * and GTK+ makes it easy for the user to position the scale exactly at the
1583  * marks value.
1584  *
1585  * If @markup is not %NULL, text is shown next to the tick mark.
1586  *
1587  * To remove marks from a scale, use gtk_scale_clear_marks().
1588  *
1589  * Since: 2.16
1590  */
1591 void
1592 gtk_scale_add_mark (GtkScale        *scale,
1593                     gdouble          value,
1594                     GtkPositionType  position,
1595                     const gchar     *markup)
1596 {
1597   GtkScalePrivate *priv;
1598   GtkScaleMark *mark;
1599   GSList *m;
1600   gdouble *values;
1601   gint n, i;
1602   GtkStyleContext *context;
1603   int all_pos;
1604
1605   g_return_if_fail (GTK_IS_SCALE (scale));
1606
1607   priv = scale->priv;
1608
1609   mark = g_new (GtkScaleMark, 1);
1610   mark->value = value;
1611   mark->markup = g_strdup (markup);
1612   if (position == GTK_POS_LEFT ||
1613       position == GTK_POS_TOP)
1614     mark->position = GTK_POS_TOP;
1615   else
1616     mark->position = GTK_POS_BOTTOM;
1617
1618   priv->marks = g_slist_insert_sorted (priv->marks, mark,
1619                                        (GCompareFunc) compare_marks);
1620
1621 #define MARKS_ABOVE 1
1622 #define MARKS_BELOW 2
1623
1624   all_pos = 0;
1625   n = g_slist_length (priv->marks);
1626   values = g_new (gdouble, n);
1627   for (m = priv->marks, i = 0; m; m = m->next, i++)
1628     {
1629       mark = m->data;
1630       values[i] = mark->value;
1631       if (mark->position == GTK_POS_TOP)
1632         all_pos |= MARKS_ABOVE;
1633       else
1634         all_pos |= MARKS_BELOW;
1635     }
1636
1637   _gtk_range_set_stop_values (GTK_RANGE (scale), values, n);
1638
1639   g_free (values);
1640
1641   /* Set the style classes for the slider, so it could
1642    * point to the right direction when marks are present
1643    */
1644   context = gtk_widget_get_style_context (GTK_WIDGET (scale));
1645
1646   if (all_pos & MARKS_ABOVE)
1647     gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
1648   else
1649     gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
1650   if (all_pos & MARKS_BELOW)
1651     gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
1652   else
1653     gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
1654
1655   gtk_widget_queue_resize (GTK_WIDGET (scale));
1656 }
1657
1658 static GtkBuildableIface *parent_buildable_iface;
1659
1660 static void
1661 gtk_scale_buildable_interface_init (GtkBuildableIface *iface)
1662 {
1663   parent_buildable_iface = g_type_interface_peek_parent (iface);
1664   iface->custom_tag_start = gtk_scale_buildable_custom_tag_start;
1665   iface->custom_finished = gtk_scale_buildable_custom_finished;
1666 }
1667
1668 typedef struct
1669 {
1670   GtkScale *scale;
1671   GtkBuilder *builder;
1672   GSList *marks;
1673 } MarksSubparserData;
1674
1675 typedef struct
1676 {
1677   gdouble value;
1678   GtkPositionType position;
1679   GString *markup;
1680   gchar *context;
1681   gboolean translatable;
1682 } MarkData;
1683
1684 static void
1685 mark_data_free (MarkData *data)
1686 {
1687   g_string_free (data->markup, TRUE);
1688   g_free (data->context);
1689   g_slice_free (MarkData, data);
1690 }
1691
1692 static void
1693 marks_start_element (GMarkupParseContext *context,
1694                      const gchar         *element_name,
1695                      const gchar        **names,
1696                      const gchar        **values,
1697                      gpointer             user_data,
1698                      GError             **error)
1699 {
1700   MarksSubparserData *parser_data = (MarksSubparserData*)user_data;
1701   guint i;
1702   gint line_number, char_number;
1703
1704   if (strcmp (element_name, "marks") == 0)
1705    ;
1706   else if (strcmp (element_name, "mark") == 0)
1707     {
1708       gdouble value = 0;
1709       gboolean has_value = FALSE;
1710       GtkPositionType position = GTK_POS_BOTTOM;
1711       const gchar *msg_context = NULL;
1712       gboolean translatable = FALSE;
1713       MarkData *mark;
1714
1715       for (i = 0; names[i]; i++)
1716         {
1717           if (strcmp (names[i], "translatable") == 0)
1718             {
1719               if (!_gtk_builder_boolean_from_string (values[i], &translatable, error))
1720                 return;
1721             }
1722           else if (strcmp (names[i], "comments") == 0)
1723             {
1724               /* do nothing, comments are for translators */
1725             }
1726           else if (strcmp (names[i], "context") == 0)
1727             msg_context = values[i];
1728           else if (strcmp (names[i], "value") == 0)
1729             {
1730               GValue gvalue = G_VALUE_INIT;
1731
1732               if (!gtk_builder_value_from_string_type (parser_data->builder, G_TYPE_DOUBLE, values[i], &gvalue, error))
1733                 return;
1734
1735               value = g_value_get_double (&gvalue);
1736               has_value = TRUE;
1737             }
1738           else if (strcmp (names[i], "position") == 0)
1739             {
1740               GValue gvalue = G_VALUE_INIT;
1741
1742               if (!gtk_builder_value_from_string_type (parser_data->builder, GTK_TYPE_POSITION_TYPE, values[i], &gvalue, error))
1743                 return;
1744
1745               position = g_value_get_enum (&gvalue);
1746             }
1747           else
1748             {
1749               g_markup_parse_context_get_position (context,
1750                                                    &line_number,
1751                                                    &char_number);
1752               g_set_error (error,
1753                            GTK_BUILDER_ERROR,
1754                            GTK_BUILDER_ERROR_INVALID_ATTRIBUTE,
1755                            "%s:%d:%d '%s' is not a valid attribute of <%s>",
1756                            "<input>",
1757                            line_number, char_number, names[i], "mark");
1758               return;
1759             }
1760         }
1761
1762       if (!has_value)
1763         {
1764           g_markup_parse_context_get_position (context,
1765                                                &line_number,
1766                                                &char_number);
1767           g_set_error (error,
1768                        GTK_BUILDER_ERROR,
1769                        GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
1770                        "%s:%d:%d <%s> requires attribute \"%s\"",
1771                        "<input>",
1772                        line_number, char_number, "mark",
1773                        "value");
1774           return;
1775         }
1776
1777       mark = g_slice_new (MarkData);
1778       mark->value = value;
1779       if (position == GTK_POS_LEFT ||
1780           position == GTK_POS_TOP)
1781         mark->position = GTK_POS_TOP;
1782       else
1783         mark->position = GTK_POS_BOTTOM;
1784       mark->markup = g_string_new ("");
1785       mark->context = g_strdup (msg_context);
1786       mark->translatable = translatable;
1787
1788       parser_data->marks = g_slist_prepend (parser_data->marks, mark);
1789     }
1790   else
1791     {
1792       g_markup_parse_context_get_position (context,
1793                                            &line_number,
1794                                            &char_number);
1795       g_set_error (error,
1796                    GTK_BUILDER_ERROR,
1797                    GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
1798                    "%s:%d:%d unsupported tag for GtkScale: \"%s\"",
1799                    "<input>",
1800                    line_number, char_number, element_name);
1801       return;
1802     }
1803 }
1804
1805 static void
1806 marks_text (GMarkupParseContext  *context,
1807             const gchar          *text,
1808             gsize                 text_len,
1809             gpointer              user_data,
1810             GError              **error)
1811 {
1812   MarksSubparserData *data = (MarksSubparserData*)user_data;
1813
1814   if (strcmp (g_markup_parse_context_get_element (context), "mark") == 0)
1815     {
1816       MarkData *mark = data->marks->data;
1817
1818       g_string_append_len (mark->markup, text, text_len);
1819     }
1820 }
1821
1822 static const GMarkupParser marks_parser =
1823   {
1824     marks_start_element,
1825     NULL,
1826     marks_text,
1827   };
1828
1829
1830 static gboolean
1831 gtk_scale_buildable_custom_tag_start (GtkBuildable  *buildable,
1832                                       GtkBuilder    *builder,
1833                                       GObject       *child,
1834                                       const gchar   *tagname,
1835                                       GMarkupParser *parser,
1836                                       gpointer      *data)
1837 {
1838   MarksSubparserData *parser_data;
1839
1840   if (child)
1841     return FALSE;
1842
1843   if (strcmp (tagname, "marks") == 0)
1844     {
1845       parser_data = g_slice_new0 (MarksSubparserData);
1846       parser_data->scale = GTK_SCALE (buildable);
1847       parser_data->marks = NULL;
1848
1849       *parser = marks_parser;
1850       *data = parser_data;
1851       return TRUE;
1852     }
1853
1854   return parent_buildable_iface->custom_tag_start (buildable, builder, child,
1855                                                    tagname, parser, data);
1856 }
1857
1858 static void
1859 gtk_scale_buildable_custom_finished (GtkBuildable *buildable,
1860                                      GtkBuilder   *builder,
1861                                      GObject      *child,
1862                                      const gchar  *tagname,
1863                                      gpointer      user_data)
1864 {
1865   GtkScale *scale = GTK_SCALE (buildable);
1866   MarksSubparserData *marks_data;
1867
1868   if (strcmp (tagname, "marks") == 0)
1869     {
1870       GSList *m;
1871       gchar *markup;
1872
1873       marks_data = (MarksSubparserData *)user_data;
1874
1875       for (m = marks_data->marks; m; m = m->next)
1876         {
1877           MarkData *mdata = m->data;
1878
1879           if (mdata->translatable && mdata->markup->len)
1880             markup = _gtk_builder_parser_translate (gtk_builder_get_translation_domain (builder),
1881                                                     mdata->context,
1882                                                     mdata->markup->str);
1883           else
1884             markup = mdata->markup->str;
1885
1886           gtk_scale_add_mark (scale, mdata->value, mdata->position, markup);
1887
1888           mark_data_free (mdata);
1889         }
1890
1891       g_slist_free (marks_data->marks);
1892       g_slice_free (MarksSubparserData, marks_data);
1893     }
1894 }