]> Pileus Git - ~andy/gtk/blob - gtk/gtkscale.c
Tons of transfer annotations
[~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_expose                  (GtkWidget      *widget,
137                                                    GdkEventExpose *event);
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->expose_event = gtk_scale_expose;
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   GtkObject *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_expose (GtkWidget      *widget,
998                   GdkEventExpose *event)
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   GdkWindow *window;
1006   gint n_marks;
1007   gint *marks;
1008   gint focus_padding;
1009   gint slider_width;
1010   gint value_spacing;
1011   gint min_sep = 4;
1012
1013   style = gtk_widget_get_style (widget);
1014   gtk_widget_style_get (widget,
1015                         "focus-padding", &focus_padding,
1016                         "slider-width", &slider_width, 
1017                         "value-spacing", &value_spacing, 
1018                         NULL);
1019
1020   /* We need to chain up _first_ so the various geometry members of
1021    * GtkRange struct are updated.
1022    */
1023   GTK_WIDGET_CLASS (gtk_scale_parent_class)->expose_event (widget, event);
1024
1025   window = gtk_widget_get_window (widget);
1026
1027   state_type = GTK_STATE_NORMAL;
1028   if (!gtk_widget_is_sensitive (widget))
1029     state_type = GTK_STATE_INSENSITIVE;
1030
1031   if (priv->marks)
1032     {
1033       GtkAllocation allocation;
1034       GtkOrientation orientation;
1035       GdkRectangle range_rect;
1036       gint i;
1037       gint x1, x2, x3, y1, y2, y3;
1038       PangoLayout *layout;
1039       PangoRectangle logical_rect;
1040       GSList *m;
1041       gint min_pos_before, min_pos_after;
1042       gint min_pos, max_pos;
1043
1044       orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (range));
1045       n_marks = _gtk_range_get_stop_positions (range, &marks);
1046       layout = gtk_widget_create_pango_layout (widget, NULL);
1047       gtk_widget_get_allocation (widget, &allocation);
1048       gtk_range_get_range_rect (range, &range_rect);
1049
1050       if (orientation == GTK_ORIENTATION_HORIZONTAL)
1051         min_pos_before = min_pos_after = allocation.x;
1052       else
1053         min_pos_before = min_pos_after = allocation.y;
1054       for (m = priv->marks, i = 0; m; m = m->next, i++)
1055         {
1056           GtkScaleMark *mark = m->data;
1057
1058           if (orientation == GTK_ORIENTATION_HORIZONTAL)
1059             {
1060               x1 = allocation.x + marks[i];
1061               if (mark->position == GTK_POS_TOP)
1062                 {
1063                   y1 = allocation.y + range_rect.y;
1064                   y2 = y1 - slider_width / 2;
1065                   min_pos = min_pos_before;
1066                   max_pos = allocation.x + find_next_pos (widget, m, marks + i, GTK_POS_TOP, 1) - min_sep;
1067                 }
1068               else
1069                 {
1070                   y1 = allocation.y + range_rect.y + range_rect.height;
1071                   y2 = y1 + slider_width / 2;
1072                   min_pos = min_pos_after;
1073                   max_pos = allocation.x + find_next_pos (widget, m, marks + i, GTK_POS_TOP, 0) - min_sep;
1074                 }
1075
1076               gtk_paint_vline (style, window, state_type,
1077                                NULL, widget, "scale-mark", y1, y2, x1);
1078
1079               if (mark->markup)
1080                 {
1081                   pango_layout_set_markup (layout, mark->markup, -1);
1082                   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1083
1084                   x3 = x1 - logical_rect.width / 2;
1085                   if (x3 < min_pos)
1086                     x3 = min_pos;
1087                   if (x3 + logical_rect.width > max_pos)
1088                         x3 = max_pos - logical_rect.width;
1089                   if (x3 < allocation.x)
1090                      x3 = allocation.x;
1091                   if (mark->position == GTK_POS_TOP)
1092                     {
1093                       y3 = y2 - value_spacing - logical_rect.height;
1094                       min_pos_before = x3 + logical_rect.width + min_sep;
1095                     }
1096                   else
1097                     {
1098                       y3 = y2 + value_spacing;
1099                       min_pos_after = x3 + logical_rect.width + min_sep;
1100                     }
1101
1102                   gtk_paint_layout (style, window, state_type,
1103                                     FALSE, NULL, widget, "scale-mark",
1104                                     x3, y3, layout);
1105                 }
1106             }
1107           else
1108             {
1109               if (mark->position == GTK_POS_LEFT)
1110                 {
1111                   x1 = allocation.x + range_rect.x;
1112                   x2 = allocation.x + range_rect.x - slider_width / 2;
1113                   min_pos = min_pos_before;
1114                   max_pos = allocation.y + find_next_pos (widget, m, marks + i, GTK_POS_LEFT, 1) - min_sep;
1115                 }
1116               else
1117                 {
1118                   x1 = allocation.x + range_rect.x + range_rect.width;
1119                   x2 = allocation.x + range_rect.x + range_rect.width + slider_width / 2;
1120                   min_pos = min_pos_after;
1121                   max_pos = allocation.y + find_next_pos (widget, m, marks + i, GTK_POS_LEFT, 0) - min_sep;
1122                 }
1123               y1 = allocation.y + marks[i];
1124
1125               gtk_paint_hline (style, window, state_type,
1126                                NULL, widget, "range-mark", x1, x2, y1);
1127
1128               if (mark->markup)
1129                 {
1130                   pango_layout_set_markup (layout, mark->markup, -1);
1131                   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1132
1133                   y3 = y1 - logical_rect.height / 2;
1134                   if (y3 < min_pos)
1135                     y3 = min_pos;
1136                   if (y3 + logical_rect.height > max_pos)
1137                     y3 = max_pos - logical_rect.height;
1138                   if (y3 < allocation.y)
1139                     y3 = allocation.y;
1140                   if (mark->position == GTK_POS_LEFT)
1141                     {
1142                       x3 = x2 - value_spacing - logical_rect.width;
1143                       min_pos_before = y3 + logical_rect.height + min_sep;
1144                     }
1145                   else
1146                     {
1147                       x3 = x2 + value_spacing;
1148                       min_pos_after = y3 + logical_rect.height + min_sep;
1149                     }
1150
1151                   gtk_paint_layout (style, window, state_type,
1152                                     FALSE, NULL, widget, "scale-mark",
1153                                     x3, y3, layout);
1154                 }
1155             }
1156         } 
1157
1158       g_object_unref (layout);
1159       g_free (marks);
1160     }
1161
1162   if (priv->draw_value)
1163     {
1164       GtkOrientation orientation;
1165       PangoLayout *layout;
1166       gint x, y;
1167
1168       orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (range));
1169       layout = gtk_scale_get_layout (scale);
1170       gtk_scale_get_layout_offsets (scale, &x, &y);
1171
1172       gtk_paint_layout (style,
1173                         window,
1174                         state_type,
1175                         FALSE,
1176                         NULL,
1177                         widget,
1178                         orientation == GTK_ORIENTATION_HORIZONTAL ?
1179                         "hscale" : "vscale",
1180                         x, y,
1181                         layout);
1182
1183     }
1184
1185   return FALSE;
1186 }
1187
1188 static void
1189 gtk_scale_real_get_layout_offsets (GtkScale *scale,
1190                                    gint     *x,
1191                                    gint     *y)
1192 {
1193   GtkScalePrivate *priv = scale->priv;
1194   GtkAllocation allocation;
1195   GtkWidget *widget = GTK_WIDGET (scale);
1196   GtkRange *range = GTK_RANGE (widget);
1197   GdkRectangle range_rect;
1198   PangoLayout *layout = gtk_scale_get_layout (scale);
1199   PangoRectangle logical_rect;
1200   gint slider_start, slider_end;
1201   gint value_spacing;
1202
1203   if (!layout)
1204     {
1205       *x = 0;
1206       *y = 0;
1207
1208       return;
1209     }
1210
1211   gtk_widget_style_get (widget, "value-spacing", &value_spacing, NULL);
1212
1213   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1214
1215   gtk_widget_get_allocation (widget, &allocation);
1216   gtk_range_get_range_rect (range, &range_rect);
1217   gtk_range_get_slider_range (range,
1218                               &slider_start,
1219                               &slider_end);
1220
1221   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL)
1222     {
1223       switch (priv->value_pos)
1224         {
1225         case GTK_POS_LEFT:
1226           *x = range_rect.x - value_spacing - logical_rect.width;
1227           *y = range_rect.y + (range_rect.height - logical_rect.height) / 2;
1228           break;
1229
1230         case GTK_POS_RIGHT:
1231           *x = range_rect.x + range_rect.width + value_spacing;
1232           *y = range_rect.y + (range_rect.height - logical_rect.height) / 2;
1233           break;
1234
1235         case GTK_POS_TOP:
1236           *x = slider_start + (slider_end - slider_start - logical_rect.width) / 2;
1237           *x = CLAMP (*x, 0, allocation.width - logical_rect.width);
1238           *y = range_rect.y - logical_rect.height - value_spacing;
1239           break;
1240
1241         case GTK_POS_BOTTOM:
1242           *x = slider_start + (slider_end - slider_start - logical_rect.width) / 2;
1243           *x = CLAMP (*x, 0, allocation.width - logical_rect.width);
1244           *y = range_rect.y + range_rect.height + value_spacing;
1245           break;
1246
1247         default:
1248           g_return_if_reached ();
1249           break;
1250         }
1251     }
1252   else
1253     {
1254       switch (priv->value_pos)
1255         {
1256         case GTK_POS_LEFT:
1257           *x = range_rect.x - logical_rect.width - value_spacing;
1258           *y = slider_start + (slider_end - slider_start - logical_rect.height) / 2;
1259           *y = CLAMP (*y, 0, allocation.height - logical_rect.height);
1260           break;
1261
1262         case GTK_POS_RIGHT:
1263           *x = range_rect.x + range_rect.width + value_spacing;
1264           *y = slider_start + (slider_end - slider_start - logical_rect.height) / 2;
1265           *y = CLAMP (*y, 0, allocation.height - logical_rect.height);
1266           break;
1267
1268         case GTK_POS_TOP:
1269           *x = range_rect.x + (range_rect.width - logical_rect.width) / 2;
1270           *y = range_rect.y - logical_rect.height - value_spacing;
1271           break;
1272
1273         case GTK_POS_BOTTOM:
1274           *x = range_rect.x + (range_rect.width - logical_rect.width) / 2;
1275           *y = range_rect.y + range_rect.height + value_spacing;
1276           break;
1277
1278         default:
1279           g_return_if_reached ();
1280         }
1281     }
1282
1283   *x += allocation.x;
1284   *y += allocation.y;
1285 }
1286
1287 /**
1288  * _gtk_scale_format_value:
1289  * @scale: a #GtkScale
1290  * @value: adjustment value
1291  * 
1292  * Emits #GtkScale::format-value signal to format the value, 
1293  * if no user signal handlers, falls back to a default format.
1294  * 
1295  * Return value: formatted value
1296  */
1297 gchar*
1298 _gtk_scale_format_value (GtkScale *scale,
1299                          gdouble   value)
1300 {
1301   GtkScalePrivate *priv = scale->priv;
1302   gchar *fmt = NULL;
1303
1304   g_signal_emit (scale,
1305                  signals[FORMAT_VALUE],
1306                  0,
1307                  value,
1308                  &fmt);
1309
1310   if (fmt)
1311     return fmt;
1312   else
1313     /* insert a LRM, to prevent -20 to come out as 20- in RTL locales */
1314     return g_strdup_printf ("\342\200\216%0.*f", priv->digits, value);
1315 }
1316
1317 static void
1318 gtk_scale_finalize (GObject *object)
1319 {
1320   GtkScale *scale = GTK_SCALE (object);
1321
1322   _gtk_scale_clear_layout (scale);
1323   gtk_scale_clear_marks (scale);
1324
1325   G_OBJECT_CLASS (gtk_scale_parent_class)->finalize (object);
1326 }
1327
1328 /**
1329  * gtk_scale_get_layout:
1330  * @scale: A #GtkScale
1331  *
1332  * Gets the #PangoLayout used to display the scale. The returned
1333  * object is owned by the scale so does not need to be freed by
1334  * the caller.
1335  *
1336  * Return value: (transfer none): the #PangoLayout for this scale,
1337  *     or %NULL if the #GtkScale:draw-value property is %FALSE.
1338  *
1339  * Since: 2.4
1340  */
1341 PangoLayout *
1342 gtk_scale_get_layout (GtkScale *scale)
1343 {
1344   GtkScalePrivate *priv;
1345   gchar *txt;
1346
1347   g_return_val_if_fail (GTK_IS_SCALE (scale), NULL);
1348
1349   priv = scale->priv;
1350
1351   if (!priv->layout)
1352     {
1353       if (priv->draw_value)
1354         priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
1355     }
1356
1357   if (priv->draw_value)
1358     {
1359       txt = _gtk_scale_format_value (scale,
1360                                      gtk_adjustment_get_value (gtk_range_get_adjustment (GTK_RANGE (scale))));
1361       pango_layout_set_text (priv->layout, txt, -1);
1362       g_free (txt);
1363     }
1364
1365   return priv->layout;
1366 }
1367
1368 /**
1369  * gtk_scale_get_layout_offsets:
1370  * @scale: a #GtkScale
1371  * @x: (allow-none): location to store X offset of layout, or %NULL
1372  * @y: (allow-none): location to store Y offset of layout, or %NULL
1373  *
1374  * Obtains the coordinates where the scale will draw the 
1375  * #PangoLayout representing the text in the scale. Remember
1376  * when using the #PangoLayout function you need to convert to
1377  * and from pixels using PANGO_PIXELS() or #PANGO_SCALE. 
1378  *
1379  * If the #GtkScale:draw-value property is %FALSE, the return 
1380  * values are undefined.
1381  *
1382  * Since: 2.4
1383  */
1384 void 
1385 gtk_scale_get_layout_offsets (GtkScale *scale,
1386                               gint     *x,
1387                               gint     *y)
1388 {
1389   gint local_x = 0; 
1390   gint local_y = 0;
1391
1392   g_return_if_fail (GTK_IS_SCALE (scale));
1393
1394   if (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets)
1395     (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets) (scale, &local_x, &local_y);
1396
1397   if (x)
1398     *x = local_x;
1399   
1400   if (y)
1401     *y = local_y;
1402 }
1403
1404 void
1405 _gtk_scale_clear_layout (GtkScale *scale)
1406 {
1407   GtkScalePrivate *priv = scale->priv;
1408
1409   g_return_if_fail (GTK_IS_SCALE (scale));
1410
1411   if (priv->layout)
1412     {
1413       g_object_unref (priv->layout);
1414       priv->layout = NULL;
1415     }
1416 }
1417
1418 static void
1419 gtk_scale_mark_free (GtkScaleMark *mark)
1420 {
1421   g_free (mark->markup);
1422   g_free (mark);
1423 }
1424
1425 /**
1426  * gtk_scale_clear_marks:
1427  * @scale: a #GtkScale
1428  * 
1429  * Removes any marks that have been added with gtk_scale_add_mark().
1430  *
1431  * Since: 2.16
1432  */
1433 void
1434 gtk_scale_clear_marks (GtkScale *scale)
1435 {
1436   GtkScalePrivate *priv;
1437
1438   g_return_if_fail (GTK_IS_SCALE (scale));
1439
1440   priv = scale->priv;
1441
1442   g_slist_foreach (priv->marks, (GFunc)gtk_scale_mark_free, NULL);
1443   g_slist_free (priv->marks);
1444   priv->marks = NULL;
1445
1446   _gtk_range_set_stop_values (GTK_RANGE (scale), NULL, 0);
1447
1448   gtk_widget_queue_resize (GTK_WIDGET (scale));
1449 }
1450
1451 static gint
1452 compare_marks (gpointer a, gpointer b)
1453 {
1454   GtkScaleMark *ma, *mb;
1455
1456   ma = a; mb = b;
1457
1458   return (gint) (ma->value - mb->value);
1459 }
1460
1461 /**
1462  * gtk_scale_add_mark:
1463  * @scale: a #GtkScale
1464  * @value: the value at which the mark is placed, must be between 
1465  *   the lower and upper limits of the scales' adjustment
1466  * @position: where to draw the mark. For a horizontal scale, #GTK_POS_TOP
1467  *   is drawn above the scale, anything else below. For a vertical scale,
1468  *   #GTK_POS_LEFT is drawn to the left of the scale, anything else to the
1469  *   right.
1470  * @markup: (allow-none): Text to be shown at the mark, using <link linkend="PangoMarkupFormat">Pango markup</link>, or %NULL
1471  *
1472  *
1473  * Adds a mark at @value. 
1474  *
1475  * A mark is indicated visually by drawing a tick mark next to the scale, 
1476  * and GTK+ makes it easy for the user to position the scale exactly at the 
1477  * marks value.
1478  *
1479  * If @markup is not %NULL, text is shown next to the tick mark. 
1480  *
1481  * To remove marks from a scale, use gtk_scale_clear_marks().
1482  *
1483  * Since: 2.16
1484  */
1485 void
1486 gtk_scale_add_mark (GtkScale        *scale,
1487                     gdouble          value,
1488                     GtkPositionType  position,
1489                     const gchar     *markup)
1490 {
1491   GtkScalePrivate *priv;
1492   GtkScaleMark *mark;
1493   GSList *m;
1494   gdouble *values;
1495   gint n, i;
1496
1497   g_return_if_fail (GTK_IS_SCALE (scale));
1498
1499   priv = scale->priv;
1500
1501   mark = g_new (GtkScaleMark, 1);
1502   mark->value = value;
1503   mark->markup = g_strdup (markup);
1504   mark->position = position;
1505  
1506   priv->marks = g_slist_insert_sorted (priv->marks, mark,
1507                                        (GCompareFunc) compare_marks);
1508
1509   n = g_slist_length (priv->marks);
1510   values = g_new (gdouble, n);
1511   for (m = priv->marks, i = 0; m; m = m->next, i++)
1512     {
1513       mark = m->data;
1514       values[i] = mark->value;
1515     }
1516   
1517   _gtk_range_set_stop_values (GTK_RANGE (scale), values, n);
1518
1519   g_free (values);
1520
1521   gtk_widget_queue_resize (GTK_WIDGET (scale));
1522 }
1523
1524 static GtkBuildableIface *parent_buildable_iface;
1525
1526 static void
1527 gtk_scale_buildable_interface_init (GtkBuildableIface *iface)
1528 {
1529   parent_buildable_iface = g_type_interface_peek_parent (iface);
1530   iface->custom_tag_start = gtk_scale_buildable_custom_tag_start;
1531   iface->custom_finished = gtk_scale_buildable_custom_finished;
1532 }
1533
1534 typedef struct
1535 {
1536   GtkScale *scale;
1537   GtkBuilder *builder;
1538   GSList *marks;
1539 } MarksSubparserData;
1540
1541 typedef struct
1542 {
1543   gdouble value;
1544   GtkPositionType position;
1545   GString *markup;
1546   gchar *context;
1547   gboolean translatable;
1548 } MarkData;
1549
1550 static void
1551 mark_data_free (MarkData *data)
1552 {
1553   g_string_free (data->markup, TRUE);
1554   g_free (data->context);
1555   g_slice_free (MarkData, data);
1556 }
1557
1558 static void
1559 marks_start_element (GMarkupParseContext *context,
1560                      const gchar         *element_name,
1561                      const gchar        **names,
1562                      const gchar        **values,
1563                      gpointer             user_data,
1564                      GError             **error)
1565 {
1566   MarksSubparserData *parser_data = (MarksSubparserData*)user_data;
1567   guint i;
1568   gint line_number, char_number;
1569
1570   if (strcmp (element_name, "marks") == 0)
1571    ;
1572   else if (strcmp (element_name, "mark") == 0)
1573     {
1574       gdouble value = 0;
1575       gboolean has_value = FALSE;
1576       GtkPositionType position = GTK_POS_BOTTOM;
1577       const gchar *msg_context = NULL;
1578       gboolean translatable = FALSE;
1579       MarkData *mark;
1580
1581       for (i = 0; names[i]; i++)
1582         {
1583           if (strcmp (names[i], "translatable") == 0)
1584             {
1585               if (!_gtk_builder_boolean_from_string (values[i], &translatable, error))
1586                 return;
1587             }
1588           else if (strcmp (names[i], "comments") == 0)
1589             {
1590               /* do nothing, comments are for translators */
1591             }
1592           else if (strcmp (names[i], "context") == 0)
1593             msg_context = values[i];
1594           else if (strcmp (names[i], "value") == 0)
1595             {
1596               GValue gvalue = { 0, };
1597
1598               if (!gtk_builder_value_from_string_type (parser_data->builder, G_TYPE_DOUBLE, values[i], &gvalue, error))
1599                 return;
1600
1601               value = g_value_get_double (&gvalue);
1602               has_value = TRUE;
1603             }
1604           else if (strcmp (names[i], "position") == 0)
1605             {
1606               GValue gvalue = { 0, };
1607
1608               if (!gtk_builder_value_from_string_type (parser_data->builder, GTK_TYPE_POSITION_TYPE, values[i], &gvalue, error))
1609                 return;
1610
1611               position = g_value_get_enum (&gvalue);
1612             }
1613           else
1614             {
1615               g_markup_parse_context_get_position (context,
1616                                                    &line_number,
1617                                                    &char_number);
1618               g_set_error (error,
1619                            GTK_BUILDER_ERROR,
1620                            GTK_BUILDER_ERROR_INVALID_ATTRIBUTE,
1621                            "%s:%d:%d '%s' is not a valid attribute of <%s>",
1622                            "<input>",
1623                            line_number, char_number, names[i], "mark");
1624               return;
1625             }
1626         }
1627
1628       if (!has_value)
1629         {
1630           g_markup_parse_context_get_position (context,
1631                                                &line_number,
1632                                                &char_number);
1633           g_set_error (error,
1634                        GTK_BUILDER_ERROR,
1635                        GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
1636                        "%s:%d:%d <%s> requires attribute \"%s\"",
1637                        "<input>",
1638                        line_number, char_number, "mark",
1639                        "value");
1640           return;
1641         }
1642
1643       mark = g_slice_new (MarkData);
1644       mark->value = value;
1645       mark->position = position;
1646       mark->markup = g_string_new ("");
1647       mark->context = g_strdup (msg_context);
1648       mark->translatable = translatable;
1649
1650       parser_data->marks = g_slist_prepend (parser_data->marks, mark);
1651     }
1652   else
1653     {
1654       g_markup_parse_context_get_position (context,
1655                                            &line_number,
1656                                            &char_number);
1657       g_set_error (error,
1658                    GTK_BUILDER_ERROR,
1659                    GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
1660                    "%s:%d:%d unsupported tag for GtkScale: \"%s\"",
1661                    "<input>",
1662                    line_number, char_number, element_name);
1663       return;
1664     }
1665 }
1666
1667 static void
1668 marks_text (GMarkupParseContext  *context,
1669             const gchar          *text,
1670             gsize                 text_len,
1671             gpointer              user_data,
1672             GError              **error)
1673 {
1674   MarksSubparserData *data = (MarksSubparserData*)user_data;
1675
1676   if (strcmp (g_markup_parse_context_get_element (context), "mark") == 0)
1677     {
1678       MarkData *mark = data->marks->data;
1679
1680       g_string_append_len (mark->markup, text, text_len);
1681     }
1682 }
1683
1684 static const GMarkupParser marks_parser =
1685   {
1686     marks_start_element,
1687     NULL,
1688     marks_text,
1689   };
1690
1691
1692 static gboolean
1693 gtk_scale_buildable_custom_tag_start (GtkBuildable  *buildable,
1694                                       GtkBuilder    *builder,
1695                                       GObject       *child,
1696                                       const gchar   *tagname,
1697                                       GMarkupParser *parser,
1698                                       gpointer      *data)
1699 {
1700   MarksSubparserData *parser_data;
1701
1702   if (child)
1703     return FALSE;
1704
1705   if (strcmp (tagname, "marks") == 0)
1706     {
1707       parser_data = g_slice_new0 (MarksSubparserData);
1708       parser_data->scale = GTK_SCALE (buildable);
1709       parser_data->marks = NULL;
1710
1711       *parser = marks_parser;
1712       *data = parser_data;
1713       return TRUE;
1714     }
1715
1716   return parent_buildable_iface->custom_tag_start (buildable, builder, child,
1717                                                    tagname, parser, data);
1718 }
1719
1720 static void
1721 gtk_scale_buildable_custom_finished (GtkBuildable *buildable,
1722                                      GtkBuilder   *builder,
1723                                      GObject      *child,
1724                                      const gchar  *tagname,
1725                                      gpointer      user_data)
1726 {
1727   GtkScale *scale = GTK_SCALE (buildable);
1728   MarksSubparserData *marks_data;
1729
1730   if (strcmp (tagname, "marks") == 0)
1731     {
1732       GSList *m;
1733       gchar *markup;
1734
1735       marks_data = (MarksSubparserData *)user_data;
1736
1737       for (m = marks_data->marks; m; m = m->next)
1738         {
1739           MarkData *mdata = m->data;
1740
1741           if (mdata->translatable && mdata->markup->len)
1742             markup = _gtk_builder_parser_translate (gtk_builder_get_translation_domain (builder),
1743                                                     mdata->context,
1744                                                     mdata->markup->str);
1745           else
1746             markup = mdata->markup->str;
1747
1748           gtk_scale_add_mark (scale, mdata->value, mdata->position, markup);
1749
1750           mark_data_free (mdata);
1751         }
1752
1753       g_slist_free (marks_data->marks);
1754       g_slice_free (MarksSubparserData, marks_data);
1755     }
1756 }