]> Pileus Git - ~andy/gtk/blob - gtk/gtkscale.c
Change FSF Address
[~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, see <http://www.gnu.org/licenses/>.
17  */
18
19 /*
20  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
21  * file for a list of people on the GTK+ Team.  See the ChangeLog
22  * 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
28 #include <math.h>
29 #include <stdlib.h>
30
31 #include "gtkscaleprivate.h"
32 #include "gtkiconfactory.h"
33 #include "gtkicontheme.h"
34 #include "gtkmarshalers.h"
35 #include "gtkbindings.h"
36 #include "gtkorientable.h"
37 #include "gtktypebuiltins.h"
38 #include "gtkprivate.h"
39 #include "gtkintl.h"
40 #include "gtkbuildable.h"
41 #include "gtkbuilderprivate.h"
42
43 #include "a11y/gtkscaleaccessible.h"
44
45
46 /**
47  * SECTION:gtkscale
48  * @Short_description: A slider widget for selecting a value from a range
49  * @Title: GtkScale
50  *
51  * A GtkScale is a slider control used to select a numeric value. 
52  * To use it, you'll probably want to investigate the methods on
53  * its base class, #GtkRange, in addition to the methods for GtkScale itself.
54  * To set the value of a scale, you would normally use gtk_range_set_value().
55  * To detect changes to the value, you would normally use the
56  * #GtkRange::value-changed signal.
57  *
58  * Note that using the same upper and lower bounds for the #GtkScale (through
59  * the #GtkRange methods) will hide the slider itself. This is useful for
60  * applications that want to show an undeterminate value on the scale, without
61  * changing the layout of the application (such as movie or music players).
62  *
63  * <refsect2 id="GtkScale-BUILDER-UI"><title>GtkScale as GtkBuildable</title>
64  * GtkScale supports a custom &lt;marks&gt; element, which
65  * can contain multiple &lt;mark&gt; elements. The "value" and "position"
66  * attributes have the same meaning as gtk_scale_add_mark() parameters of the
67  * same name. If the element is not empty, its content is taken as the markup
68  * to show at the mark. It can be translated with the usual "translatable and
69  * "context" attributes.
70  * </refsect2>
71  */
72
73
74 #define MAX_DIGITS      (64)    /* don't change this,
75                                  * a) you don't need to and
76                                  * b) you might cause buffer owerflows in
77                                  *    unrelated code portions otherwise
78                                  */
79
80 typedef struct _GtkScaleMark GtkScaleMark;
81
82 struct _GtkScalePrivate
83 {
84   PangoLayout  *layout;
85
86   GSList       *marks;
87
88   gint          digits;
89
90   guint         draw_value : 1;
91   guint         value_pos  : 2;
92 };
93
94 struct _GtkScaleMark
95 {
96   gdouble          value;
97   gchar           *markup;
98   GtkPositionType  position; /* always GTK_POS_TOP or GTK_POS_BOTTOM */
99 };
100
101 enum {
102   PROP_0,
103   PROP_DIGITS,
104   PROP_DRAW_VALUE,
105   PROP_HAS_ORIGIN,
106   PROP_VALUE_POS
107 };
108
109 enum {
110   FORMAT_VALUE,
111   LAST_SIGNAL
112 };
113
114 static guint signals[LAST_SIGNAL];
115
116 static void     gtk_scale_set_property            (GObject        *object,
117                                                    guint           prop_id,
118                                                    const GValue   *value,
119                                                    GParamSpec     *pspec);
120 static void     gtk_scale_get_property            (GObject        *object,
121                                                    guint           prop_id,
122                                                    GValue         *value,
123                                                    GParamSpec     *pspec);
124 static void     gtk_scale_get_preferred_width     (GtkWidget      *widget,
125                                                    gint           *minimum,
126                                                    gint           *natural);
127 static void     gtk_scale_get_preferred_height    (GtkWidget      *widget,
128                                                    gint           *minimum,
129                                                    gint           *natural);
130 static void     gtk_scale_style_updated           (GtkWidget      *widget);
131 static void     gtk_scale_get_range_border        (GtkRange       *range,
132                                                    GtkBorder      *border);
133 static void     gtk_scale_get_mark_label_size     (GtkScale        *scale,
134                                                    GtkPositionType  position,
135                                                    gint            *count1,
136                                                    gint            *width1,
137                                                    gint            *height1,
138                                                    gint            *count2,
139                                                    gint            *width2,
140                                                    gint            *height2);
141 static void     gtk_scale_finalize                (GObject        *object);
142 static void     gtk_scale_screen_changed          (GtkWidget      *widget,
143                                                    GdkScreen      *old_screen);
144 static gboolean gtk_scale_draw                    (GtkWidget      *widget,
145                                                    cairo_t        *cr);
146 static void     gtk_scale_real_get_layout_offsets (GtkScale       *scale,
147                                                    gint           *x,
148                                                    gint           *y);
149 static void     gtk_scale_buildable_interface_init   (GtkBuildableIface *iface);
150 static gboolean gtk_scale_buildable_custom_tag_start (GtkBuildable  *buildable,
151                                                       GtkBuilder    *builder,
152                                                       GObject       *child,
153                                                       const gchar   *tagname,
154                                                       GMarkupParser *parser,
155                                                       gpointer      *data);
156 static void     gtk_scale_buildable_custom_finished  (GtkBuildable  *buildable,
157                                                       GtkBuilder    *builder,
158                                                       GObject       *child,
159                                                       const gchar   *tagname,
160                                                       gpointer       user_data);
161
162
163 G_DEFINE_TYPE_WITH_CODE (GtkScale, gtk_scale, GTK_TYPE_RANGE,
164                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
165                                                 gtk_scale_buildable_interface_init))
166
167 static gint
168 compare_marks (gconstpointer a, gconstpointer b, gpointer data)
169 {
170   gboolean inverted = GPOINTER_TO_INT (data);
171   gint val;
172   const GtkScaleMark *ma, *mb;
173
174   val = inverted ? -1 : 1;
175
176   ma = a; mb = b;
177
178   return (ma->value > mb->value) ? val : ((ma->value < mb->value) ? -val : 0);
179 }
180
181 static void
182 gtk_scale_notify (GObject    *object,
183                   GParamSpec *pspec)
184 {
185   if (strcmp (pspec->name, "orientation") == 0)
186     {
187       GtkOrientation orientation;
188
189       orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (object));
190       gtk_range_set_flippable (GTK_RANGE (object),
191                                orientation == GTK_ORIENTATION_HORIZONTAL);
192     }
193   else if (strcmp (pspec->name, "inverted") == 0)
194     {
195       GtkScale *scale = GTK_SCALE (object);
196       GtkScaleMark *mark;
197       GSList *m;
198       gint i, n;
199       gdouble *values;
200
201       scale->priv->marks = g_slist_sort_with_data (scale->priv->marks,
202                                                    compare_marks,
203                                                    GINT_TO_POINTER (gtk_range_get_inverted (GTK_RANGE (scale))));
204
205       n = g_slist_length (scale->priv->marks);
206       values = g_new (gdouble, n);
207       for (m = scale->priv->marks, i = 0; m; m = m->next, i++)
208         {
209           mark = m->data;
210           values[i] = mark->value;
211         }
212
213       _gtk_range_set_stop_values (GTK_RANGE (scale), values, n);
214
215       g_free (values);
216     }
217
218   if (G_OBJECT_CLASS (gtk_scale_parent_class)->notify)
219     G_OBJECT_CLASS (gtk_scale_parent_class)->notify (object, pspec);
220 }
221
222
223 #define add_slider_binding(binding_set, keyval, mask, scroll)              \
224   gtk_binding_entry_add_signal (binding_set, keyval, mask,                 \
225                                 I_("move-slider"), 1, \
226                                 GTK_TYPE_SCROLL_TYPE, scroll)
227
228 static void
229 gtk_scale_class_init (GtkScaleClass *class)
230 {
231   GObjectClass   *gobject_class;
232   GtkWidgetClass *widget_class;
233   GtkRangeClass  *range_class;
234   GtkBindingSet  *binding_set;
235   
236   gobject_class = G_OBJECT_CLASS (class);
237   range_class = (GtkRangeClass*) class;
238   widget_class = (GtkWidgetClass*) class;
239   
240   gobject_class->set_property = gtk_scale_set_property;
241   gobject_class->get_property = gtk_scale_get_property;
242   gobject_class->notify = gtk_scale_notify;
243   gobject_class->finalize = gtk_scale_finalize;
244
245   widget_class->style_updated = gtk_scale_style_updated;
246   widget_class->screen_changed = gtk_scale_screen_changed;
247   widget_class->draw = gtk_scale_draw;
248   widget_class->get_preferred_width = gtk_scale_get_preferred_width;
249   widget_class->get_preferred_height = gtk_scale_get_preferred_height;
250
251   range_class->slider_detail = "Xscale";
252   range_class->get_range_border = gtk_scale_get_range_border;
253
254   class->get_layout_offsets = gtk_scale_real_get_layout_offsets;
255
256   /**
257    * GtkScale::format-value:
258    * @scale: the object which received the signal
259    * @value: the value to format
260    *
261    * Signal which allows you to change how the scale value is displayed.
262    * Connect a signal handler which returns an allocated string representing 
263    * @value. That string will then be used to display the scale's value.
264    *
265    * Here's an example signal handler which displays a value 1.0 as
266    * with "--&gt;1.0&lt;--".
267    * |[
268    * static gchar*
269    * format_value_callback (GtkScale *scale,
270    *                        gdouble   value)
271    * {
272    *   return g_strdup_printf ("--&gt;&percnt;0.*g&lt;--",
273    *                           gtk_scale_get_digits (scale), value);
274    *  }
275    * ]|
276    *
277    * Return value: allocated string representing @value
278    */
279   signals[FORMAT_VALUE] =
280     g_signal_new (I_("format-value"),
281                   G_TYPE_FROM_CLASS (gobject_class),
282                   G_SIGNAL_RUN_LAST,
283                   G_STRUCT_OFFSET (GtkScaleClass, format_value),
284                   _gtk_single_string_accumulator, NULL,
285                   _gtk_marshal_STRING__DOUBLE,
286                   G_TYPE_STRING, 1,
287                   G_TYPE_DOUBLE);
288
289   g_object_class_install_property (gobject_class,
290                                    PROP_DIGITS,
291                                    g_param_spec_int ("digits",
292                                                      P_("Digits"),
293                                                      P_("The number of decimal places that are displayed in the value"),
294                                                      -1,
295                                                      MAX_DIGITS,
296                                                      1,
297                                                      GTK_PARAM_READWRITE));
298   
299   g_object_class_install_property (gobject_class,
300                                    PROP_DRAW_VALUE,
301                                    g_param_spec_boolean ("draw-value",
302                                                          P_("Draw Value"),
303                                                          P_("Whether the current value is displayed as a string next to the slider"),
304                                                          TRUE,
305                                                          GTK_PARAM_READWRITE));
306
307   g_object_class_install_property (gobject_class,
308                                    PROP_HAS_ORIGIN,
309                                    g_param_spec_boolean ("has-origin",
310                                                          P_("Has Origin"),
311                                                          P_("Whether the scale has an origin"),
312                                                          TRUE,
313                                                          GTK_PARAM_READWRITE));
314
315   g_object_class_install_property (gobject_class,
316                                    PROP_VALUE_POS,
317                                    g_param_spec_enum ("value-pos",
318                                                       P_("Value Position"),
319                                                       P_("The position in which the current value is displayed"),
320                                                       GTK_TYPE_POSITION_TYPE,
321                                                       GTK_POS_TOP,
322                                                       GTK_PARAM_READWRITE));
323
324   gtk_widget_class_install_style_property (widget_class,
325                                            g_param_spec_int ("slider-length",
326                                                              P_("Slider Length"),
327                                                              P_("Length of scale's slider"),
328                                                              0,
329                                                              G_MAXINT,
330                                                              31,
331                                                              GTK_PARAM_READABLE));
332
333   gtk_widget_class_install_style_property (widget_class,
334                                            g_param_spec_int ("value-spacing",
335                                                              P_("Value spacing"),
336                                                              P_("Space between value text and the slider/trough area"),
337                                                              0,
338                                                              G_MAXINT,
339                                                              2,
340                                                              GTK_PARAM_READABLE));
341   
342   /* All bindings (even arrow keys) are on both h/v scale, because
343    * blind users etc. don't care about scale orientation.
344    */
345   
346   binding_set = gtk_binding_set_by_class (class);
347
348   add_slider_binding (binding_set, GDK_KEY_Left, 0,
349                       GTK_SCROLL_STEP_LEFT);
350
351   add_slider_binding (binding_set, GDK_KEY_Left, GDK_CONTROL_MASK,
352                       GTK_SCROLL_PAGE_LEFT);
353
354   add_slider_binding (binding_set, GDK_KEY_KP_Left, 0,
355                       GTK_SCROLL_STEP_LEFT);
356
357   add_slider_binding (binding_set, GDK_KEY_KP_Left, GDK_CONTROL_MASK,
358                       GTK_SCROLL_PAGE_LEFT);
359
360   add_slider_binding (binding_set, GDK_KEY_Right, 0,
361                       GTK_SCROLL_STEP_RIGHT);
362
363   add_slider_binding (binding_set, GDK_KEY_Right, GDK_CONTROL_MASK,
364                       GTK_SCROLL_PAGE_RIGHT);
365
366   add_slider_binding (binding_set, GDK_KEY_KP_Right, 0,
367                       GTK_SCROLL_STEP_RIGHT);
368
369   add_slider_binding (binding_set, GDK_KEY_KP_Right, GDK_CONTROL_MASK,
370                       GTK_SCROLL_PAGE_RIGHT);
371
372   add_slider_binding (binding_set, GDK_KEY_Up, 0,
373                       GTK_SCROLL_STEP_UP);
374
375   add_slider_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK,
376                       GTK_SCROLL_PAGE_UP);
377
378   add_slider_binding (binding_set, GDK_KEY_KP_Up, 0,
379                       GTK_SCROLL_STEP_UP);
380
381   add_slider_binding (binding_set, GDK_KEY_KP_Up, GDK_CONTROL_MASK,
382                       GTK_SCROLL_PAGE_UP);
383
384   add_slider_binding (binding_set, GDK_KEY_Down, 0,
385                       GTK_SCROLL_STEP_DOWN);
386
387   add_slider_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK,
388                       GTK_SCROLL_PAGE_DOWN);
389
390   add_slider_binding (binding_set, GDK_KEY_KP_Down, 0,
391                       GTK_SCROLL_STEP_DOWN);
392
393   add_slider_binding (binding_set, GDK_KEY_KP_Down, GDK_CONTROL_MASK,
394                       GTK_SCROLL_PAGE_DOWN);
395    
396   add_slider_binding (binding_set, GDK_KEY_Page_Up, GDK_CONTROL_MASK,
397                       GTK_SCROLL_PAGE_LEFT);
398
399   add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, GDK_CONTROL_MASK,
400                       GTK_SCROLL_PAGE_LEFT);  
401
402   add_slider_binding (binding_set, GDK_KEY_Page_Up, 0,
403                       GTK_SCROLL_PAGE_UP);
404
405   add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, 0,
406                       GTK_SCROLL_PAGE_UP);
407   
408   add_slider_binding (binding_set, GDK_KEY_Page_Down, GDK_CONTROL_MASK,
409                       GTK_SCROLL_PAGE_RIGHT);
410
411   add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, GDK_CONTROL_MASK,
412                       GTK_SCROLL_PAGE_RIGHT);
413
414   add_slider_binding (binding_set, GDK_KEY_Page_Down, 0,
415                       GTK_SCROLL_PAGE_DOWN);
416
417   add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, 0,
418                       GTK_SCROLL_PAGE_DOWN);
419
420   /* Logical bindings (vs. visual bindings above) */
421
422   add_slider_binding (binding_set, GDK_KEY_plus, 0,
423                       GTK_SCROLL_STEP_FORWARD);  
424
425   add_slider_binding (binding_set, GDK_KEY_minus, 0,
426                       GTK_SCROLL_STEP_BACKWARD);  
427
428   add_slider_binding (binding_set, GDK_KEY_plus, GDK_CONTROL_MASK,
429                       GTK_SCROLL_PAGE_FORWARD);  
430
431   add_slider_binding (binding_set, GDK_KEY_minus, GDK_CONTROL_MASK,
432                       GTK_SCROLL_PAGE_BACKWARD);
433
434
435   add_slider_binding (binding_set, GDK_KEY_KP_Add, 0,
436                       GTK_SCROLL_STEP_FORWARD);  
437
438   add_slider_binding (binding_set, GDK_KEY_KP_Subtract, 0,
439                       GTK_SCROLL_STEP_BACKWARD);  
440
441   add_slider_binding (binding_set, GDK_KEY_KP_Add, GDK_CONTROL_MASK,
442                       GTK_SCROLL_PAGE_FORWARD);  
443
444   add_slider_binding (binding_set, GDK_KEY_KP_Subtract, GDK_CONTROL_MASK,
445                       GTK_SCROLL_PAGE_BACKWARD);
446   
447   
448   add_slider_binding (binding_set, GDK_KEY_Home, 0,
449                       GTK_SCROLL_START);
450
451   add_slider_binding (binding_set, GDK_KEY_KP_Home, 0,
452                       GTK_SCROLL_START);
453
454   add_slider_binding (binding_set, GDK_KEY_End, 0,
455                       GTK_SCROLL_END);
456
457   add_slider_binding (binding_set, GDK_KEY_KP_End, 0,
458                       GTK_SCROLL_END);
459
460   g_type_class_add_private (gobject_class, sizeof (GtkScalePrivate));
461
462   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SCALE_ACCESSIBLE);
463 }
464
465 static void
466 gtk_scale_init (GtkScale *scale)
467 {
468   GtkScalePrivate *priv;
469   GtkRange *range = GTK_RANGE (scale);
470   GtkStyleContext *context;
471
472   scale->priv = G_TYPE_INSTANCE_GET_PRIVATE (scale,
473                                              GTK_TYPE_SCALE,
474                                              GtkScalePrivate);
475   priv = scale->priv;
476
477   gtk_widget_set_can_focus (GTK_WIDGET (scale), TRUE);
478
479   gtk_range_set_slider_size_fixed (range, TRUE);
480
481   _gtk_range_set_has_origin (range, TRUE);
482
483   priv->draw_value = TRUE;
484   priv->value_pos = GTK_POS_TOP;
485   priv->digits = 1;
486   gtk_range_set_round_digits (range, priv->digits);
487
488   gtk_range_set_flippable (range,
489                            gtk_orientable_get_orientation (GTK_ORIENTABLE (range))== GTK_ORIENTATION_HORIZONTAL);
490
491   context = gtk_widget_get_style_context (GTK_WIDGET (scale));
492   gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE);
493 }
494
495 static void
496 gtk_scale_set_property (GObject      *object,
497                         guint         prop_id,
498                         const GValue *value,
499                         GParamSpec   *pspec)
500 {
501   GtkScale *scale;
502
503   scale = GTK_SCALE (object);
504
505   switch (prop_id)
506     {
507     case PROP_DIGITS:
508       gtk_scale_set_digits (scale, g_value_get_int (value));
509       break;
510     case PROP_DRAW_VALUE:
511       gtk_scale_set_draw_value (scale, g_value_get_boolean (value));
512       break;
513     case PROP_HAS_ORIGIN:
514       gtk_scale_set_has_origin (scale, g_value_get_boolean (value));
515       break;
516     case PROP_VALUE_POS:
517       gtk_scale_set_value_pos (scale, g_value_get_enum (value));
518       break;
519     default:
520       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
521       break;
522     }
523 }
524
525 static void
526 gtk_scale_get_property (GObject      *object,
527                         guint         prop_id,
528                         GValue       *value,
529                         GParamSpec   *pspec)
530 {
531   GtkScale *scale = GTK_SCALE (object);
532   GtkScalePrivate *priv = scale->priv;
533
534   switch (prop_id)
535     {
536     case PROP_DIGITS:
537       g_value_set_int (value, priv->digits);
538       break;
539     case PROP_DRAW_VALUE:
540       g_value_set_boolean (value, priv->draw_value);
541       break;
542     case PROP_HAS_ORIGIN:
543       g_value_set_boolean (value, gtk_scale_get_has_origin (scale));
544       break;
545     case PROP_VALUE_POS:
546       g_value_set_enum (value, priv->value_pos);
547       break;
548     default:
549       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
550       break;
551     }
552 }
553
554 /**
555  * gtk_scale_new:
556  * @orientation: the scale's orientation.
557  * @adjustment: (allow-none): the #GtkAdjustment which sets the range
558  *              of the scale, or %NULL to create a new adjustment.
559  *
560  * Creates a new #GtkScale.
561  *
562  * Return value: a new #GtkScale
563  *
564  * Since: 3.0
565  **/
566 GtkWidget *
567 gtk_scale_new (GtkOrientation  orientation,
568                GtkAdjustment  *adjustment)
569 {
570   g_return_val_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment),
571                         NULL);
572
573   return g_object_new (GTK_TYPE_SCALE,
574                        "orientation", orientation,
575                        "adjustment",  adjustment,
576                        NULL);
577 }
578
579 /**
580  * gtk_scale_new_with_range:
581  * @orientation: the scale's orientation.
582  * @min: minimum value
583  * @max: maximum value
584  * @step: step increment (tick size) used with keyboard shortcuts
585  *
586  * Creates a new scale widget with the given orientation that lets the
587  * user input a number between @min and @max (including @min and @max)
588  * with the increment @step.  @step must be nonzero; it's the distance
589  * the slider moves when using the arrow keys to adjust the scale
590  * value.
591  *
592  * Note that the way in which the precision is derived works best if @step
593  * is a power of ten. If the resulting precision is not suitable for your
594  * needs, use gtk_scale_set_digits() to correct it.
595  *
596  * Return value: a new #GtkScale
597  *
598  * Since: 3.0
599  */
600 GtkWidget *
601 gtk_scale_new_with_range (GtkOrientation orientation,
602                           gdouble        min,
603                           gdouble        max,
604                           gdouble        step)
605 {
606   GtkAdjustment *adj;
607   gint digits;
608
609   g_return_val_if_fail (min < max, NULL);
610   g_return_val_if_fail (step != 0.0, NULL);
611
612   adj = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
613
614   if (fabs (step) >= 1.0 || step == 0.0)
615     {
616       digits = 0;
617     }
618   else
619     {
620       digits = abs ((gint) floor (log10 (fabs (step))));
621       if (digits > 5)
622         digits = 5;
623     }
624
625   return g_object_new (GTK_TYPE_SCALE,
626                        "orientation", orientation,
627                        "adjustment",  adj,
628                        "digits",      digits,
629                        NULL);
630 }
631
632 /**
633  * gtk_scale_set_digits:
634  * @scale: a #GtkScale
635  * @digits: the number of decimal places to display,
636  *     e.g. use 1 to display 1.0, 2 to display 1.00, etc
637  *
638  * Sets the number of decimal places that are displayed in the value.
639  * Also causes the value of the adjustment to be rounded off to this
640  * number of digits, so the retrieved value matches the value the user saw.
641  */
642 void
643 gtk_scale_set_digits (GtkScale *scale,
644                       gint      digits)
645 {
646   GtkScalePrivate *priv;
647   GtkRange *range;
648
649   g_return_if_fail (GTK_IS_SCALE (scale));
650
651   priv = scale->priv;
652   range = GTK_RANGE (scale);
653   
654   digits = CLAMP (digits, -1, MAX_DIGITS);
655
656   if (priv->digits != digits)
657     {
658       priv->digits = digits;
659       if (priv->draw_value)
660         gtk_range_set_round_digits (range, digits);
661
662       _gtk_scale_clear_layout (scale);
663       gtk_widget_queue_resize (GTK_WIDGET (scale));
664
665       g_object_notify (G_OBJECT (scale), "digits");
666     }
667 }
668
669 /**
670  * gtk_scale_get_digits:
671  * @scale: a #GtkScale
672  *
673  * Gets the number of decimal places that are displayed in the value.
674  *
675  * Returns: the number of decimal places that are displayed
676  */
677 gint
678 gtk_scale_get_digits (GtkScale *scale)
679 {
680   g_return_val_if_fail (GTK_IS_SCALE (scale), -1);
681
682   return scale->priv->digits;
683 }
684
685 /**
686  * gtk_scale_set_draw_value:
687  * @scale: a #GtkScale
688  * @draw_value: %TRUE to draw the value
689  * 
690  * Specifies whether the current value is displayed as a string next 
691  * to the slider.
692  */
693 void
694 gtk_scale_set_draw_value (GtkScale *scale,
695                           gboolean  draw_value)
696 {
697   GtkScalePrivate *priv;
698
699   g_return_if_fail (GTK_IS_SCALE (scale));
700
701   priv = scale->priv;
702
703   draw_value = draw_value != FALSE;
704
705   if (priv->draw_value != draw_value)
706     {
707       priv->draw_value = draw_value;
708       if (draw_value)
709         gtk_range_set_round_digits (GTK_RANGE (scale), priv->digits);
710       else
711         gtk_range_set_round_digits (GTK_RANGE (scale), -1);
712
713       _gtk_scale_clear_layout (scale);
714
715       gtk_widget_queue_resize (GTK_WIDGET (scale));
716
717       g_object_notify (G_OBJECT (scale), "draw-value");
718     }
719 }
720
721 /**
722  * gtk_scale_get_draw_value:
723  * @scale: a #GtkScale
724  *
725  * Returns whether the current value is displayed as a string 
726  * next to the slider.
727  *
728  * Returns: whether the current value is displayed as a string
729  */
730 gboolean
731 gtk_scale_get_draw_value (GtkScale *scale)
732 {
733   g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE);
734
735   return scale->priv->draw_value;
736 }
737
738 /**
739  * gtk_scale_set_has_origin:
740  * @scale: a #GtkScale
741  * @has_origin: %TRUE if the scale has an origin
742  * 
743  * If @has_origin is set to %TRUE (the default),
744  * the scale will highlight the part of the scale
745  * between the origin (bottom or left side) of the scale
746  * and the current value.
747  *
748  * Since: 3.4
749  */
750 void
751 gtk_scale_set_has_origin (GtkScale *scale,
752                           gboolean  has_origin)
753 {
754   g_return_if_fail (GTK_IS_SCALE (scale));
755
756   has_origin = has_origin != FALSE;
757
758   if (_gtk_range_get_has_origin (GTK_RANGE (scale)) != has_origin)
759     {
760       _gtk_range_set_has_origin (GTK_RANGE (scale), has_origin);
761
762       gtk_widget_queue_draw (GTK_WIDGET (scale));
763
764       g_object_notify (G_OBJECT (scale), "has-origin");
765     }
766 }
767
768 /**
769  * gtk_scale_get_has_origin:
770  * @scale: a #GtkScale
771  *
772  * Returns whether the scale has an origin.
773  *
774  * Returns: %TRUE if the scale has an origin.
775  * 
776  * Since: 3.4
777  */
778 gboolean
779 gtk_scale_get_has_origin (GtkScale *scale)
780 {
781   g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE);
782
783   return _gtk_range_get_has_origin (GTK_RANGE (scale));
784 }
785
786 /**
787  * gtk_scale_set_value_pos:
788  * @scale: a #GtkScale
789  * @pos: the position in which the current value is displayed
790  * 
791  * Sets the position in which the current value is displayed.
792  */
793 void
794 gtk_scale_set_value_pos (GtkScale        *scale,
795                          GtkPositionType  pos)
796 {
797   GtkScalePrivate *priv;
798   GtkWidget *widget;
799
800   g_return_if_fail (GTK_IS_SCALE (scale));
801
802   priv = scale->priv;
803
804   if (priv->value_pos != pos)
805     {
806       priv->value_pos = pos;
807       widget = GTK_WIDGET (scale);
808
809       _gtk_scale_clear_layout (scale);
810       if (gtk_widget_get_visible (widget) && gtk_widget_get_mapped (widget))
811         gtk_widget_queue_resize (widget);
812
813       g_object_notify (G_OBJECT (scale), "value-pos");
814     }
815 }
816
817 /**
818  * gtk_scale_get_value_pos:
819  * @scale: a #GtkScale
820  *
821  * Gets the position in which the current value is displayed.
822  *
823  * Returns: the position in which the current value is displayed
824  */
825 GtkPositionType
826 gtk_scale_get_value_pos (GtkScale *scale)
827 {
828   g_return_val_if_fail (GTK_IS_SCALE (scale), 0);
829
830   return scale->priv->value_pos;
831 }
832
833 static void
834 gtk_scale_get_range_border (GtkRange  *range,
835                             GtkBorder *border)
836 {
837   GtkScalePrivate *priv;
838   GtkWidget *widget;
839   GtkScale *scale;
840   gint w, h;
841   
842   widget = GTK_WIDGET (range);
843   scale = GTK_SCALE (range);
844   priv = scale->priv;
845
846   _gtk_scale_get_value_size (scale, &w, &h);
847
848   border->left = 0;
849   border->right = 0;
850   border->top = 0;
851   border->bottom = 0;
852
853   if (priv->draw_value)
854     {
855       gint value_spacing;
856       gtk_widget_style_get (widget, "value-spacing", &value_spacing, NULL);
857
858       switch (priv->value_pos)
859         {
860         case GTK_POS_LEFT:
861           border->left += w + value_spacing;
862           break;
863         case GTK_POS_RIGHT:
864           border->right += w + value_spacing;
865           break;
866         case GTK_POS_TOP:
867           border->top += h + value_spacing;
868           break;
869         case GTK_POS_BOTTOM:
870           border->bottom += h + value_spacing;
871           break;
872         }
873     }
874
875   if (priv->marks)
876     {
877       gint slider_width;
878       gint value_spacing;
879       gint n1, w1, h1, n2, w2, h2;
880   
881       gtk_widget_style_get (widget, 
882                             "slider-width", &slider_width,
883                             "value-spacing", &value_spacing, 
884                             NULL);
885
886
887       gtk_scale_get_mark_label_size (scale, GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
888
889       if (gtk_orientable_get_orientation (GTK_ORIENTABLE (scale)) == GTK_ORIENTATION_HORIZONTAL)
890         {
891           if (n1 > 0)
892             border->top += h1 + value_spacing + slider_width / 2;
893           if (n2 > 0)
894             border->bottom += h2 + value_spacing + slider_width / 2;
895         }
896       else
897         {
898           if (n1 > 0)
899             border->left += w1 + value_spacing + slider_width / 2;
900           if (n2 > 0)
901             border->right += w2 + value_spacing + slider_width / 2;
902         }
903     }
904 }
905
906 /* FIXME this could actually be static at the moment. */
907 void
908 _gtk_scale_get_value_size (GtkScale *scale,
909                            gint     *width,
910                            gint     *height)
911 {
912   GtkScalePrivate *priv = scale->priv;
913   GtkRange *range;
914
915   if (priv->draw_value)
916     {
917       GtkAdjustment *adjustment;
918       PangoLayout *layout;
919       PangoRectangle logical_rect;
920       gchar *txt;
921       
922       range = GTK_RANGE (scale);
923
924       layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
925       adjustment = gtk_range_get_adjustment (range);
926
927       txt = _gtk_scale_format_value (scale, gtk_adjustment_get_lower (adjustment));
928       pango_layout_set_text (layout, txt, -1);
929       g_free (txt);
930       
931       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
932
933       if (width)
934         *width = logical_rect.width;
935       if (height)
936         *height = logical_rect.height;
937
938       txt = _gtk_scale_format_value (scale, gtk_adjustment_get_upper (adjustment));
939       pango_layout_set_text (layout, txt, -1);
940       g_free (txt);
941       
942       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
943
944       if (width)
945         *width = MAX (*width, logical_rect.width);
946       if (height)
947         *height = MAX (*height, logical_rect.height);
948
949       g_object_unref (layout);
950     }
951   else
952     {
953       if (width)
954         *width = 0;
955       if (height)
956         *height = 0;
957     }
958
959 }
960
961 static void
962 gtk_scale_get_mark_label_size (GtkScale        *scale,
963                                GtkPositionType  position,
964                                gint            *count1,
965                                gint            *width1,
966                                gint            *height1,
967                                gint            *count2,
968                                gint            *width2,
969                                gint            *height2)
970 {
971   GtkScalePrivate *priv = scale->priv;
972   PangoLayout *layout;
973   PangoRectangle logical_rect;
974   GSList *m;
975   gint w, h;
976
977   *count1 = *count2 = 0;
978   *width1 = *width2 = 0;
979   *height1 = *height2 = 0;
980
981   layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
982
983   for (m = priv->marks; m; m = m->next)
984     {
985       GtkScaleMark *mark = m->data;
986
987       if (mark->markup && *mark->markup)
988         {
989           pango_layout_set_markup (layout, mark->markup, -1);
990           pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
991
992           w = logical_rect.width;
993           h = logical_rect.height;
994         }
995       else
996         {
997           w = 0;
998           h = 0;
999         }
1000
1001       if (mark->position == position)
1002         {
1003           (*count1)++;
1004           *width1 = MAX (*width1, w);
1005           *height1 = MAX (*height1, h);
1006         }
1007       else
1008         {
1009           (*count2)++;
1010           *width2 = MAX (*width2, w);
1011           *height2 = MAX (*height2, h);
1012         }
1013     }
1014
1015   g_object_unref (layout);
1016 }
1017
1018 static void
1019 gtk_scale_style_updated (GtkWidget *widget)
1020 {
1021   gint slider_length;
1022   GtkRange *range;
1023
1024   range = GTK_RANGE (widget);
1025   
1026   gtk_widget_style_get (widget,
1027                         "slider-length", &slider_length,
1028                         NULL);
1029
1030   gtk_range_set_min_slider_size (range, slider_length);
1031
1032   _gtk_scale_clear_layout (GTK_SCALE (widget));
1033
1034   GTK_WIDGET_CLASS (gtk_scale_parent_class)->style_updated (widget);
1035 }
1036
1037 static void
1038 gtk_scale_screen_changed (GtkWidget *widget,
1039                           GdkScreen *old_screen)
1040 {
1041   _gtk_scale_clear_layout (GTK_SCALE (widget));
1042 }
1043
1044 static void
1045 gtk_scale_get_preferred_width (GtkWidget *widget,
1046                                gint      *minimum,
1047                                gint      *natural)
1048 {
1049   GTK_WIDGET_CLASS (gtk_scale_parent_class)->get_preferred_width (widget, minimum, natural);
1050   
1051   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
1052     {
1053       gint n1, w1, h1, n2, w2, h2;
1054       gint slider_length;
1055       gint w;
1056
1057       gtk_widget_style_get (widget, "slider-length", &slider_length, NULL);
1058
1059       gtk_scale_get_mark_label_size (GTK_SCALE (widget), GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
1060
1061       w1 = (n1 - 1) * w1 + MAX (w1, slider_length);
1062       w2 = (n2 - 1) * w2 + MAX (w2, slider_length);
1063       w = MAX (w1, w2);
1064
1065       *minimum = MAX (*minimum, w);
1066       *natural = MAX (*natural, w);
1067     }
1068 }
1069
1070 static void
1071 gtk_scale_get_preferred_height (GtkWidget *widget,
1072                                 gint      *minimum,
1073                                 gint      *natural)
1074 {
1075   GTK_WIDGET_CLASS (gtk_scale_parent_class)->get_preferred_height (widget, minimum, natural);
1076
1077
1078   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_VERTICAL)
1079     {
1080       gint n1, w1, h1, n2, w2, h2;
1081       gint slider_length;
1082       gint h;
1083
1084       gtk_widget_style_get (widget, "slider-length", &slider_length, NULL);
1085
1086       gtk_scale_get_mark_label_size (GTK_SCALE (widget), GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
1087       h1 = (n1 - 1) * h1 + MAX (h1, slider_length);
1088       h2 = (n2 - 1) * h1 + MAX (h2, slider_length);
1089       h = MAX (h1, h2);
1090
1091       *minimum = MAX (*minimum, h);
1092       *natural = MAX (*natural, h);
1093     }
1094 }
1095
1096 static gint
1097 find_next_pos (GtkWidget       *widget,
1098                GSList          *list,
1099                gint            *marks,
1100                GtkPositionType  pos)
1101 {
1102   GtkAllocation allocation;
1103   GSList *m;
1104   gint i;
1105
1106   for (m = list->next, i = 1; m; m = m->next, i++)
1107     {
1108       GtkScaleMark *mark = m->data;
1109
1110       if (mark->position == pos)
1111         return marks[i];
1112     }
1113
1114   gtk_widget_get_allocation (widget, &allocation);
1115   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
1116     return allocation.width;
1117   else
1118     return allocation.height;
1119 }
1120
1121 static gboolean
1122 gtk_scale_draw (GtkWidget *widget,
1123                 cairo_t   *cr)
1124 {
1125   GtkScale *scale = GTK_SCALE (widget);
1126   GtkScalePrivate *priv = scale->priv;
1127   GtkRange *range = GTK_RANGE (scale);
1128   GtkStyleContext *context;
1129   gint *marks;
1130   gint focus_padding;
1131   gint slider_width;
1132   gint value_spacing;
1133   gint min_sep = 4;
1134
1135   context = gtk_widget_get_style_context (widget);
1136   gtk_widget_style_get (widget,
1137                         "focus-padding", &focus_padding,
1138                         "slider-width", &slider_width,
1139                         "value-spacing", &value_spacing,
1140                         NULL);
1141
1142   /* We need to chain up _first_ so the various geometry members of
1143    * GtkRange struct are updated.
1144    */
1145   GTK_WIDGET_CLASS (gtk_scale_parent_class)->draw (widget, cr);
1146
1147   if (priv->marks)
1148     {
1149       GtkOrientation orientation;
1150       GdkRectangle range_rect;
1151       gint i;
1152       gint x1, x2, x3, y1, y2, y3;
1153       PangoLayout *layout;
1154       PangoRectangle logical_rect;
1155       GSList *m;
1156       gint min_pos_before, min_pos_after;
1157       gint min_pos, max_pos;
1158
1159       orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (range));
1160       _gtk_range_get_stop_positions (range, &marks);
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_with_data (priv->marks, mark,
1619                                                  compare_marks,
1620                                                  GINT_TO_POINTER (gtk_range_get_inverted (GTK_RANGE (scale))));
1621
1622 #define MARKS_ABOVE 1
1623 #define MARKS_BELOW 2
1624
1625   all_pos = 0;
1626   n = g_slist_length (priv->marks);
1627   values = g_new (gdouble, n);
1628   for (m = priv->marks, i = 0; m; m = m->next, i++)
1629     {
1630       mark = m->data;
1631       values[i] = mark->value;
1632       if (mark->position == GTK_POS_TOP)
1633         all_pos |= MARKS_ABOVE;
1634       else
1635         all_pos |= MARKS_BELOW;
1636     }
1637
1638   _gtk_range_set_stop_values (GTK_RANGE (scale), values, n);
1639
1640   g_free (values);
1641
1642   /* Set the style classes for the slider, so it could
1643    * point to the right direction when marks are present
1644    */
1645   context = gtk_widget_get_style_context (GTK_WIDGET (scale));
1646
1647   if (all_pos & MARKS_ABOVE)
1648     gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
1649   else
1650     gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
1651   if (all_pos & MARKS_BELOW)
1652     gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
1653   else
1654     gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
1655
1656   gtk_widget_queue_resize (GTK_WIDGET (scale));
1657 }
1658
1659 static GtkBuildableIface *parent_buildable_iface;
1660
1661 static void
1662 gtk_scale_buildable_interface_init (GtkBuildableIface *iface)
1663 {
1664   parent_buildable_iface = g_type_interface_peek_parent (iface);
1665   iface->custom_tag_start = gtk_scale_buildable_custom_tag_start;
1666   iface->custom_finished = gtk_scale_buildable_custom_finished;
1667 }
1668
1669 typedef struct
1670 {
1671   GtkScale *scale;
1672   GtkBuilder *builder;
1673   GSList *marks;
1674 } MarksSubparserData;
1675
1676 typedef struct
1677 {
1678   gdouble value;
1679   GtkPositionType position;
1680   GString *markup;
1681   gchar *context;
1682   gboolean translatable;
1683 } MarkData;
1684
1685 static void
1686 mark_data_free (MarkData *data)
1687 {
1688   g_string_free (data->markup, TRUE);
1689   g_free (data->context);
1690   g_slice_free (MarkData, data);
1691 }
1692
1693 static void
1694 marks_start_element (GMarkupParseContext *context,
1695                      const gchar         *element_name,
1696                      const gchar        **names,
1697                      const gchar        **values,
1698                      gpointer             user_data,
1699                      GError             **error)
1700 {
1701   MarksSubparserData *parser_data = (MarksSubparserData*)user_data;
1702   guint i;
1703   gint line_number, char_number;
1704
1705   if (strcmp (element_name, "marks") == 0)
1706    ;
1707   else if (strcmp (element_name, "mark") == 0)
1708     {
1709       gdouble value = 0;
1710       gboolean has_value = FALSE;
1711       GtkPositionType position = GTK_POS_BOTTOM;
1712       const gchar *msg_context = NULL;
1713       gboolean translatable = FALSE;
1714       MarkData *mark;
1715
1716       for (i = 0; names[i]; i++)
1717         {
1718           if (strcmp (names[i], "translatable") == 0)
1719             {
1720               if (!_gtk_builder_boolean_from_string (values[i], &translatable, error))
1721                 return;
1722             }
1723           else if (strcmp (names[i], "comments") == 0)
1724             {
1725               /* do nothing, comments are for translators */
1726             }
1727           else if (strcmp (names[i], "context") == 0)
1728             msg_context = values[i];
1729           else if (strcmp (names[i], "value") == 0)
1730             {
1731               GValue gvalue = G_VALUE_INIT;
1732
1733               if (!gtk_builder_value_from_string_type (parser_data->builder, G_TYPE_DOUBLE, values[i], &gvalue, error))
1734                 return;
1735
1736               value = g_value_get_double (&gvalue);
1737               has_value = TRUE;
1738             }
1739           else if (strcmp (names[i], "position") == 0)
1740             {
1741               GValue gvalue = G_VALUE_INIT;
1742
1743               if (!gtk_builder_value_from_string_type (parser_data->builder, GTK_TYPE_POSITION_TYPE, values[i], &gvalue, error))
1744                 return;
1745
1746               position = g_value_get_enum (&gvalue);
1747             }
1748           else
1749             {
1750               g_markup_parse_context_get_position (context,
1751                                                    &line_number,
1752                                                    &char_number);
1753               g_set_error (error,
1754                            GTK_BUILDER_ERROR,
1755                            GTK_BUILDER_ERROR_INVALID_ATTRIBUTE,
1756                            "%s:%d:%d '%s' is not a valid attribute of <%s>",
1757                            "<input>",
1758                            line_number, char_number, names[i], "mark");
1759               return;
1760             }
1761         }
1762
1763       if (!has_value)
1764         {
1765           g_markup_parse_context_get_position (context,
1766                                                &line_number,
1767                                                &char_number);
1768           g_set_error (error,
1769                        GTK_BUILDER_ERROR,
1770                        GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
1771                        "%s:%d:%d <%s> requires attribute \"%s\"",
1772                        "<input>",
1773                        line_number, char_number, "mark",
1774                        "value");
1775           return;
1776         }
1777
1778       mark = g_slice_new (MarkData);
1779       mark->value = value;
1780       if (position == GTK_POS_LEFT ||
1781           position == GTK_POS_TOP)
1782         mark->position = GTK_POS_TOP;
1783       else
1784         mark->position = GTK_POS_BOTTOM;
1785       mark->markup = g_string_new ("");
1786       mark->context = g_strdup (msg_context);
1787       mark->translatable = translatable;
1788
1789       parser_data->marks = g_slist_prepend (parser_data->marks, mark);
1790     }
1791   else
1792     {
1793       g_markup_parse_context_get_position (context,
1794                                            &line_number,
1795                                            &char_number);
1796       g_set_error (error,
1797                    GTK_BUILDER_ERROR,
1798                    GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
1799                    "%s:%d:%d unsupported tag for GtkScale: \"%s\"",
1800                    "<input>",
1801                    line_number, char_number, element_name);
1802       return;
1803     }
1804 }
1805
1806 static void
1807 marks_text (GMarkupParseContext  *context,
1808             const gchar          *text,
1809             gsize                 text_len,
1810             gpointer              user_data,
1811             GError              **error)
1812 {
1813   MarksSubparserData *data = (MarksSubparserData*)user_data;
1814
1815   if (strcmp (g_markup_parse_context_get_element (context), "mark") == 0)
1816     {
1817       MarkData *mark = data->marks->data;
1818
1819       g_string_append_len (mark->markup, text, text_len);
1820     }
1821 }
1822
1823 static const GMarkupParser marks_parser =
1824   {
1825     marks_start_element,
1826     NULL,
1827     marks_text,
1828   };
1829
1830
1831 static gboolean
1832 gtk_scale_buildable_custom_tag_start (GtkBuildable  *buildable,
1833                                       GtkBuilder    *builder,
1834                                       GObject       *child,
1835                                       const gchar   *tagname,
1836                                       GMarkupParser *parser,
1837                                       gpointer      *data)
1838 {
1839   MarksSubparserData *parser_data;
1840
1841   if (child)
1842     return FALSE;
1843
1844   if (strcmp (tagname, "marks") == 0)
1845     {
1846       parser_data = g_slice_new0 (MarksSubparserData);
1847       parser_data->scale = GTK_SCALE (buildable);
1848       parser_data->marks = NULL;
1849
1850       *parser = marks_parser;
1851       *data = parser_data;
1852       return TRUE;
1853     }
1854
1855   return parent_buildable_iface->custom_tag_start (buildable, builder, child,
1856                                                    tagname, parser, data);
1857 }
1858
1859 static void
1860 gtk_scale_buildable_custom_finished (GtkBuildable *buildable,
1861                                      GtkBuilder   *builder,
1862                                      GObject      *child,
1863                                      const gchar  *tagname,
1864                                      gpointer      user_data)
1865 {
1866   GtkScale *scale = GTK_SCALE (buildable);
1867   MarksSubparserData *marks_data;
1868
1869   if (strcmp (tagname, "marks") == 0)
1870     {
1871       GSList *m;
1872       gchar *markup;
1873
1874       marks_data = (MarksSubparserData *)user_data;
1875
1876       for (m = marks_data->marks; m; m = m->next)
1877         {
1878           MarkData *mdata = m->data;
1879
1880           if (mdata->translatable && mdata->markup->len)
1881             markup = _gtk_builder_parser_translate (gtk_builder_get_translation_domain (builder),
1882                                                     mdata->context,
1883                                                     mdata->markup->str);
1884           else
1885             markup = mdata->markup->str;
1886
1887           gtk_scale_add_mark (scale, mdata->value, mdata->position, markup);
1888
1889           mark_data_free (mdata);
1890         }
1891
1892       g_slist_free (marks_data->marks);
1893       g_slice_free (MarksSubparserData, marks_data);
1894     }
1895 }