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