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