]> Pileus Git - ~andy/gtk/blob - gtk/gtkscale.c
scale: update style properties on GtkRange at init
[~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_render_line (context, cr, x1, y1, x1, y2);
1200
1201               if (mark->markup)
1202                 {
1203                   pango_layout_set_markup (layout, mark->markup, -1);
1204                   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1205
1206                   x3 = x1 - logical_rect.width / 2;
1207                   if (x3 < min_pos)
1208                     x3 = min_pos;
1209                   if (x3 + logical_rect.width > max_pos)
1210                         x3 = max_pos - logical_rect.width;
1211                   if (x3 < 0)
1212                      x3 = 0;
1213                   if (mark->position == GTK_POS_TOP)
1214                     {
1215                       y3 = y2 - value_spacing - logical_rect.height;
1216                       min_pos_before = x3 + logical_rect.width + min_sep;
1217                     }
1218                   else
1219                     {
1220                       y3 = y2 + value_spacing;
1221                       min_pos_after = x3 + logical_rect.width + min_sep;
1222                     }
1223
1224                   gtk_render_layout (context, cr, x3, y3, layout);
1225                 }
1226
1227               gtk_style_context_restore (context);
1228             }
1229           else
1230             {
1231               if (mark->position == GTK_POS_TOP)
1232                 {
1233                   x1 = range_rect.x;
1234                   x2 = range_rect.x - slider_width / 2;
1235                   min_pos = min_pos_before;
1236                   max_pos = find_next_pos (widget, m, marks + i, GTK_POS_TOP) - min_sep;
1237                 }
1238               else
1239                 {
1240                   x1 = range_rect.x + range_rect.width;
1241                   x2 = range_rect.x + range_rect.width + slider_width / 2;
1242                   min_pos = min_pos_after;
1243                   max_pos = find_next_pos (widget, m, marks + i, GTK_POS_BOTTOM) - min_sep;
1244                 }
1245               y1 = marks[i];
1246
1247               gtk_style_context_save (context);
1248               gtk_style_context_add_class (context, GTK_STYLE_CLASS_MARK);
1249
1250               gtk_render_line (context, cr, x1, y1, x2, y1);
1251
1252               if (mark->markup)
1253                 {
1254                   pango_layout_set_markup (layout, mark->markup, -1);
1255                   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1256
1257                   y3 = y1 - logical_rect.height / 2;
1258                   if (y3 < min_pos)
1259                     y3 = min_pos;
1260                   if (y3 + logical_rect.height > max_pos)
1261                     y3 = max_pos - logical_rect.height;
1262                   if (y3 < 0)
1263                     y3 = 0;
1264                   if (mark->position == GTK_POS_TOP)
1265                     {
1266                       x3 = x2 - value_spacing - logical_rect.width;
1267                       min_pos_before = y3 + logical_rect.height + min_sep;
1268                     }
1269                   else
1270                     {
1271                       x3 = x2 + value_spacing;
1272                       min_pos_after = y3 + logical_rect.height + min_sep;
1273                     }
1274
1275                   gtk_render_layout (context, cr, x3, y3, layout);
1276                 }
1277
1278               gtk_style_context_restore (context);
1279             }
1280         }
1281
1282       g_object_unref (layout);
1283       g_free (marks);
1284     }
1285
1286   if (priv->draw_value)
1287     {
1288       GtkAllocation allocation;
1289
1290       PangoLayout *layout;
1291       gint x, y;
1292
1293       layout = gtk_scale_get_layout (scale);
1294       gtk_scale_get_layout_offsets (scale, &x, &y);
1295       gtk_widget_get_allocation (widget, &allocation);
1296
1297       gtk_render_layout (context, cr,
1298                          x - allocation.x,
1299                          y - allocation.y,
1300                          layout);
1301     }
1302
1303   return FALSE;
1304 }
1305
1306 static void
1307 gtk_scale_real_get_layout_offsets (GtkScale *scale,
1308                                    gint     *x,
1309                                    gint     *y)
1310 {
1311   GtkScalePrivate *priv = scale->priv;
1312   GtkAllocation allocation;
1313   GtkWidget *widget = GTK_WIDGET (scale);
1314   GtkRange *range = GTK_RANGE (widget);
1315   GdkRectangle range_rect;
1316   PangoLayout *layout = gtk_scale_get_layout (scale);
1317   PangoRectangle logical_rect;
1318   gint slider_start, slider_end;
1319   gint value_spacing;
1320
1321   if (!layout)
1322     {
1323       *x = 0;
1324       *y = 0;
1325
1326       return;
1327     }
1328
1329   gtk_widget_style_get (widget, "value-spacing", &value_spacing, NULL);
1330
1331   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1332
1333   gtk_widget_get_allocation (widget, &allocation);
1334   gtk_range_get_range_rect (range, &range_rect);
1335   gtk_range_get_slider_range (range,
1336                               &slider_start,
1337                               &slider_end);
1338
1339   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL)
1340     {
1341       switch (priv->value_pos)
1342         {
1343         case GTK_POS_LEFT:
1344           *x = range_rect.x - value_spacing - logical_rect.width;
1345           *y = range_rect.y + (range_rect.height - logical_rect.height) / 2;
1346           break;
1347
1348         case GTK_POS_RIGHT:
1349           *x = range_rect.x + range_rect.width + value_spacing;
1350           *y = range_rect.y + (range_rect.height - logical_rect.height) / 2;
1351           break;
1352
1353         case GTK_POS_TOP:
1354           *x = slider_start + (slider_end - slider_start - logical_rect.width) / 2;
1355           *x = CLAMP (*x, 0, allocation.width - logical_rect.width);
1356           *y = range_rect.y - logical_rect.height - value_spacing;
1357           break;
1358
1359         case GTK_POS_BOTTOM:
1360           *x = slider_start + (slider_end - slider_start - logical_rect.width) / 2;
1361           *x = CLAMP (*x, 0, allocation.width - logical_rect.width);
1362           *y = range_rect.y + range_rect.height + value_spacing;
1363           break;
1364
1365         default:
1366           g_return_if_reached ();
1367           break;
1368         }
1369     }
1370   else
1371     {
1372       switch (priv->value_pos)
1373         {
1374         case GTK_POS_LEFT:
1375           *x = range_rect.x - logical_rect.width - value_spacing;
1376           *y = slider_start + (slider_end - slider_start - logical_rect.height) / 2;
1377           *y = CLAMP (*y, 0, allocation.height - logical_rect.height);
1378           break;
1379
1380         case GTK_POS_RIGHT:
1381           *x = range_rect.x + range_rect.width + value_spacing;
1382           *y = slider_start + (slider_end - slider_start - logical_rect.height) / 2;
1383           *y = CLAMP (*y, 0, allocation.height - logical_rect.height);
1384           break;
1385
1386         case GTK_POS_TOP:
1387           *x = range_rect.x + (range_rect.width - logical_rect.width) / 2;
1388           *y = range_rect.y - logical_rect.height - value_spacing;
1389           break;
1390
1391         case GTK_POS_BOTTOM:
1392           *x = range_rect.x + (range_rect.width - logical_rect.width) / 2;
1393           *y = range_rect.y + range_rect.height + value_spacing;
1394           break;
1395
1396         default:
1397           g_return_if_reached ();
1398         }
1399     }
1400
1401   *x += allocation.x;
1402   *y += allocation.y;
1403 }
1404
1405 /**
1406  * _gtk_scale_format_value:
1407  * @scale: a #GtkScale
1408  * @value: adjustment value
1409  * 
1410  * Emits #GtkScale::format-value signal to format the value, 
1411  * if no user signal handlers, falls back to a default format.
1412  * 
1413  * Return value: formatted value
1414  */
1415 gchar*
1416 _gtk_scale_format_value (GtkScale *scale,
1417                          gdouble   value)
1418 {
1419   GtkScalePrivate *priv = scale->priv;
1420   gchar *fmt = NULL;
1421
1422   g_signal_emit (scale,
1423                  signals[FORMAT_VALUE],
1424                  0,
1425                  value,
1426                  &fmt);
1427
1428   if (fmt)
1429     return fmt;
1430   else
1431     return g_strdup_printf ("%0.*f", priv->digits, value);
1432 }
1433
1434 static void
1435 gtk_scale_finalize (GObject *object)
1436 {
1437   GtkScale *scale = GTK_SCALE (object);
1438
1439   _gtk_scale_clear_layout (scale);
1440   gtk_scale_clear_marks (scale);
1441
1442   G_OBJECT_CLASS (gtk_scale_parent_class)->finalize (object);
1443 }
1444
1445 /**
1446  * gtk_scale_get_layout:
1447  * @scale: A #GtkScale
1448  *
1449  * Gets the #PangoLayout used to display the scale. The returned
1450  * object is owned by the scale so does not need to be freed by
1451  * the caller.
1452  *
1453  * Return value: (transfer none): the #PangoLayout for this scale,
1454  *     or %NULL if the #GtkScale:draw-value property is %FALSE.
1455  *
1456  * Since: 2.4
1457  */
1458 PangoLayout *
1459 gtk_scale_get_layout (GtkScale *scale)
1460 {
1461   GtkScalePrivate *priv;
1462   gchar *txt;
1463
1464   g_return_val_if_fail (GTK_IS_SCALE (scale), NULL);
1465
1466   priv = scale->priv;
1467
1468   if (!priv->layout)
1469     {
1470       if (priv->draw_value)
1471         priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
1472     }
1473
1474   if (priv->draw_value)
1475     {
1476       txt = _gtk_scale_format_value (scale,
1477                                      gtk_adjustment_get_value (gtk_range_get_adjustment (GTK_RANGE (scale))));
1478       pango_layout_set_text (priv->layout, txt, -1);
1479       g_free (txt);
1480     }
1481
1482   return priv->layout;
1483 }
1484
1485 /**
1486  * gtk_scale_get_layout_offsets:
1487  * @scale: a #GtkScale
1488  * @x: (out) (allow-none): location to store X offset of layout, or %NULL
1489  * @y: (out) (allow-none): location to store Y offset of layout, or %NULL
1490  *
1491  * Obtains the coordinates where the scale will draw the 
1492  * #PangoLayout representing the text in the scale. Remember
1493  * when using the #PangoLayout function you need to convert to
1494  * and from pixels using PANGO_PIXELS() or #PANGO_SCALE. 
1495  *
1496  * If the #GtkScale:draw-value property is %FALSE, the return 
1497  * values are undefined.
1498  *
1499  * Since: 2.4
1500  */
1501 void 
1502 gtk_scale_get_layout_offsets (GtkScale *scale,
1503                               gint     *x,
1504                               gint     *y)
1505 {
1506   gint local_x = 0; 
1507   gint local_y = 0;
1508
1509   g_return_if_fail (GTK_IS_SCALE (scale));
1510
1511   if (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets)
1512     (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets) (scale, &local_x, &local_y);
1513
1514   if (x)
1515     *x = local_x;
1516   
1517   if (y)
1518     *y = local_y;
1519 }
1520
1521 void
1522 _gtk_scale_clear_layout (GtkScale *scale)
1523 {
1524   GtkScalePrivate *priv = scale->priv;
1525
1526   g_return_if_fail (GTK_IS_SCALE (scale));
1527
1528   if (priv->layout)
1529     {
1530       g_object_unref (priv->layout);
1531       priv->layout = NULL;
1532     }
1533 }
1534
1535 static void
1536 gtk_scale_mark_free (gpointer data)
1537 {
1538   GtkScaleMark *mark = data;
1539
1540   g_free (mark->markup);
1541   g_free (mark);
1542 }
1543
1544 /**
1545  * gtk_scale_clear_marks:
1546  * @scale: a #GtkScale
1547  * 
1548  * Removes any marks that have been added with gtk_scale_add_mark().
1549  *
1550  * Since: 2.16
1551  */
1552 void
1553 gtk_scale_clear_marks (GtkScale *scale)
1554 {
1555   GtkScalePrivate *priv;
1556   GtkStyleContext *context;
1557
1558   g_return_if_fail (GTK_IS_SCALE (scale));
1559
1560   priv = scale->priv;
1561
1562   g_slist_free_full (priv->marks, gtk_scale_mark_free);
1563   priv->marks = NULL;
1564
1565   context = gtk_widget_get_style_context (GTK_WIDGET (scale));
1566   gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
1567   gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
1568
1569   _gtk_range_set_stop_values (GTK_RANGE (scale), NULL, 0);
1570
1571   gtk_widget_queue_resize (GTK_WIDGET (scale));
1572 }
1573
1574 /**
1575  * gtk_scale_add_mark:
1576  * @scale: a #GtkScale
1577  * @value: the value at which the mark is placed, must be between
1578  *   the lower and upper limits of the scales' adjustment
1579  * @position: where to draw the mark. For a horizontal scale, #GTK_POS_TOP
1580  *   and %GTK_POS_LEFT are drawn above the scale, anything else below.
1581  *   For a vertical scale, #GTK_POS_LEFT and %GTK_POS_TOP are drawn to
1582  *   the left of the scale, anything else to the right.
1583  * @markup: (allow-none): Text to be shown at the mark, using <link linkend="PangoMarkupFormat">Pango markup</link>, or %NULL
1584  *
1585  *
1586  * Adds a mark at @value.
1587  *
1588  * A mark is indicated visually by drawing a tick mark next to the scale,
1589  * and GTK+ makes it easy for the user to position the scale exactly at the
1590  * marks value.
1591  *
1592  * If @markup is not %NULL, text is shown next to the tick mark.
1593  *
1594  * To remove marks from a scale, use gtk_scale_clear_marks().
1595  *
1596  * Since: 2.16
1597  */
1598 void
1599 gtk_scale_add_mark (GtkScale        *scale,
1600                     gdouble          value,
1601                     GtkPositionType  position,
1602                     const gchar     *markup)
1603 {
1604   GtkScalePrivate *priv;
1605   GtkScaleMark *mark;
1606   GSList *m;
1607   gdouble *values;
1608   gint n, i;
1609   GtkStyleContext *context;
1610   int all_pos;
1611
1612   g_return_if_fail (GTK_IS_SCALE (scale));
1613
1614   priv = scale->priv;
1615
1616   mark = g_new (GtkScaleMark, 1);
1617   mark->value = value;
1618   mark->markup = g_strdup (markup);
1619   if (position == GTK_POS_LEFT ||
1620       position == GTK_POS_TOP)
1621     mark->position = GTK_POS_TOP;
1622   else
1623     mark->position = GTK_POS_BOTTOM;
1624
1625   priv->marks = g_slist_insert_sorted_with_data (priv->marks, mark,
1626                                                  compare_marks,
1627                                                  GINT_TO_POINTER (gtk_range_get_inverted (GTK_RANGE (scale))));
1628
1629 #define MARKS_ABOVE 1
1630 #define MARKS_BELOW 2
1631
1632   all_pos = 0;
1633   n = g_slist_length (priv->marks);
1634   values = g_new (gdouble, n);
1635   for (m = priv->marks, i = 0; m; m = m->next, i++)
1636     {
1637       mark = m->data;
1638       values[i] = mark->value;
1639       if (mark->position == GTK_POS_TOP)
1640         all_pos |= MARKS_ABOVE;
1641       else
1642         all_pos |= MARKS_BELOW;
1643     }
1644
1645   _gtk_range_set_stop_values (GTK_RANGE (scale), values, n);
1646
1647   g_free (values);
1648
1649   /* Set the style classes for the slider, so it could
1650    * point to the right direction when marks are present
1651    */
1652   context = gtk_widget_get_style_context (GTK_WIDGET (scale));
1653
1654   if (all_pos & MARKS_ABOVE)
1655     gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
1656   else
1657     gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
1658   if (all_pos & MARKS_BELOW)
1659     gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
1660   else
1661     gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
1662
1663   gtk_widget_queue_resize (GTK_WIDGET (scale));
1664 }
1665
1666 static GtkBuildableIface *parent_buildable_iface;
1667
1668 static void
1669 gtk_scale_buildable_interface_init (GtkBuildableIface *iface)
1670 {
1671   parent_buildable_iface = g_type_interface_peek_parent (iface);
1672   iface->custom_tag_start = gtk_scale_buildable_custom_tag_start;
1673   iface->custom_finished = gtk_scale_buildable_custom_finished;
1674 }
1675
1676 typedef struct
1677 {
1678   GtkScale *scale;
1679   GtkBuilder *builder;
1680   GSList *marks;
1681 } MarksSubparserData;
1682
1683 typedef struct
1684 {
1685   gdouble value;
1686   GtkPositionType position;
1687   GString *markup;
1688   gchar *context;
1689   gboolean translatable;
1690 } MarkData;
1691
1692 static void
1693 mark_data_free (MarkData *data)
1694 {
1695   g_string_free (data->markup, TRUE);
1696   g_free (data->context);
1697   g_slice_free (MarkData, data);
1698 }
1699
1700 static void
1701 marks_start_element (GMarkupParseContext *context,
1702                      const gchar         *element_name,
1703                      const gchar        **names,
1704                      const gchar        **values,
1705                      gpointer             user_data,
1706                      GError             **error)
1707 {
1708   MarksSubparserData *parser_data = (MarksSubparserData*)user_data;
1709   guint i;
1710   gint line_number, char_number;
1711
1712   if (strcmp (element_name, "marks") == 0)
1713    ;
1714   else if (strcmp (element_name, "mark") == 0)
1715     {
1716       gdouble value = 0;
1717       gboolean has_value = FALSE;
1718       GtkPositionType position = GTK_POS_BOTTOM;
1719       const gchar *msg_context = NULL;
1720       gboolean translatable = FALSE;
1721       MarkData *mark;
1722
1723       for (i = 0; names[i]; i++)
1724         {
1725           if (strcmp (names[i], "translatable") == 0)
1726             {
1727               if (!_gtk_builder_boolean_from_string (values[i], &translatable, error))
1728                 return;
1729             }
1730           else if (strcmp (names[i], "comments") == 0)
1731             {
1732               /* do nothing, comments are for translators */
1733             }
1734           else if (strcmp (names[i], "context") == 0)
1735             msg_context = values[i];
1736           else if (strcmp (names[i], "value") == 0)
1737             {
1738               GValue gvalue = G_VALUE_INIT;
1739
1740               if (!gtk_builder_value_from_string_type (parser_data->builder, G_TYPE_DOUBLE, values[i], &gvalue, error))
1741                 return;
1742
1743               value = g_value_get_double (&gvalue);
1744               has_value = TRUE;
1745             }
1746           else if (strcmp (names[i], "position") == 0)
1747             {
1748               GValue gvalue = G_VALUE_INIT;
1749
1750               if (!gtk_builder_value_from_string_type (parser_data->builder, GTK_TYPE_POSITION_TYPE, values[i], &gvalue, error))
1751                 return;
1752
1753               position = g_value_get_enum (&gvalue);
1754             }
1755           else
1756             {
1757               g_markup_parse_context_get_position (context,
1758                                                    &line_number,
1759                                                    &char_number);
1760               g_set_error (error,
1761                            GTK_BUILDER_ERROR,
1762                            GTK_BUILDER_ERROR_INVALID_ATTRIBUTE,
1763                            "%s:%d:%d '%s' is not a valid attribute of <%s>",
1764                            "<input>",
1765                            line_number, char_number, names[i], "mark");
1766               return;
1767             }
1768         }
1769
1770       if (!has_value)
1771         {
1772           g_markup_parse_context_get_position (context,
1773                                                &line_number,
1774                                                &char_number);
1775           g_set_error (error,
1776                        GTK_BUILDER_ERROR,
1777                        GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
1778                        "%s:%d:%d <%s> requires attribute \"%s\"",
1779                        "<input>",
1780                        line_number, char_number, "mark",
1781                        "value");
1782           return;
1783         }
1784
1785       mark = g_slice_new (MarkData);
1786       mark->value = value;
1787       if (position == GTK_POS_LEFT ||
1788           position == GTK_POS_TOP)
1789         mark->position = GTK_POS_TOP;
1790       else
1791         mark->position = GTK_POS_BOTTOM;
1792       mark->markup = g_string_new ("");
1793       mark->context = g_strdup (msg_context);
1794       mark->translatable = translatable;
1795
1796       parser_data->marks = g_slist_prepend (parser_data->marks, mark);
1797     }
1798   else
1799     {
1800       g_markup_parse_context_get_position (context,
1801                                            &line_number,
1802                                            &char_number);
1803       g_set_error (error,
1804                    GTK_BUILDER_ERROR,
1805                    GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
1806                    "%s:%d:%d unsupported tag for GtkScale: \"%s\"",
1807                    "<input>",
1808                    line_number, char_number, element_name);
1809       return;
1810     }
1811 }
1812
1813 static void
1814 marks_text (GMarkupParseContext  *context,
1815             const gchar          *text,
1816             gsize                 text_len,
1817             gpointer              user_data,
1818             GError              **error)
1819 {
1820   MarksSubparserData *data = (MarksSubparserData*)user_data;
1821
1822   if (strcmp (g_markup_parse_context_get_element (context), "mark") == 0)
1823     {
1824       MarkData *mark = data->marks->data;
1825
1826       g_string_append_len (mark->markup, text, text_len);
1827     }
1828 }
1829
1830 static const GMarkupParser marks_parser =
1831   {
1832     marks_start_element,
1833     NULL,
1834     marks_text,
1835   };
1836
1837
1838 static gboolean
1839 gtk_scale_buildable_custom_tag_start (GtkBuildable  *buildable,
1840                                       GtkBuilder    *builder,
1841                                       GObject       *child,
1842                                       const gchar   *tagname,
1843                                       GMarkupParser *parser,
1844                                       gpointer      *data)
1845 {
1846   MarksSubparserData *parser_data;
1847
1848   if (child)
1849     return FALSE;
1850
1851   if (strcmp (tagname, "marks") == 0)
1852     {
1853       parser_data = g_slice_new0 (MarksSubparserData);
1854       parser_data->scale = GTK_SCALE (buildable);
1855       parser_data->marks = NULL;
1856
1857       *parser = marks_parser;
1858       *data = parser_data;
1859       return TRUE;
1860     }
1861
1862   return parent_buildable_iface->custom_tag_start (buildable, builder, child,
1863                                                    tagname, parser, data);
1864 }
1865
1866 static void
1867 gtk_scale_buildable_custom_finished (GtkBuildable *buildable,
1868                                      GtkBuilder   *builder,
1869                                      GObject      *child,
1870                                      const gchar  *tagname,
1871                                      gpointer      user_data)
1872 {
1873   GtkScale *scale = GTK_SCALE (buildable);
1874   MarksSubparserData *marks_data;
1875
1876   if (strcmp (tagname, "marks") == 0)
1877     {
1878       GSList *m;
1879       gchar *markup;
1880
1881       marks_data = (MarksSubparserData *)user_data;
1882
1883       for (m = marks_data->marks; m; m = m->next)
1884         {
1885           MarkData *mdata = m->data;
1886
1887           if (mdata->translatable && mdata->markup->len)
1888             markup = _gtk_builder_parser_translate (gtk_builder_get_translation_domain (builder),
1889                                                     mdata->context,
1890                                                     mdata->markup->str);
1891           else
1892             markup = mdata->markup->str;
1893
1894           gtk_scale_add_mark (scale, mdata->value, mdata->position, markup);
1895
1896           mark_data_free (mdata);
1897         }
1898
1899       g_slist_free (marks_data->marks);
1900       g_slice_free (MarksSubparserData, marks_data);
1901     }
1902 }