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