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