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