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