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