]> Pileus Git - ~andy/gtk/blob - gtk/gtkscale.c
Merge branch 'bgo593793-filechooser-recent-folders-master'
[~andy/gtk] / gtk / gtkscale.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  * Copyright (C) 2001 Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 /*
22  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
23  * file for a list of people on the GTK+ Team.  See the ChangeLog
24  * files for a list of changes.  These files are distributed with
25  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
26  */
27
28 #include "config.h"
29
30 #include <math.h>
31 #include <stdlib.h>
32
33 #include "gtkscaleprivate.h"
34 #include "gtkiconfactory.h"
35 #include "gtkicontheme.h"
36 #include "gtkmarshalers.h"
37 #include "gtkbindings.h"
38 #include "gtkorientable.h"
39 #include "gtktypebuiltins.h"
40 #include "gtkprivate.h"
41 #include "gtkintl.h"
42 #include "gtkbuildable.h"
43 #include "gtkbuilderprivate.h"
44
45 #include "a11y/gtkscaleaccessible.h"
46
47
48 /**
49  * SECTION:gtkscale
50  * @Short_description: A slider widget for selecting a value from a range
51  * @Title: GtkScale
52  *
53  * A GtkScale is a slider control used to select a numeric value. 
54  * To use it, you'll probably want to investigate the methods on
55  * its base class, #GtkRange, in addition to the methods for GtkScale itself.
56  * To set the value of a scale, you would normally use gtk_range_set_value().
57  * To detect changes to the value, you would normally use the
58  * #GtkRange::value-changed signal.
59  *
60  * Note that using the same upper and lower bounds for the #GtkScale (through
61  * the #GtkRange methods) will hide the slider itself. This is useful for
62  * applications that want to show an undeterminate value on the scale, without
63  * changing the layout of the application (such as movie or music players).
64  *
65  * <refsect2 id="GtkScale-BUILDER-UI"><title>GtkScale as GtkBuildable</title>
66  * GtkScale supports a custom &lt;marks&gt; element, which
67  * can contain multiple &lt;mark&gt; elements. The "value" and "position"
68  * attributes have the same meaning as gtk_scale_add_mark() parameters of the
69  * same name. If the element is not empty, its content is taken as the markup
70  * to show at the mark. It can be translated with the usual "translatable and
71  * "context" attributes.
72  * </refsect2>
73  */
74
75
76 #define MAX_DIGITS      (64)    /* don't change this,
77                                  * a) you don't need to and
78                                  * b) you might cause buffer owerflows in
79                                  *    unrelated code portions otherwise
80                                  */
81
82 typedef struct _GtkScaleMark GtkScaleMark;
83
84 struct _GtkScalePrivate
85 {
86   PangoLayout  *layout;
87
88   GSList       *marks;
89
90   gint          digits;
91
92   guint         draw_value : 1;
93   guint         value_pos  : 2;
94 };
95
96 struct _GtkScaleMark
97 {
98   gdouble          value;
99   gchar           *markup;
100   GtkPositionType  position; /* always GTK_POS_TOP or GTK_POS_BOTTOM */
101 };
102
103 enum {
104   PROP_0,
105   PROP_DIGITS,
106   PROP_DRAW_VALUE,
107   PROP_VALUE_POS
108 };
109
110 enum {
111   FORMAT_VALUE,
112   LAST_SIGNAL
113 };
114
115 static guint signals[LAST_SIGNAL];
116
117 static void     gtk_scale_set_property            (GObject        *object,
118                                                    guint           prop_id,
119                                                    const GValue   *value,
120                                                    GParamSpec     *pspec);
121 static void     gtk_scale_get_property            (GObject        *object,
122                                                    guint           prop_id,
123                                                    GValue         *value,
124                                                    GParamSpec     *pspec);
125 static void     gtk_scale_get_preferred_width     (GtkWidget      *widget,
126                                                    gint           *minimum,
127                                                    gint           *natural);
128 static void     gtk_scale_get_preferred_height    (GtkWidget      *widget,
129                                                    gint           *minimum,
130                                                    gint           *natural);
131 static void     gtk_scale_style_updated           (GtkWidget      *widget);
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_updated = gtk_scale_style_updated;
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   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SCALE_ACCESSIBLE);
417 }
418
419 static void
420 gtk_scale_orientation_notify (GtkRange         *range,
421                               const GParamSpec *pspec)
422 {
423   GtkOrientation orientation;
424
425   orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (range));
426   gtk_range_set_flippable (range,
427                            orientation == GTK_ORIENTATION_HORIZONTAL);
428 }
429
430 static void
431 gtk_scale_init (GtkScale *scale)
432 {
433   GtkScalePrivate *priv;
434   GtkRange *range = GTK_RANGE (scale);
435   GtkStyleContext *context;
436
437   scale->priv = G_TYPE_INSTANCE_GET_PRIVATE (scale,
438                                              GTK_TYPE_SCALE,
439                                              GtkScalePrivate);
440   priv = scale->priv;
441
442   gtk_widget_set_can_focus (GTK_WIDGET (scale), TRUE);
443
444   gtk_range_set_slider_size_fixed (range, TRUE);
445
446   priv->draw_value = TRUE;
447   priv->value_pos = GTK_POS_TOP;
448   priv->digits = 1;
449   gtk_range_set_round_digits (range, priv->digits);
450
451   gtk_scale_orientation_notify (range, NULL);
452   g_signal_connect (scale, "notify::orientation",
453                     G_CALLBACK (gtk_scale_orientation_notify),
454                     NULL);
455
456   context = gtk_widget_get_style_context (GTK_WIDGET (scale));
457   gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE);
458 }
459
460 static void
461 gtk_scale_set_property (GObject      *object,
462                         guint         prop_id,
463                         const GValue *value,
464                         GParamSpec   *pspec)
465 {
466   GtkScale *scale;
467
468   scale = GTK_SCALE (object);
469
470   switch (prop_id)
471     {
472     case PROP_DIGITS:
473       gtk_scale_set_digits (scale, g_value_get_int (value));
474       break;
475     case PROP_DRAW_VALUE:
476       gtk_scale_set_draw_value (scale, g_value_get_boolean (value));
477       break;
478     case PROP_VALUE_POS:
479       gtk_scale_set_value_pos (scale, g_value_get_enum (value));
480       break;
481     default:
482       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
483       break;
484     }
485 }
486
487 static void
488 gtk_scale_get_property (GObject      *object,
489                         guint         prop_id,
490                         GValue       *value,
491                         GParamSpec   *pspec)
492 {
493   GtkScale *scale = GTK_SCALE (object);
494   GtkScalePrivate *priv = scale->priv;
495
496   switch (prop_id)
497     {
498     case PROP_DIGITS:
499       g_value_set_int (value, priv->digits);
500       break;
501     case PROP_DRAW_VALUE:
502       g_value_set_boolean (value, priv->draw_value);
503       break;
504     case PROP_VALUE_POS:
505       g_value_set_enum (value, priv->value_pos);
506       break;
507     default:
508       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
509       break;
510     }
511 }
512
513 /**
514  * gtk_scale_new:
515  * @orientation: the scale's orientation.
516  * @adjustment: (allow-none): the #GtkAdjustment which sets the range
517  *              of the scale, or %NULL to create a new adjustment.
518  *
519  * Creates a new #GtkScale.
520  *
521  * Return value: a new #GtkScale
522  *
523  * Since: 3.0
524  **/
525 GtkWidget *
526 gtk_scale_new (GtkOrientation  orientation,
527                GtkAdjustment  *adjustment)
528 {
529   g_return_val_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment),
530                         NULL);
531
532   return g_object_new (GTK_TYPE_SCALE,
533                        "orientation", orientation,
534                        "adjustment",  adjustment,
535                        NULL);
536 }
537
538 /**
539  * gtk_scale_new_with_range:
540  * @orientation: the scale's orientation.
541  * @min: minimum value
542  * @max: maximum value
543  * @step: step increment (tick size) used with keyboard shortcuts
544  *
545  * Creates a new scale widget with the given orientation that lets the
546  * user input a number between @min and @max (including @min and @max)
547  * with the increment @step.  @step must be nonzero; it's the distance
548  * the slider moves when using the arrow keys to adjust the scale
549  * value.
550  *
551  * Note that the way in which the precision is derived works best if @step
552  * is a power of ten. If the resulting precision is not suitable for your
553  * needs, use gtk_scale_set_digits() to correct it.
554  *
555  * Return value: a new #GtkScale
556  *
557  * Since: 3.0
558  */
559 GtkWidget *
560 gtk_scale_new_with_range (GtkOrientation orientation,
561                           gdouble        min,
562                           gdouble        max,
563                           gdouble        step)
564 {
565   GtkAdjustment *adj;
566   gint digits;
567
568   g_return_val_if_fail (min < max, NULL);
569   g_return_val_if_fail (step != 0.0, NULL);
570
571   adj = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
572
573   if (fabs (step) >= 1.0 || step == 0.0)
574     {
575       digits = 0;
576     }
577   else
578     {
579       digits = abs ((gint) floor (log10 (fabs (step))));
580       if (digits > 5)
581         digits = 5;
582     }
583
584   return g_object_new (GTK_TYPE_SCALE,
585                        "orientation", orientation,
586                        "adjustment",  adj,
587                        "digits",      digits,
588                        NULL);
589 }
590
591 /**
592  * gtk_scale_set_digits:
593  * @scale: a #GtkScale
594  * @digits: the number of decimal places to display,
595  *     e.g. use 1 to display 1.0, 2 to display 1.00, etc
596  *
597  * Sets the number of decimal places that are displayed in the value.
598  * Also causes the value of the adjustment to be rounded off to this
599  * number of digits, so the retrieved value matches the value the user saw.
600  */
601 void
602 gtk_scale_set_digits (GtkScale *scale,
603                       gint      digits)
604 {
605   GtkScalePrivate *priv;
606   GtkRange *range;
607
608   g_return_if_fail (GTK_IS_SCALE (scale));
609
610   priv = scale->priv;
611   range = GTK_RANGE (scale);
612   
613   digits = CLAMP (digits, -1, MAX_DIGITS);
614
615   if (priv->digits != digits)
616     {
617       priv->digits = digits;
618       if (priv->draw_value)
619         gtk_range_set_round_digits (range, digits);
620
621       _gtk_scale_clear_layout (scale);
622       gtk_widget_queue_resize (GTK_WIDGET (scale));
623
624       g_object_notify (G_OBJECT (scale), "digits");
625     }
626 }
627
628 /**
629  * gtk_scale_get_digits:
630  * @scale: a #GtkScale
631  *
632  * Gets the number of decimal places that are displayed in the value.
633  *
634  * Returns: the number of decimal places that are displayed
635  */
636 gint
637 gtk_scale_get_digits (GtkScale *scale)
638 {
639   g_return_val_if_fail (GTK_IS_SCALE (scale), -1);
640
641   return scale->priv->digits;
642 }
643
644 /**
645  * gtk_scale_set_draw_value:
646  * @scale: a #GtkScale
647  * @draw_value: %TRUE to draw the value
648  * 
649  * Specifies whether the current value is displayed as a string next 
650  * to the slider.
651  */
652 void
653 gtk_scale_set_draw_value (GtkScale *scale,
654                           gboolean  draw_value)
655 {
656   GtkScalePrivate *priv;
657
658   g_return_if_fail (GTK_IS_SCALE (scale));
659
660   priv = scale->priv;
661
662   draw_value = draw_value != FALSE;
663
664   if (priv->draw_value != draw_value)
665     {
666       priv->draw_value = draw_value;
667       if (draw_value)
668         gtk_range_set_round_digits (GTK_RANGE (scale), priv->digits);
669       else
670         gtk_range_set_round_digits (GTK_RANGE (scale), -1);
671
672       _gtk_scale_clear_layout (scale);
673
674       gtk_widget_queue_resize (GTK_WIDGET (scale));
675
676       g_object_notify (G_OBJECT (scale), "draw-value");
677     }
678 }
679
680 /**
681  * gtk_scale_get_draw_value:
682  * @scale: a #GtkScale
683  *
684  * Returns whether the current value is displayed as a string 
685  * next to the slider.
686  *
687  * Returns: whether the current value is displayed as a string
688  */
689 gboolean
690 gtk_scale_get_draw_value (GtkScale *scale)
691 {
692   g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE);
693
694   return scale->priv->draw_value;
695 }
696
697 /**
698  * gtk_scale_set_value_pos:
699  * @scale: a #GtkScale
700  * @pos: the position in which the current value is displayed
701  * 
702  * Sets the position in which the current value is displayed.
703  */
704 void
705 gtk_scale_set_value_pos (GtkScale        *scale,
706                          GtkPositionType  pos)
707 {
708   GtkScalePrivate *priv;
709   GtkWidget *widget;
710
711   g_return_if_fail (GTK_IS_SCALE (scale));
712
713   priv = scale->priv;
714
715   if (priv->value_pos != pos)
716     {
717       priv->value_pos = pos;
718       widget = GTK_WIDGET (scale);
719
720       _gtk_scale_clear_layout (scale);
721       if (gtk_widget_get_visible (widget) && gtk_widget_get_mapped (widget))
722         gtk_widget_queue_resize (widget);
723
724       g_object_notify (G_OBJECT (scale), "value-pos");
725     }
726 }
727
728 /**
729  * gtk_scale_get_value_pos:
730  * @scale: a #GtkScale
731  *
732  * Gets the position in which the current value is displayed.
733  *
734  * Returns: the position in which the current value is displayed
735  */
736 GtkPositionType
737 gtk_scale_get_value_pos (GtkScale *scale)
738 {
739   g_return_val_if_fail (GTK_IS_SCALE (scale), 0);
740
741   return scale->priv->value_pos;
742 }
743
744 static void
745 gtk_scale_get_range_border (GtkRange  *range,
746                             GtkBorder *border)
747 {
748   GtkScalePrivate *priv;
749   GtkWidget *widget;
750   GtkScale *scale;
751   gint w, h;
752   
753   widget = GTK_WIDGET (range);
754   scale = GTK_SCALE (range);
755   priv = scale->priv;
756
757   _gtk_scale_get_value_size (scale, &w, &h);
758
759   border->left = 0;
760   border->right = 0;
761   border->top = 0;
762   border->bottom = 0;
763
764   if (priv->draw_value)
765     {
766       gint value_spacing;
767       gtk_widget_style_get (widget, "value-spacing", &value_spacing, NULL);
768
769       switch (priv->value_pos)
770         {
771         case GTK_POS_LEFT:
772           border->left += w + value_spacing;
773           break;
774         case GTK_POS_RIGHT:
775           border->right += w + value_spacing;
776           break;
777         case GTK_POS_TOP:
778           border->top += h + value_spacing;
779           break;
780         case GTK_POS_BOTTOM:
781           border->bottom += h + value_spacing;
782           break;
783         }
784     }
785
786   if (priv->marks)
787     {
788       gint slider_width;
789       gint value_spacing;
790       gint n1, w1, h1, n2, w2, h2;
791   
792       gtk_widget_style_get (widget, 
793                             "slider-width", &slider_width,
794                             "value-spacing", &value_spacing, 
795                             NULL);
796
797
798       gtk_scale_get_mark_label_size (scale, GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
799
800       if (gtk_orientable_get_orientation (GTK_ORIENTABLE (scale)) == GTK_ORIENTATION_HORIZONTAL)
801         {
802           if (n1 > 0)
803             border->top += h1 + value_spacing + slider_width / 2;
804           if (n2 > 0)
805             border->bottom += h2 + value_spacing + slider_width / 2;
806         }
807       else
808         {
809           if (n1 > 0)
810             border->left += w1 + value_spacing + slider_width / 2;
811           if (n2 > 0)
812             border->right += w2 + value_spacing + slider_width / 2;
813         }
814     }
815 }
816
817 /* FIXME this could actually be static at the moment. */
818 void
819 _gtk_scale_get_value_size (GtkScale *scale,
820                            gint     *width,
821                            gint     *height)
822 {
823   GtkScalePrivate *priv = scale->priv;
824   GtkRange *range;
825
826   if (priv->draw_value)
827     {
828       GtkAdjustment *adjustment;
829       PangoLayout *layout;
830       PangoRectangle logical_rect;
831       gchar *txt;
832       
833       range = GTK_RANGE (scale);
834
835       layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
836       adjustment = gtk_range_get_adjustment (range);
837
838       txt = _gtk_scale_format_value (scale, gtk_adjustment_get_lower (adjustment));
839       pango_layout_set_text (layout, txt, -1);
840       g_free (txt);
841       
842       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
843
844       if (width)
845         *width = logical_rect.width;
846       if (height)
847         *height = logical_rect.height;
848
849       txt = _gtk_scale_format_value (scale, gtk_adjustment_get_upper (adjustment));
850       pango_layout_set_text (layout, txt, -1);
851       g_free (txt);
852       
853       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
854
855       if (width)
856         *width = MAX (*width, logical_rect.width);
857       if (height)
858         *height = MAX (*height, logical_rect.height);
859
860       g_object_unref (layout);
861     }
862   else
863     {
864       if (width)
865         *width = 0;
866       if (height)
867         *height = 0;
868     }
869
870 }
871
872 static void
873 gtk_scale_get_mark_label_size (GtkScale        *scale,
874                                GtkPositionType  position,
875                                gint            *count1,
876                                gint            *width1,
877                                gint            *height1,
878                                gint            *count2,
879                                gint            *width2,
880                                gint            *height2)
881 {
882   GtkScalePrivate *priv = scale->priv;
883   PangoLayout *layout;
884   PangoRectangle logical_rect;
885   GSList *m;
886   gint w, h;
887
888   *count1 = *count2 = 0;
889   *width1 = *width2 = 0;
890   *height1 = *height2 = 0;
891
892   layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
893
894   for (m = priv->marks; m; m = m->next)
895     {
896       GtkScaleMark *mark = m->data;
897
898       if (mark->markup && *mark->markup)
899         {
900           pango_layout_set_markup (layout, mark->markup, -1);
901           pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
902
903           w = logical_rect.width;
904           h = logical_rect.height;
905         }
906       else
907         {
908           w = 0;
909           h = 0;
910         }
911
912       if (mark->position == position)
913         {
914           (*count1)++;
915           *width1 = MAX (*width1, w);
916           *height1 = MAX (*height1, h);
917         }
918       else
919         {
920           (*count2)++;
921           *width2 = MAX (*width2, w);
922           *height2 = MAX (*height2, h);
923         }
924     }
925
926   g_object_unref (layout);
927 }
928
929 static void
930 gtk_scale_style_updated (GtkWidget *widget)
931 {
932   gint slider_length;
933   GtkRange *range;
934
935   range = GTK_RANGE (widget);
936   
937   gtk_widget_style_get (widget,
938                         "slider-length", &slider_length,
939                         NULL);
940
941   gtk_range_set_min_slider_size (range, slider_length);
942
943   _gtk_scale_clear_layout (GTK_SCALE (widget));
944
945   GTK_WIDGET_CLASS (gtk_scale_parent_class)->style_updated (widget);
946 }
947
948 static void
949 gtk_scale_screen_changed (GtkWidget *widget,
950                           GdkScreen *old_screen)
951 {
952   _gtk_scale_clear_layout (GTK_SCALE (widget));
953 }
954
955 static void
956 gtk_scale_get_preferred_width (GtkWidget *widget,
957                                gint      *minimum,
958                                gint      *natural)
959 {
960   GTK_WIDGET_CLASS (gtk_scale_parent_class)->get_preferred_width (widget, minimum, natural);
961   
962   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
963     {
964       gint n1, w1, h1, n2, w2, h2;
965       gint slider_length;
966       gint w;
967
968       gtk_widget_style_get (widget, "slider-length", &slider_length, NULL);
969
970       gtk_scale_get_mark_label_size (GTK_SCALE (widget), GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
971
972       w1 = (n1 - 1) * w1 + MAX (w1, slider_length);
973       w2 = (n2 - 1) * w2 + MAX (w2, slider_length);
974       w = MAX (w1, w2);
975
976       *minimum = MAX (*minimum, w);
977       *natural = MAX (*natural, w);
978     }
979 }
980
981 static void
982 gtk_scale_get_preferred_height (GtkWidget *widget,
983                                 gint      *minimum,
984                                 gint      *natural)
985 {
986   GTK_WIDGET_CLASS (gtk_scale_parent_class)->get_preferred_height (widget, minimum, natural);
987
988
989   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_VERTICAL)
990     {
991       gint n1, w1, h1, n2, w2, h2;
992       gint slider_length;
993       gint h;
994
995       gtk_widget_style_get (widget, "slider-length", &slider_length, NULL);
996
997       gtk_scale_get_mark_label_size (GTK_SCALE (widget), GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
998       h1 = (n1 - 1) * h1 + MAX (h1, slider_length);
999       h2 = (n2 - 1) * h1 + MAX (h2, slider_length);
1000       h = MAX (h1, h2);
1001
1002       *minimum = MAX (*minimum, h);
1003       *natural = MAX (*natural, h);
1004     }
1005 }
1006
1007 static gint
1008 find_next_pos (GtkWidget      *widget,
1009                GSList          *list,
1010                gint            *marks,
1011                GtkPositionType  pos,
1012                gint             match)
1013 {
1014   GtkAllocation allocation;
1015   GSList *m;
1016   gint i;
1017
1018   for (m = list->next, i = 1; m; m = m->next, i++)
1019     {
1020       GtkScaleMark *mark = m->data;
1021
1022       if (match == (mark->position == pos))
1023         return marks[i];
1024     }
1025
1026   gtk_widget_get_allocation (widget, &allocation);
1027   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
1028     return allocation.width;
1029   else
1030     return allocation.height;
1031 }
1032
1033 static gboolean
1034 gtk_scale_draw (GtkWidget *widget,
1035                 cairo_t   *cr)
1036 {
1037   GtkScale *scale = GTK_SCALE (widget);
1038   GtkScalePrivate *priv = scale->priv;
1039   GtkRange *range = GTK_RANGE (scale);
1040   GtkStateFlags state = 0;
1041   GtkStyleContext *context;
1042   gint *marks;
1043   gint focus_padding;
1044   gint slider_width;
1045   gint value_spacing;
1046   gint min_sep = 4;
1047
1048   context = gtk_widget_get_style_context (widget);
1049   gtk_widget_style_get (widget,
1050                         "focus-padding", &focus_padding,
1051                         "slider-width", &slider_width,
1052                         "value-spacing", &value_spacing,
1053                         NULL);
1054
1055   /* We need to chain up _first_ so the various geometry members of
1056    * GtkRange struct are updated.
1057    */
1058   GTK_WIDGET_CLASS (gtk_scale_parent_class)->draw (widget, cr);
1059
1060   if (!gtk_widget_is_sensitive (widget))
1061     state |= GTK_STATE_FLAG_INSENSITIVE;
1062
1063   if (priv->marks)
1064     {
1065       GtkOrientation orientation;
1066       GdkRectangle range_rect;
1067       gint i;
1068       gint x1, x2, x3, y1, y2, y3;
1069       PangoLayout *layout;
1070       PangoRectangle logical_rect;
1071       GSList *m;
1072       gint min_pos_before, min_pos_after;
1073       gint min_pos, max_pos;
1074
1075       orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (range));
1076       _gtk_range_get_stop_positions (range, &marks);
1077       layout = gtk_widget_create_pango_layout (widget, NULL);
1078       gtk_range_get_range_rect (range, &range_rect);
1079
1080       min_pos_before = min_pos_after = 0;
1081
1082       for (m = priv->marks, i = 0; m; m = m->next, i++)
1083         {
1084           GtkScaleMark *mark = m->data;
1085
1086           if (orientation == GTK_ORIENTATION_HORIZONTAL)
1087             {
1088               x1 = marks[i];
1089               if (mark->position == GTK_POS_TOP)
1090                 {
1091                   y1 = range_rect.y;
1092                   y2 = y1 - slider_width / 2;
1093                   min_pos = min_pos_before;
1094                   max_pos = find_next_pos (widget, m, marks + i, GTK_POS_TOP, 1) - min_sep;
1095                 }
1096               else
1097                 {
1098                   y1 = range_rect.y + range_rect.height;
1099                   y2 = y1 + slider_width / 2;
1100                   min_pos = min_pos_after;
1101                   max_pos = find_next_pos (widget, m, marks + i, GTK_POS_BOTTOM, 1) - min_sep;
1102                 }
1103
1104               gtk_style_context_save (context);
1105               gtk_style_context_add_class (context, GTK_STYLE_CLASS_MARK);
1106               gtk_style_context_set_state (context, state);
1107
1108               gtk_render_line (context, cr, x1, y1, x1, y2);
1109
1110               if (mark->markup)
1111                 {
1112                   pango_layout_set_markup (layout, mark->markup, -1);
1113                   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1114
1115                   x3 = x1 - logical_rect.width / 2;
1116                   if (x3 < min_pos)
1117                     x3 = min_pos;
1118                   if (x3 + logical_rect.width > max_pos)
1119                         x3 = max_pos - logical_rect.width;
1120                   if (x3 < 0)
1121                      x3 = 0;
1122                   if (mark->position == GTK_POS_TOP)
1123                     {
1124                       y3 = y2 - value_spacing - logical_rect.height;
1125                       min_pos_before = x3 + logical_rect.width + min_sep;
1126                     }
1127                   else
1128                     {
1129                       y3 = y2 + value_spacing;
1130                       min_pos_after = x3 + logical_rect.width + min_sep;
1131                     }
1132
1133                   gtk_render_layout (context, cr, x3, y3, layout);
1134                 }
1135
1136               gtk_style_context_restore (context);
1137             }
1138           else
1139             {
1140               if (mark->position == GTK_POS_TOP)
1141                 {
1142                   x1 = range_rect.x;
1143                   x2 = range_rect.x - slider_width / 2;
1144                   min_pos = min_pos_before;
1145                   max_pos = find_next_pos (widget, m, marks + i, GTK_POS_TOP, 1) - min_sep;
1146                 }
1147               else
1148                 {
1149                   x1 = range_rect.x + range_rect.width;
1150                   x2 = range_rect.x + range_rect.width + slider_width / 2;
1151                   min_pos = min_pos_after;
1152                   max_pos = find_next_pos (widget, m, marks + i, GTK_POS_BOTTOM, 1) - min_sep;
1153                 }
1154               y1 = marks[i];
1155
1156               gtk_style_context_save (context);
1157               gtk_style_context_add_class (context, GTK_STYLE_CLASS_MARK);
1158               gtk_style_context_set_state (context, state);
1159
1160               gtk_render_line (context, cr, x1, y1, x2, y1);
1161
1162               if (mark->markup)
1163                 {
1164                   pango_layout_set_markup (layout, mark->markup, -1);
1165                   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1166
1167                   y3 = y1 - logical_rect.height / 2;
1168                   if (y3 < min_pos)
1169                     y3 = min_pos;
1170                   if (y3 + logical_rect.height > max_pos)
1171                     y3 = max_pos - logical_rect.height;
1172                   if (y3 < 0)
1173                     y3 = 0;
1174                   if (mark->position == GTK_POS_TOP)
1175                     {
1176                       x3 = x2 - value_spacing - logical_rect.width;
1177                       min_pos_before = y3 + logical_rect.height + min_sep;
1178                     }
1179                   else
1180                     {
1181                       x3 = x2 + value_spacing;
1182                       min_pos_after = y3 + logical_rect.height + min_sep;
1183                     }
1184
1185                   gtk_render_layout (context, cr, x3, y3, layout);
1186                 }
1187
1188               gtk_style_context_restore (context);
1189             }
1190         }
1191
1192       g_object_unref (layout);
1193       g_free (marks);
1194     }
1195
1196   if (priv->draw_value)
1197     {
1198       GtkAllocation allocation;
1199
1200       PangoLayout *layout;
1201       gint x, y;
1202
1203       layout = gtk_scale_get_layout (scale);
1204       gtk_scale_get_layout_offsets (scale, &x, &y);
1205       gtk_widget_get_allocation (widget, &allocation);
1206
1207       gtk_render_layout (context, cr,
1208                          x - allocation.x,
1209                          y - allocation.y,
1210                          layout);
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     return g_strdup_printf ("%0.*f", priv->digits, value);
1342 }
1343
1344 static void
1345 gtk_scale_finalize (GObject *object)
1346 {
1347   GtkScale *scale = GTK_SCALE (object);
1348
1349   _gtk_scale_clear_layout (scale);
1350   gtk_scale_clear_marks (scale);
1351
1352   G_OBJECT_CLASS (gtk_scale_parent_class)->finalize (object);
1353 }
1354
1355 /**
1356  * gtk_scale_get_layout:
1357  * @scale: A #GtkScale
1358  *
1359  * Gets the #PangoLayout used to display the scale. The returned
1360  * object is owned by the scale so does not need to be freed by
1361  * the caller.
1362  *
1363  * Return value: (transfer none): the #PangoLayout for this scale,
1364  *     or %NULL if the #GtkScale:draw-value property is %FALSE.
1365  *
1366  * Since: 2.4
1367  */
1368 PangoLayout *
1369 gtk_scale_get_layout (GtkScale *scale)
1370 {
1371   GtkScalePrivate *priv;
1372   gchar *txt;
1373
1374   g_return_val_if_fail (GTK_IS_SCALE (scale), NULL);
1375
1376   priv = scale->priv;
1377
1378   if (!priv->layout)
1379     {
1380       if (priv->draw_value)
1381         priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
1382     }
1383
1384   if (priv->draw_value)
1385     {
1386       txt = _gtk_scale_format_value (scale,
1387                                      gtk_adjustment_get_value (gtk_range_get_adjustment (GTK_RANGE (scale))));
1388       pango_layout_set_text (priv->layout, txt, -1);
1389       g_free (txt);
1390     }
1391
1392   return priv->layout;
1393 }
1394
1395 /**
1396  * gtk_scale_get_layout_offsets:
1397  * @scale: a #GtkScale
1398  * @x: (out) (allow-none): location to store X offset of layout, or %NULL
1399  * @y: (out) (allow-none): location to store Y offset of layout, or %NULL
1400  *
1401  * Obtains the coordinates where the scale will draw the 
1402  * #PangoLayout representing the text in the scale. Remember
1403  * when using the #PangoLayout function you need to convert to
1404  * and from pixels using PANGO_PIXELS() or #PANGO_SCALE. 
1405  *
1406  * If the #GtkScale:draw-value property is %FALSE, the return 
1407  * values are undefined.
1408  *
1409  * Since: 2.4
1410  */
1411 void 
1412 gtk_scale_get_layout_offsets (GtkScale *scale,
1413                               gint     *x,
1414                               gint     *y)
1415 {
1416   gint local_x = 0; 
1417   gint local_y = 0;
1418
1419   g_return_if_fail (GTK_IS_SCALE (scale));
1420
1421   if (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets)
1422     (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets) (scale, &local_x, &local_y);
1423
1424   if (x)
1425     *x = local_x;
1426   
1427   if (y)
1428     *y = local_y;
1429 }
1430
1431 void
1432 _gtk_scale_clear_layout (GtkScale *scale)
1433 {
1434   GtkScalePrivate *priv = scale->priv;
1435
1436   g_return_if_fail (GTK_IS_SCALE (scale));
1437
1438   if (priv->layout)
1439     {
1440       g_object_unref (priv->layout);
1441       priv->layout = NULL;
1442     }
1443 }
1444
1445 static void
1446 gtk_scale_mark_free (GtkScaleMark *mark)
1447 {
1448   g_free (mark->markup);
1449   g_free (mark);
1450 }
1451
1452 /**
1453  * gtk_scale_clear_marks:
1454  * @scale: a #GtkScale
1455  * 
1456  * Removes any marks that have been added with gtk_scale_add_mark().
1457  *
1458  * Since: 2.16
1459  */
1460 void
1461 gtk_scale_clear_marks (GtkScale *scale)
1462 {
1463   GtkScalePrivate *priv;
1464   GtkStyleContext *context;
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   context = gtk_widget_get_style_context (GTK_WIDGET (scale));
1475   gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
1476   gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
1477
1478   _gtk_range_set_stop_values (GTK_RANGE (scale), NULL, 0);
1479
1480   gtk_widget_queue_resize (GTK_WIDGET (scale));
1481 }
1482
1483 static gint
1484 compare_marks (gpointer a, gpointer b)
1485 {
1486   GtkScaleMark *ma, *mb;
1487
1488   ma = a; mb = b;
1489
1490   return (gint) (ma->value - mb->value);
1491 }
1492
1493 /**
1494  * gtk_scale_add_mark:
1495  * @scale: a #GtkScale
1496  * @value: the value at which the mark is placed, must be between
1497  *   the lower and upper limits of the scales' adjustment
1498  * @position: where to draw the mark. For a horizontal scale, #GTK_POS_TOP
1499  *   and %GTK_POS_LEFT are drawn above the scale, anything else below.
1500  *   For a vertical scale, #GTK_POS_LEFT and %GTK_POS_TOP are drawn to
1501  *   the left of the scale, anything else to the right.
1502  * @markup: (allow-none): Text to be shown at the mark, using <link linkend="PangoMarkupFormat">Pango markup</link>, or %NULL
1503  *
1504  *
1505  * Adds a mark at @value.
1506  *
1507  * A mark is indicated visually by drawing a tick mark next to the scale,
1508  * and GTK+ makes it easy for the user to position the scale exactly at the
1509  * marks value.
1510  *
1511  * If @markup is not %NULL, text is shown next to the tick mark.
1512  *
1513  * To remove marks from a scale, use gtk_scale_clear_marks().
1514  *
1515  * Since: 2.16
1516  */
1517 void
1518 gtk_scale_add_mark (GtkScale        *scale,
1519                     gdouble          value,
1520                     GtkPositionType  position,
1521                     const gchar     *markup)
1522 {
1523   GtkScalePrivate *priv;
1524   GtkScaleMark *mark;
1525   GSList *m;
1526   gdouble *values;
1527   gint n, i;
1528   GtkStyleContext *context;
1529   int all_pos;
1530
1531   g_return_if_fail (GTK_IS_SCALE (scale));
1532
1533   priv = scale->priv;
1534
1535   mark = g_new (GtkScaleMark, 1);
1536   mark->value = value;
1537   mark->markup = g_strdup (markup);
1538   if (position == GTK_POS_LEFT ||
1539       position == GTK_POS_TOP)
1540     mark->position = GTK_POS_TOP;
1541   else
1542     mark->position = GTK_POS_BOTTOM;
1543
1544   priv->marks = g_slist_insert_sorted (priv->marks, mark,
1545                                        (GCompareFunc) compare_marks);
1546
1547 #define MARKS_ABOVE 1
1548 #define MARKS_BELOW 2
1549
1550   all_pos = 0;
1551   n = g_slist_length (priv->marks);
1552   values = g_new (gdouble, n);
1553   for (m = priv->marks, i = 0; m; m = m->next, i++)
1554     {
1555       mark = m->data;
1556       values[i] = mark->value;
1557       if (mark->position == GTK_POS_TOP)
1558         all_pos |= MARKS_ABOVE;
1559       else
1560         all_pos |= MARKS_BELOW;
1561     }
1562
1563   _gtk_range_set_stop_values (GTK_RANGE (scale), values, n);
1564
1565   g_free (values);
1566
1567   /* Set the style classes for the slider, so it could
1568    * point to the right direction when marks are present
1569    */
1570   context = gtk_widget_get_style_context (GTK_WIDGET (scale));
1571
1572   if (all_pos & MARKS_ABOVE)
1573     gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
1574   else
1575     gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
1576   if (all_pos & MARKS_BELOW)
1577     gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
1578   else
1579     gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
1580
1581   gtk_widget_queue_resize (GTK_WIDGET (scale));
1582 }
1583
1584 static GtkBuildableIface *parent_buildable_iface;
1585
1586 static void
1587 gtk_scale_buildable_interface_init (GtkBuildableIface *iface)
1588 {
1589   parent_buildable_iface = g_type_interface_peek_parent (iface);
1590   iface->custom_tag_start = gtk_scale_buildable_custom_tag_start;
1591   iface->custom_finished = gtk_scale_buildable_custom_finished;
1592 }
1593
1594 typedef struct
1595 {
1596   GtkScale *scale;
1597   GtkBuilder *builder;
1598   GSList *marks;
1599 } MarksSubparserData;
1600
1601 typedef struct
1602 {
1603   gdouble value;
1604   GtkPositionType position;
1605   GString *markup;
1606   gchar *context;
1607   gboolean translatable;
1608 } MarkData;
1609
1610 static void
1611 mark_data_free (MarkData *data)
1612 {
1613   g_string_free (data->markup, TRUE);
1614   g_free (data->context);
1615   g_slice_free (MarkData, data);
1616 }
1617
1618 static void
1619 marks_start_element (GMarkupParseContext *context,
1620                      const gchar         *element_name,
1621                      const gchar        **names,
1622                      const gchar        **values,
1623                      gpointer             user_data,
1624                      GError             **error)
1625 {
1626   MarksSubparserData *parser_data = (MarksSubparserData*)user_data;
1627   guint i;
1628   gint line_number, char_number;
1629
1630   if (strcmp (element_name, "marks") == 0)
1631    ;
1632   else if (strcmp (element_name, "mark") == 0)
1633     {
1634       gdouble value = 0;
1635       gboolean has_value = FALSE;
1636       GtkPositionType position = GTK_POS_BOTTOM;
1637       const gchar *msg_context = NULL;
1638       gboolean translatable = FALSE;
1639       MarkData *mark;
1640
1641       for (i = 0; names[i]; i++)
1642         {
1643           if (strcmp (names[i], "translatable") == 0)
1644             {
1645               if (!_gtk_builder_boolean_from_string (values[i], &translatable, error))
1646                 return;
1647             }
1648           else if (strcmp (names[i], "comments") == 0)
1649             {
1650               /* do nothing, comments are for translators */
1651             }
1652           else if (strcmp (names[i], "context") == 0)
1653             msg_context = values[i];
1654           else if (strcmp (names[i], "value") == 0)
1655             {
1656               GValue gvalue = { 0, };
1657
1658               if (!gtk_builder_value_from_string_type (parser_data->builder, G_TYPE_DOUBLE, values[i], &gvalue, error))
1659                 return;
1660
1661               value = g_value_get_double (&gvalue);
1662               has_value = TRUE;
1663             }
1664           else if (strcmp (names[i], "position") == 0)
1665             {
1666               GValue gvalue = { 0, };
1667
1668               if (!gtk_builder_value_from_string_type (parser_data->builder, GTK_TYPE_POSITION_TYPE, values[i], &gvalue, error))
1669                 return;
1670
1671               position = g_value_get_enum (&gvalue);
1672             }
1673           else
1674             {
1675               g_markup_parse_context_get_position (context,
1676                                                    &line_number,
1677                                                    &char_number);
1678               g_set_error (error,
1679                            GTK_BUILDER_ERROR,
1680                            GTK_BUILDER_ERROR_INVALID_ATTRIBUTE,
1681                            "%s:%d:%d '%s' is not a valid attribute of <%s>",
1682                            "<input>",
1683                            line_number, char_number, names[i], "mark");
1684               return;
1685             }
1686         }
1687
1688       if (!has_value)
1689         {
1690           g_markup_parse_context_get_position (context,
1691                                                &line_number,
1692                                                &char_number);
1693           g_set_error (error,
1694                        GTK_BUILDER_ERROR,
1695                        GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
1696                        "%s:%d:%d <%s> requires attribute \"%s\"",
1697                        "<input>",
1698                        line_number, char_number, "mark",
1699                        "value");
1700           return;
1701         }
1702
1703       mark = g_slice_new (MarkData);
1704       mark->value = value;
1705       if (position == GTK_POS_LEFT ||
1706           position == GTK_POS_TOP)
1707         mark->position = GTK_POS_TOP;
1708       else
1709         mark->position = GTK_POS_BOTTOM;
1710       mark->markup = g_string_new ("");
1711       mark->context = g_strdup (msg_context);
1712       mark->translatable = translatable;
1713
1714       parser_data->marks = g_slist_prepend (parser_data->marks, mark);
1715     }
1716   else
1717     {
1718       g_markup_parse_context_get_position (context,
1719                                            &line_number,
1720                                            &char_number);
1721       g_set_error (error,
1722                    GTK_BUILDER_ERROR,
1723                    GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
1724                    "%s:%d:%d unsupported tag for GtkScale: \"%s\"",
1725                    "<input>",
1726                    line_number, char_number, element_name);
1727       return;
1728     }
1729 }
1730
1731 static void
1732 marks_text (GMarkupParseContext  *context,
1733             const gchar          *text,
1734             gsize                 text_len,
1735             gpointer              user_data,
1736             GError              **error)
1737 {
1738   MarksSubparserData *data = (MarksSubparserData*)user_data;
1739
1740   if (strcmp (g_markup_parse_context_get_element (context), "mark") == 0)
1741     {
1742       MarkData *mark = data->marks->data;
1743
1744       g_string_append_len (mark->markup, text, text_len);
1745     }
1746 }
1747
1748 static const GMarkupParser marks_parser =
1749   {
1750     marks_start_element,
1751     NULL,
1752     marks_text,
1753   };
1754
1755
1756 static gboolean
1757 gtk_scale_buildable_custom_tag_start (GtkBuildable  *buildable,
1758                                       GtkBuilder    *builder,
1759                                       GObject       *child,
1760                                       const gchar   *tagname,
1761                                       GMarkupParser *parser,
1762                                       gpointer      *data)
1763 {
1764   MarksSubparserData *parser_data;
1765
1766   if (child)
1767     return FALSE;
1768
1769   if (strcmp (tagname, "marks") == 0)
1770     {
1771       parser_data = g_slice_new0 (MarksSubparserData);
1772       parser_data->scale = GTK_SCALE (buildable);
1773       parser_data->marks = NULL;
1774
1775       *parser = marks_parser;
1776       *data = parser_data;
1777       return TRUE;
1778     }
1779
1780   return parent_buildable_iface->custom_tag_start (buildable, builder, child,
1781                                                    tagname, parser, data);
1782 }
1783
1784 static void
1785 gtk_scale_buildable_custom_finished (GtkBuildable *buildable,
1786                                      GtkBuilder   *builder,
1787                                      GObject      *child,
1788                                      const gchar  *tagname,
1789                                      gpointer      user_data)
1790 {
1791   GtkScale *scale = GTK_SCALE (buildable);
1792   MarksSubparserData *marks_data;
1793
1794   if (strcmp (tagname, "marks") == 0)
1795     {
1796       GSList *m;
1797       gchar *markup;
1798
1799       marks_data = (MarksSubparserData *)user_data;
1800
1801       for (m = marks_data->marks; m; m = m->next)
1802         {
1803           MarkData *mdata = m->data;
1804
1805           if (mdata->translatable && mdata->markup->len)
1806             markup = _gtk_builder_parser_translate (gtk_builder_get_translation_domain (builder),
1807                                                     mdata->context,
1808                                                     mdata->markup->str);
1809           else
1810             markup = mdata->markup->str;
1811
1812           gtk_scale_add_mark (scale, mdata->value, mdata->position, markup);
1813
1814           mark_data_free (mdata);
1815         }
1816
1817       g_slist_free (marks_data->marks);
1818       g_slice_free (MarksSubparserData, marks_data);
1819     }
1820 }