]> Pileus Git - ~andy/gtk/blob - gtk/gtkscale.c
GtkComboBox: Add the format-entry-text signal.
[~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 "gtkscaleprivate.h"
34 #include "gtkiconfactory.h"
35 #include "gtkicontheme.h"
36 #include "gtkmarshalers.h"
37 #include "gtkbindings.h"
38 #include "gtkorientable.h"
39 #include "gtktypebuiltins.h"
40 #include "gtkprivate.h"
41 #include "gtkintl.h"
42 #include "gtkbuildable.h"
43 #include "gtkbuilderprivate.h"
44 #include "gtkmainprivate.h"
45
46 #include "a11y/gtkscaleaccessible.h"
47
48
49 /**
50  * SECTION:gtkscale
51  * @Short_description: A slider widget for selecting a value from a range
52  * @Title: GtkScale
53  *
54  * A GtkScale is a slider control used to select a numeric value. 
55  * To use it, you'll probably want to investigate the methods on
56  * its base class, #GtkRange, in addition to the methods for GtkScale itself.
57  * To set the value of a scale, you would normally use gtk_range_set_value().
58  * To detect changes to the value, you would normally use the
59  * #GtkRange::value-changed signal.
60  *
61  * Note that using the same upper and lower bounds for the #GtkScale (through
62  * the #GtkRange methods) will hide the slider itself. This is useful for
63  * applications that want to show an undeterminate value on the scale, without
64  * changing the layout of the application (such as movie or music players).
65  *
66  * <refsect2 id="GtkScale-BUILDER-UI"><title>GtkScale as GtkBuildable</title>
67  * GtkScale supports a custom &lt;marks&gt; element, which
68  * can contain multiple &lt;mark&gt; elements. The "value" and "position"
69  * attributes have the same meaning as gtk_scale_add_mark() parameters of the
70  * same name. If the element is not empty, its content is taken as the markup
71  * to show at the mark. It can be translated with the usual "translatable and
72  * "context" attributes.
73  * </refsect2>
74  */
75
76
77 #define MAX_DIGITS      (64)    /* don't change this,
78                                  * a) you don't need to and
79                                  * b) you might cause buffer owerflows in
80                                  *    unrelated code portions otherwise
81                                  */
82
83 typedef struct _GtkScaleMark GtkScaleMark;
84
85 struct _GtkScalePrivate
86 {
87   PangoLayout  *layout;
88
89   GSList       *marks;
90
91   gint          digits;
92
93   guint         draw_value : 1;
94   guint         value_pos  : 2;
95 };
96
97 struct _GtkScaleMark
98 {
99   gdouble          value;
100   gchar           *markup;
101   GtkPositionType  position; /* always GTK_POS_TOP or GTK_POS_BOTTOM */
102 };
103
104 enum {
105   PROP_0,
106   PROP_DIGITS,
107   PROP_DRAW_VALUE,
108   PROP_VALUE_POS
109 };
110
111 enum {
112   FORMAT_VALUE,
113   LAST_SIGNAL
114 };
115
116 static guint signals[LAST_SIGNAL];
117
118 static void     gtk_scale_set_property            (GObject        *object,
119                                                    guint           prop_id,
120                                                    const GValue   *value,
121                                                    GParamSpec     *pspec);
122 static void     gtk_scale_get_property            (GObject        *object,
123                                                    guint           prop_id,
124                                                    GValue         *value,
125                                                    GParamSpec     *pspec);
126 static void     gtk_scale_get_preferred_width     (GtkWidget      *widget,
127                                                    gint           *minimum,
128                                                    gint           *natural);
129 static void     gtk_scale_get_preferred_height    (GtkWidget      *widget,
130                                                    gint           *minimum,
131                                                    gint           *natural);
132 static void     gtk_scale_style_updated           (GtkWidget      *widget);
133 static void     gtk_scale_get_range_border        (GtkRange       *range,
134                                                    GtkBorder      *border);
135 static void     gtk_scale_get_mark_label_size     (GtkScale        *scale,
136                                                    GtkPositionType  position,
137                                                    gint            *count1,
138                                                    gint            *width1,
139                                                    gint            *height1,
140                                                    gint            *count2,
141                                                    gint            *width2,
142                                                    gint            *height2);
143 static void     gtk_scale_finalize                (GObject        *object);
144 static void     gtk_scale_screen_changed          (GtkWidget      *widget,
145                                                    GdkScreen      *old_screen);
146 static gboolean gtk_scale_draw                    (GtkWidget      *widget,
147                                                    cairo_t        *cr);
148 static void     gtk_scale_real_get_layout_offsets (GtkScale       *scale,
149                                                    gint           *x,
150                                                    gint           *y);
151 static void     gtk_scale_buildable_interface_init   (GtkBuildableIface *iface);
152 static gboolean gtk_scale_buildable_custom_tag_start (GtkBuildable  *buildable,
153                                                       GtkBuilder    *builder,
154                                                       GObject       *child,
155                                                       const gchar   *tagname,
156                                                       GMarkupParser *parser,
157                                                       gpointer      *data);
158 static void     gtk_scale_buildable_custom_finished  (GtkBuildable  *buildable,
159                                                       GtkBuilder    *builder,
160                                                       GObject       *child,
161                                                       const gchar   *tagname,
162                                                       gpointer       user_data);
163
164
165 G_DEFINE_TYPE_WITH_CODE (GtkScale, gtk_scale, GTK_TYPE_RANGE,
166                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
167                                                 gtk_scale_buildable_interface_init))
168
169
170 #define add_slider_binding(binding_set, keyval, mask, scroll)              \
171   gtk_binding_entry_add_signal (binding_set, keyval, mask,                 \
172                                 I_("move-slider"), 1, \
173                                 GTK_TYPE_SCROLL_TYPE, scroll)
174
175 static void
176 gtk_scale_class_init (GtkScaleClass *class)
177 {
178   GObjectClass   *gobject_class;
179   GtkWidgetClass *widget_class;
180   GtkRangeClass  *range_class;
181   GtkBindingSet  *binding_set;
182   
183   gobject_class = G_OBJECT_CLASS (class);
184   range_class = (GtkRangeClass*) class;
185   widget_class = (GtkWidgetClass*) class;
186   
187   gobject_class->set_property = gtk_scale_set_property;
188   gobject_class->get_property = gtk_scale_get_property;
189   gobject_class->finalize = gtk_scale_finalize;
190
191   widget_class->style_updated = gtk_scale_style_updated;
192   widget_class->screen_changed = gtk_scale_screen_changed;
193   widget_class->draw = gtk_scale_draw;
194   widget_class->get_preferred_width = gtk_scale_get_preferred_width;
195   widget_class->get_preferred_height = gtk_scale_get_preferred_height;
196
197   range_class->slider_detail = "Xscale";
198   range_class->get_range_border = gtk_scale_get_range_border;
199
200   class->get_layout_offsets = gtk_scale_real_get_layout_offsets;
201
202   /**
203    * GtkScale::format-value:
204    * @scale: the object which received the signal
205    * @value: the value to format
206    *
207    * Signal which allows you to change how the scale value is displayed.
208    * Connect a signal handler which returns an allocated string representing 
209    * @value. That string will then be used to display the scale's value.
210    *
211    * Here's an example signal handler which displays a value 1.0 as
212    * with "--&gt;1.0&lt;--".
213    * |[
214    * static gchar*
215    * format_value_callback (GtkScale *scale,
216    *                        gdouble   value)
217    * {
218    *   return g_strdup_printf ("--&gt;&percnt;0.*g&lt;--",
219    *                           gtk_scale_get_digits (scale), value);
220    *  }
221    * ]|
222    *
223    * Return value: allocated string representing @value
224    */
225   signals[FORMAT_VALUE] =
226     g_signal_new (I_("format-value"),
227                   G_TYPE_FROM_CLASS (gobject_class),
228                   G_SIGNAL_RUN_LAST,
229                   G_STRUCT_OFFSET (GtkScaleClass, format_value),
230                   _gtk_single_string_accumulator, NULL,
231                   _gtk_marshal_STRING__DOUBLE,
232                   G_TYPE_STRING, 1,
233                   G_TYPE_DOUBLE);
234
235   g_object_class_install_property (gobject_class,
236                                    PROP_DIGITS,
237                                    g_param_spec_int ("digits",
238                                                      P_("Digits"),
239                                                      P_("The number of decimal places that are displayed in the value"),
240                                                      -1,
241                                                      MAX_DIGITS,
242                                                      1,
243                                                      GTK_PARAM_READWRITE));
244   
245   g_object_class_install_property (gobject_class,
246                                    PROP_DRAW_VALUE,
247                                    g_param_spec_boolean ("draw-value",
248                                                          P_("Draw Value"),
249                                                          P_("Whether the current value is displayed as a string next to the slider"),
250                                                          TRUE,
251                                                          GTK_PARAM_READWRITE));
252   
253   g_object_class_install_property (gobject_class,
254                                    PROP_VALUE_POS,
255                                    g_param_spec_enum ("value-pos",
256                                                       P_("Value Position"),
257                                                       P_("The position in which the current value is displayed"),
258                                                       GTK_TYPE_POSITION_TYPE,
259                                                       GTK_POS_TOP,
260                                                       GTK_PARAM_READWRITE));
261
262   gtk_widget_class_install_style_property (widget_class,
263                                            g_param_spec_int ("slider-length",
264                                                              P_("Slider Length"),
265                                                              P_("Length of scale's slider"),
266                                                              0,
267                                                              G_MAXINT,
268                                                              31,
269                                                              GTK_PARAM_READABLE));
270
271   gtk_widget_class_install_style_property (widget_class,
272                                            g_param_spec_int ("value-spacing",
273                                                              P_("Value spacing"),
274                                                              P_("Space between value text and the slider/trough area"),
275                                                              0,
276                                                              G_MAXINT,
277                                                              2,
278                                                              GTK_PARAM_READABLE));
279   
280   /* All bindings (even arrow keys) are on both h/v scale, because
281    * blind users etc. don't care about scale orientation.
282    */
283   
284   binding_set = gtk_binding_set_by_class (class);
285
286   add_slider_binding (binding_set, GDK_KEY_Left, 0,
287                       GTK_SCROLL_STEP_LEFT);
288
289   add_slider_binding (binding_set, GDK_KEY_Left, GDK_CONTROL_MASK,
290                       GTK_SCROLL_PAGE_LEFT);
291
292   add_slider_binding (binding_set, GDK_KEY_KP_Left, 0,
293                       GTK_SCROLL_STEP_LEFT);
294
295   add_slider_binding (binding_set, GDK_KEY_KP_Left, GDK_CONTROL_MASK,
296                       GTK_SCROLL_PAGE_LEFT);
297
298   add_slider_binding (binding_set, GDK_KEY_Right, 0,
299                       GTK_SCROLL_STEP_RIGHT);
300
301   add_slider_binding (binding_set, GDK_KEY_Right, GDK_CONTROL_MASK,
302                       GTK_SCROLL_PAGE_RIGHT);
303
304   add_slider_binding (binding_set, GDK_KEY_KP_Right, 0,
305                       GTK_SCROLL_STEP_RIGHT);
306
307   add_slider_binding (binding_set, GDK_KEY_KP_Right, GDK_CONTROL_MASK,
308                       GTK_SCROLL_PAGE_RIGHT);
309
310   add_slider_binding (binding_set, GDK_KEY_Up, 0,
311                       GTK_SCROLL_STEP_UP);
312
313   add_slider_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK,
314                       GTK_SCROLL_PAGE_UP);
315
316   add_slider_binding (binding_set, GDK_KEY_KP_Up, 0,
317                       GTK_SCROLL_STEP_UP);
318
319   add_slider_binding (binding_set, GDK_KEY_KP_Up, GDK_CONTROL_MASK,
320                       GTK_SCROLL_PAGE_UP);
321
322   add_slider_binding (binding_set, GDK_KEY_Down, 0,
323                       GTK_SCROLL_STEP_DOWN);
324
325   add_slider_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK,
326                       GTK_SCROLL_PAGE_DOWN);
327
328   add_slider_binding (binding_set, GDK_KEY_KP_Down, 0,
329                       GTK_SCROLL_STEP_DOWN);
330
331   add_slider_binding (binding_set, GDK_KEY_KP_Down, GDK_CONTROL_MASK,
332                       GTK_SCROLL_PAGE_DOWN);
333    
334   add_slider_binding (binding_set, GDK_KEY_Page_Up, GDK_CONTROL_MASK,
335                       GTK_SCROLL_PAGE_LEFT);
336
337   add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, GDK_CONTROL_MASK,
338                       GTK_SCROLL_PAGE_LEFT);  
339
340   add_slider_binding (binding_set, GDK_KEY_Page_Up, 0,
341                       GTK_SCROLL_PAGE_UP);
342
343   add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, 0,
344                       GTK_SCROLL_PAGE_UP);
345   
346   add_slider_binding (binding_set, GDK_KEY_Page_Down, GDK_CONTROL_MASK,
347                       GTK_SCROLL_PAGE_RIGHT);
348
349   add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, GDK_CONTROL_MASK,
350                       GTK_SCROLL_PAGE_RIGHT);
351
352   add_slider_binding (binding_set, GDK_KEY_Page_Down, 0,
353                       GTK_SCROLL_PAGE_DOWN);
354
355   add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, 0,
356                       GTK_SCROLL_PAGE_DOWN);
357
358   /* Logical bindings (vs. visual bindings above) */
359
360   add_slider_binding (binding_set, GDK_KEY_plus, 0,
361                       GTK_SCROLL_STEP_FORWARD);  
362
363   add_slider_binding (binding_set, GDK_KEY_minus, 0,
364                       GTK_SCROLL_STEP_BACKWARD);  
365
366   add_slider_binding (binding_set, GDK_KEY_plus, GDK_CONTROL_MASK,
367                       GTK_SCROLL_PAGE_FORWARD);  
368
369   add_slider_binding (binding_set, GDK_KEY_minus, GDK_CONTROL_MASK,
370                       GTK_SCROLL_PAGE_BACKWARD);
371
372
373   add_slider_binding (binding_set, GDK_KEY_KP_Add, 0,
374                       GTK_SCROLL_STEP_FORWARD);  
375
376   add_slider_binding (binding_set, GDK_KEY_KP_Subtract, 0,
377                       GTK_SCROLL_STEP_BACKWARD);  
378
379   add_slider_binding (binding_set, GDK_KEY_KP_Add, GDK_CONTROL_MASK,
380                       GTK_SCROLL_PAGE_FORWARD);  
381
382   add_slider_binding (binding_set, GDK_KEY_KP_Subtract, GDK_CONTROL_MASK,
383                       GTK_SCROLL_PAGE_BACKWARD);
384   
385   
386   add_slider_binding (binding_set, GDK_KEY_Home, 0,
387                       GTK_SCROLL_START);
388
389   add_slider_binding (binding_set, GDK_KEY_KP_Home, 0,
390                       GTK_SCROLL_START);
391
392   add_slider_binding (binding_set, GDK_KEY_End, 0,
393                       GTK_SCROLL_END);
394
395   add_slider_binding (binding_set, GDK_KEY_KP_End, 0,
396                       GTK_SCROLL_END);
397
398   g_type_class_add_private (gobject_class, sizeof (GtkScalePrivate));
399
400   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SCALE_ACCESSIBLE);
401 }
402
403 static void
404 gtk_scale_orientation_notify (GtkRange         *range,
405                               const GParamSpec *pspec)
406 {
407   GtkOrientation orientation;
408
409   orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (range));
410   gtk_range_set_flippable (range,
411                            orientation == GTK_ORIENTATION_HORIZONTAL);
412 }
413
414 static void
415 gtk_scale_init (GtkScale *scale)
416 {
417   GtkScalePrivate *priv;
418   GtkRange *range = GTK_RANGE (scale);
419   GtkStyleContext *context;
420
421   scale->priv = G_TYPE_INSTANCE_GET_PRIVATE (scale,
422                                              GTK_TYPE_SCALE,
423                                              GtkScalePrivate);
424   priv = scale->priv;
425
426   gtk_widget_set_can_focus (GTK_WIDGET (scale), TRUE);
427
428   gtk_range_set_slider_size_fixed (range, TRUE);
429
430   priv->draw_value = TRUE;
431   priv->value_pos = GTK_POS_TOP;
432   priv->digits = 1;
433   gtk_range_set_round_digits (range, priv->digits);
434
435   gtk_scale_orientation_notify (range, NULL);
436   g_signal_connect (scale, "notify::orientation",
437                     G_CALLBACK (gtk_scale_orientation_notify),
438                     NULL);
439
440   context = gtk_widget_get_style_context (GTK_WIDGET (scale));
441   gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE);
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: (allow-none): the #GtkAdjustment which sets the range
501  *              of the scale, or %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       gtk_scale_get_mark_label_size (scale, GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
783
784       if (gtk_orientable_get_orientation (GTK_ORIENTABLE (scale)) == GTK_ORIENTATION_HORIZONTAL)
785         {
786           if (n1 > 0)
787             border->top += h1 + value_spacing + slider_width / 2;
788           if (n2 > 0)
789             border->bottom += h2 + value_spacing + slider_width / 2;
790         }
791       else
792         {
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_updated (GtkWidget *widget)
915 {
916   gint slider_length;
917   GtkRange *range;
918
919   range = GTK_RANGE (widget);
920   
921   gtk_widget_style_get (widget,
922                         "slider-length", &slider_length,
923                         NULL);
924
925   gtk_range_set_min_slider_size (range, slider_length);
926
927   _gtk_scale_clear_layout (GTK_SCALE (widget));
928
929   GTK_WIDGET_CLASS (gtk_scale_parent_class)->style_updated (widget);
930 }
931
932 static void
933 gtk_scale_screen_changed (GtkWidget *widget,
934                           GdkScreen *old_screen)
935 {
936   _gtk_scale_clear_layout (GTK_SCALE (widget));
937 }
938
939 static void
940 gtk_scale_get_preferred_width (GtkWidget *widget,
941                                gint      *minimum,
942                                gint      *natural)
943 {
944   GTK_WIDGET_CLASS (gtk_scale_parent_class)->get_preferred_width (widget, minimum, natural);
945   
946   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
947     {
948       gint n1, w1, h1, n2, w2, h2;
949       gint slider_length;
950       gint w;
951
952       gtk_widget_style_get (widget, "slider-length", &slider_length, NULL);
953
954       gtk_scale_get_mark_label_size (GTK_SCALE (widget), GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
955
956       w1 = (n1 - 1) * w1 + MAX (w1, slider_length);
957       w2 = (n2 - 1) * w2 + MAX (w2, slider_length);
958       w = MAX (w1, w2);
959
960       *minimum = MAX (*minimum, w);
961       *natural = MAX (*natural, w);
962     }
963 }
964
965 static void
966 gtk_scale_get_preferred_height (GtkWidget *widget,
967                                 gint      *minimum,
968                                 gint      *natural)
969 {
970   GTK_WIDGET_CLASS (gtk_scale_parent_class)->get_preferred_height (widget, minimum, natural);
971
972
973   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_VERTICAL)
974     {
975       gint n1, w1, h1, n2, w2, h2;
976       gint slider_length;
977       gint h;
978
979       gtk_widget_style_get (widget, "slider-length", &slider_length, NULL);
980
981       gtk_scale_get_mark_label_size (GTK_SCALE (widget), GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
982       h1 = (n1 - 1) * h1 + MAX (h1, slider_length);
983       h2 = (n2 - 1) * h1 + MAX (h2, slider_length);
984       h = MAX (h1, h2);
985
986       *minimum = MAX (*minimum, h);
987       *natural = MAX (*natural, h);
988     }
989 }
990
991 static gint
992 find_next_pos (GtkWidget      *widget,
993                GSList          *list,
994                gint            *marks,
995                GtkPositionType  pos,
996                gint             match)
997 {
998   GtkAllocation allocation;
999   GSList *m;
1000   gint i;
1001
1002   for (m = list->next, i = 1; m; m = m->next, i++)
1003     {
1004       GtkScaleMark *mark = m->data;
1005
1006       if (match == (mark->position == pos))
1007         return marks[i];
1008     }
1009
1010   gtk_widget_get_allocation (widget, &allocation);
1011   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
1012     return allocation.width;
1013   else
1014     return allocation.height;
1015 }
1016
1017 static gboolean
1018 gtk_scale_draw (GtkWidget *widget,
1019                 cairo_t   *cr)
1020 {
1021   GtkScale *scale = GTK_SCALE (widget);
1022   GtkScalePrivate *priv = scale->priv;
1023   GtkRange *range = GTK_RANGE (scale);
1024   GtkStateFlags state = 0;
1025   GtkStyleContext *context;
1026   gint *marks;
1027   gint focus_padding;
1028   gint slider_width;
1029   gint value_spacing;
1030   gint min_sep = 4;
1031
1032   context = gtk_widget_get_style_context (widget);
1033   gtk_widget_style_get (widget,
1034                         "focus-padding", &focus_padding,
1035                         "slider-width", &slider_width,
1036                         "value-spacing", &value_spacing,
1037                         NULL);
1038
1039   /* We need to chain up _first_ so the various geometry members of
1040    * GtkRange struct are updated.
1041    */
1042   GTK_WIDGET_CLASS (gtk_scale_parent_class)->draw (widget, cr);
1043
1044   if (!gtk_widget_is_sensitive (widget))
1045     state |= GTK_STATE_FLAG_INSENSITIVE;
1046
1047   if (priv->marks)
1048     {
1049       GtkOrientation orientation;
1050       GdkRectangle range_rect;
1051       gint i;
1052       gint x1, x2, x3, y1, y2, y3;
1053       PangoLayout *layout;
1054       PangoRectangle logical_rect;
1055       GSList *m;
1056       gint min_pos_before, min_pos_after;
1057       gint min_pos, max_pos;
1058
1059       orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (range));
1060       _gtk_range_get_stop_positions (range, &marks);
1061       layout = gtk_widget_create_pango_layout (widget, NULL);
1062       gtk_range_get_range_rect (range, &range_rect);
1063
1064       min_pos_before = min_pos_after = 0;
1065
1066       for (m = priv->marks, i = 0; m; m = m->next, i++)
1067         {
1068           GtkScaleMark *mark = m->data;
1069
1070           if (orientation == GTK_ORIENTATION_HORIZONTAL)
1071             {
1072               x1 = marks[i];
1073               if (mark->position == GTK_POS_TOP)
1074                 {
1075                   y1 = range_rect.y;
1076                   y2 = y1 - slider_width / 2;
1077                   min_pos = min_pos_before;
1078                   max_pos = find_next_pos (widget, m, marks + i, GTK_POS_TOP, 1) - min_sep;
1079                 }
1080               else
1081                 {
1082                   y1 = range_rect.y + range_rect.height;
1083                   y2 = y1 + slider_width / 2;
1084                   min_pos = min_pos_after;
1085                   max_pos = find_next_pos (widget, m, marks + i, GTK_POS_BOTTOM, 1) - min_sep;
1086                 }
1087
1088               gtk_style_context_save (context);
1089               gtk_style_context_add_class (context, GTK_STYLE_CLASS_MARK);
1090               gtk_style_context_set_state (context, state);
1091
1092               gtk_render_line (context, cr, x1, y1, x1, y2);
1093
1094               if (mark->markup)
1095                 {
1096                   pango_layout_set_markup (layout, mark->markup, -1);
1097                   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1098
1099                   x3 = x1 - logical_rect.width / 2;
1100                   if (x3 < min_pos)
1101                     x3 = min_pos;
1102                   if (x3 + logical_rect.width > max_pos)
1103                         x3 = max_pos - logical_rect.width;
1104                   if (x3 < 0)
1105                      x3 = 0;
1106                   if (mark->position == GTK_POS_TOP)
1107                     {
1108                       y3 = y2 - value_spacing - logical_rect.height;
1109                       min_pos_before = x3 + logical_rect.width + min_sep;
1110                     }
1111                   else
1112                     {
1113                       y3 = y2 + value_spacing;
1114                       min_pos_after = x3 + logical_rect.width + min_sep;
1115                     }
1116
1117                   gtk_render_layout (context, cr, x3, y3, layout);
1118                 }
1119
1120               gtk_style_context_restore (context);
1121             }
1122           else
1123             {
1124               if (mark->position == GTK_POS_TOP)
1125                 {
1126                   x1 = range_rect.x;
1127                   x2 = range_rect.x - slider_width / 2;
1128                   min_pos = min_pos_before;
1129                   max_pos = find_next_pos (widget, m, marks + i, GTK_POS_TOP, 1) - min_sep;
1130                 }
1131               else
1132                 {
1133                   x1 = range_rect.x + range_rect.width;
1134                   x2 = range_rect.x + range_rect.width + slider_width / 2;
1135                   min_pos = min_pos_after;
1136                   max_pos = find_next_pos (widget, m, marks + i, GTK_POS_BOTTOM, 1) - min_sep;
1137                 }
1138               y1 = marks[i];
1139
1140               gtk_style_context_save (context);
1141               gtk_style_context_add_class (context, GTK_STYLE_CLASS_MARK);
1142               gtk_style_context_set_state (context, state);
1143
1144               gtk_render_line (context, cr, x1, y1, x2, y1);
1145
1146               if (mark->markup)
1147                 {
1148                   pango_layout_set_markup (layout, mark->markup, -1);
1149                   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1150
1151                   y3 = y1 - logical_rect.height / 2;
1152                   if (y3 < min_pos)
1153                     y3 = min_pos;
1154                   if (y3 + logical_rect.height > max_pos)
1155                     y3 = max_pos - logical_rect.height;
1156                   if (y3 < 0)
1157                     y3 = 0;
1158                   if (mark->position == GTK_POS_TOP)
1159                     {
1160                       x3 = x2 - value_spacing - logical_rect.width;
1161                       min_pos_before = y3 + logical_rect.height + min_sep;
1162                     }
1163                   else
1164                     {
1165                       x3 = x2 + value_spacing;
1166                       min_pos_after = y3 + logical_rect.height + min_sep;
1167                     }
1168
1169                   gtk_render_layout (context, cr, x3, y3, layout);
1170                 }
1171
1172               gtk_style_context_restore (context);
1173             }
1174         }
1175
1176       g_object_unref (layout);
1177       g_free (marks);
1178     }
1179
1180   if (priv->draw_value)
1181     {
1182       GtkAllocation allocation;
1183
1184       PangoLayout *layout;
1185       gint x, y;
1186
1187       layout = gtk_scale_get_layout (scale);
1188       gtk_scale_get_layout_offsets (scale, &x, &y);
1189       gtk_widget_get_allocation (widget, &allocation);
1190
1191       gtk_render_layout (context, cr,
1192                          x - allocation.x,
1193                          y - allocation.y,
1194                          layout);
1195     }
1196
1197   return FALSE;
1198 }
1199
1200 static void
1201 gtk_scale_real_get_layout_offsets (GtkScale *scale,
1202                                    gint     *x,
1203                                    gint     *y)
1204 {
1205   GtkScalePrivate *priv = scale->priv;
1206   GtkAllocation allocation;
1207   GtkWidget *widget = GTK_WIDGET (scale);
1208   GtkRange *range = GTK_RANGE (widget);
1209   GdkRectangle range_rect;
1210   PangoLayout *layout = gtk_scale_get_layout (scale);
1211   PangoRectangle logical_rect;
1212   gint slider_start, slider_end;
1213   gint value_spacing;
1214
1215   if (!layout)
1216     {
1217       *x = 0;
1218       *y = 0;
1219
1220       return;
1221     }
1222
1223   gtk_widget_style_get (widget, "value-spacing", &value_spacing, NULL);
1224
1225   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1226
1227   gtk_widget_get_allocation (widget, &allocation);
1228   gtk_range_get_range_rect (range, &range_rect);
1229   gtk_range_get_slider_range (range,
1230                               &slider_start,
1231                               &slider_end);
1232
1233   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL)
1234     {
1235       switch (priv->value_pos)
1236         {
1237         case GTK_POS_LEFT:
1238           *x = range_rect.x - value_spacing - logical_rect.width;
1239           *y = range_rect.y + (range_rect.height - logical_rect.height) / 2;
1240           break;
1241
1242         case GTK_POS_RIGHT:
1243           *x = range_rect.x + range_rect.width + value_spacing;
1244           *y = range_rect.y + (range_rect.height - logical_rect.height) / 2;
1245           break;
1246
1247         case GTK_POS_TOP:
1248           *x = slider_start + (slider_end - slider_start - logical_rect.width) / 2;
1249           *x = CLAMP (*x, 0, allocation.width - logical_rect.width);
1250           *y = range_rect.y - logical_rect.height - value_spacing;
1251           break;
1252
1253         case GTK_POS_BOTTOM:
1254           *x = slider_start + (slider_end - slider_start - logical_rect.width) / 2;
1255           *x = CLAMP (*x, 0, allocation.width - logical_rect.width);
1256           *y = range_rect.y + range_rect.height + value_spacing;
1257           break;
1258
1259         default:
1260           g_return_if_reached ();
1261           break;
1262         }
1263     }
1264   else
1265     {
1266       switch (priv->value_pos)
1267         {
1268         case GTK_POS_LEFT:
1269           *x = range_rect.x - logical_rect.width - value_spacing;
1270           *y = slider_start + (slider_end - slider_start - logical_rect.height) / 2;
1271           *y = CLAMP (*y, 0, allocation.height - logical_rect.height);
1272           break;
1273
1274         case GTK_POS_RIGHT:
1275           *x = range_rect.x + range_rect.width + value_spacing;
1276           *y = slider_start + (slider_end - slider_start - logical_rect.height) / 2;
1277           *y = CLAMP (*y, 0, allocation.height - logical_rect.height);
1278           break;
1279
1280         case GTK_POS_TOP:
1281           *x = range_rect.x + (range_rect.width - logical_rect.width) / 2;
1282           *y = range_rect.y - logical_rect.height - value_spacing;
1283           break;
1284
1285         case GTK_POS_BOTTOM:
1286           *x = range_rect.x + (range_rect.width - logical_rect.width) / 2;
1287           *y = range_rect.y + range_rect.height + value_spacing;
1288           break;
1289
1290         default:
1291           g_return_if_reached ();
1292         }
1293     }
1294
1295   *x += allocation.x;
1296   *y += allocation.y;
1297 }
1298
1299 /**
1300  * _gtk_scale_format_value:
1301  * @scale: a #GtkScale
1302  * @value: adjustment value
1303  * 
1304  * Emits #GtkScale::format-value signal to format the value, 
1305  * if no user signal handlers, falls back to a default format.
1306  * 
1307  * Return value: formatted value
1308  */
1309 gchar*
1310 _gtk_scale_format_value (GtkScale *scale,
1311                          gdouble   value)
1312 {
1313   GtkScalePrivate *priv = scale->priv;
1314   gchar *fmt = NULL;
1315
1316   g_signal_emit (scale,
1317                  signals[FORMAT_VALUE],
1318                  0,
1319                  value,
1320                  &fmt);
1321
1322   if (fmt)
1323     return fmt;
1324   else
1325     return g_strdup_printf ("%0.*f", priv->digits, value);
1326 }
1327
1328 static void
1329 gtk_scale_finalize (GObject *object)
1330 {
1331   GtkScale *scale = GTK_SCALE (object);
1332
1333   _gtk_scale_clear_layout (scale);
1334   gtk_scale_clear_marks (scale);
1335
1336   G_OBJECT_CLASS (gtk_scale_parent_class)->finalize (object);
1337 }
1338
1339 /**
1340  * gtk_scale_get_layout:
1341  * @scale: A #GtkScale
1342  *
1343  * Gets the #PangoLayout used to display the scale. The returned
1344  * object is owned by the scale so does not need to be freed by
1345  * the caller.
1346  *
1347  * Return value: (transfer none): the #PangoLayout for this scale,
1348  *     or %NULL if the #GtkScale:draw-value property is %FALSE.
1349  *
1350  * Since: 2.4
1351  */
1352 PangoLayout *
1353 gtk_scale_get_layout (GtkScale *scale)
1354 {
1355   GtkScalePrivate *priv;
1356   gchar *txt;
1357
1358   g_return_val_if_fail (GTK_IS_SCALE (scale), NULL);
1359
1360   priv = scale->priv;
1361
1362   if (!priv->layout)
1363     {
1364       if (priv->draw_value)
1365         priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
1366     }
1367
1368   if (priv->draw_value)
1369     {
1370       txt = _gtk_scale_format_value (scale,
1371                                      gtk_adjustment_get_value (gtk_range_get_adjustment (GTK_RANGE (scale))));
1372       pango_layout_set_text (priv->layout, txt, -1);
1373       g_free (txt);
1374     }
1375
1376   return priv->layout;
1377 }
1378
1379 /**
1380  * gtk_scale_get_layout_offsets:
1381  * @scale: a #GtkScale
1382  * @x: (out) (allow-none): location to store X offset of layout, or %NULL
1383  * @y: (out) (allow-none): location to store Y offset of layout, or %NULL
1384  *
1385  * Obtains the coordinates where the scale will draw the 
1386  * #PangoLayout representing the text in the scale. Remember
1387  * when using the #PangoLayout function you need to convert to
1388  * and from pixels using PANGO_PIXELS() or #PANGO_SCALE. 
1389  *
1390  * If the #GtkScale:draw-value property is %FALSE, the return 
1391  * values are undefined.
1392  *
1393  * Since: 2.4
1394  */
1395 void 
1396 gtk_scale_get_layout_offsets (GtkScale *scale,
1397                               gint     *x,
1398                               gint     *y)
1399 {
1400   gint local_x = 0; 
1401   gint local_y = 0;
1402
1403   g_return_if_fail (GTK_IS_SCALE (scale));
1404
1405   if (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets)
1406     (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets) (scale, &local_x, &local_y);
1407
1408   if (x)
1409     *x = local_x;
1410   
1411   if (y)
1412     *y = local_y;
1413 }
1414
1415 void
1416 _gtk_scale_clear_layout (GtkScale *scale)
1417 {
1418   GtkScalePrivate *priv = scale->priv;
1419
1420   g_return_if_fail (GTK_IS_SCALE (scale));
1421
1422   if (priv->layout)
1423     {
1424       g_object_unref (priv->layout);
1425       priv->layout = NULL;
1426     }
1427 }
1428
1429 static void
1430 gtk_scale_mark_free (GtkScaleMark *mark)
1431 {
1432   g_free (mark->markup);
1433   g_free (mark);
1434 }
1435
1436 /**
1437  * gtk_scale_clear_marks:
1438  * @scale: a #GtkScale
1439  * 
1440  * Removes any marks that have been added with gtk_scale_add_mark().
1441  *
1442  * Since: 2.16
1443  */
1444 void
1445 gtk_scale_clear_marks (GtkScale *scale)
1446 {
1447   GtkScalePrivate *priv;
1448   GtkStyleContext *context;
1449
1450   g_return_if_fail (GTK_IS_SCALE (scale));
1451
1452   priv = scale->priv;
1453
1454   g_slist_foreach (priv->marks, (GFunc)gtk_scale_mark_free, NULL);
1455   g_slist_free (priv->marks);
1456   priv->marks = NULL;
1457
1458   context = gtk_widget_get_style_context (GTK_WIDGET (scale));
1459   gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
1460   gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
1461
1462   _gtk_range_set_stop_values (GTK_RANGE (scale), NULL, 0);
1463
1464   gtk_widget_queue_resize (GTK_WIDGET (scale));
1465 }
1466
1467 static gint
1468 compare_marks (gpointer a, gpointer b)
1469 {
1470   GtkScaleMark *ma, *mb;
1471
1472   ma = a; mb = b;
1473
1474   return (gint) (ma->value - mb->value);
1475 }
1476
1477 /**
1478  * gtk_scale_add_mark:
1479  * @scale: a #GtkScale
1480  * @value: the value at which the mark is placed, must be between
1481  *   the lower and upper limits of the scales' adjustment
1482  * @position: where to draw the mark. For a horizontal scale, #GTK_POS_TOP
1483  *   and %GTK_POS_LEFT are drawn above the scale, anything else below.
1484  *   For a vertical scale, #GTK_POS_LEFT and %GTK_POS_TOP are drawn to
1485  *   the left of the scale, anything else to the right.
1486  * @markup: (allow-none): Text to be shown at the mark, using <link linkend="PangoMarkupFormat">Pango markup</link>, or %NULL
1487  *
1488  *
1489  * Adds a mark at @value.
1490  *
1491  * A mark is indicated visually by drawing a tick mark next to the scale,
1492  * and GTK+ makes it easy for the user to position the scale exactly at the
1493  * marks value.
1494  *
1495  * If @markup is not %NULL, text is shown next to the tick mark.
1496  *
1497  * To remove marks from a scale, use gtk_scale_clear_marks().
1498  *
1499  * Since: 2.16
1500  */
1501 void
1502 gtk_scale_add_mark (GtkScale        *scale,
1503                     gdouble          value,
1504                     GtkPositionType  position,
1505                     const gchar     *markup)
1506 {
1507   GtkScalePrivate *priv;
1508   GtkScaleMark *mark;
1509   GSList *m;
1510   gdouble *values;
1511   gint n, i;
1512   GtkStyleContext *context;
1513   int all_pos;
1514
1515   g_return_if_fail (GTK_IS_SCALE (scale));
1516
1517   priv = scale->priv;
1518
1519   mark = g_new (GtkScaleMark, 1);
1520   mark->value = value;
1521   mark->markup = g_strdup (markup);
1522   if (position == GTK_POS_LEFT ||
1523       position == GTK_POS_TOP)
1524     mark->position = GTK_POS_TOP;
1525   else
1526     mark->position = GTK_POS_BOTTOM;
1527
1528   priv->marks = g_slist_insert_sorted (priv->marks, mark,
1529                                        (GCompareFunc) compare_marks);
1530
1531 #define MARKS_ABOVE 1
1532 #define MARKS_BELOW 2
1533
1534   all_pos = 0;
1535   n = g_slist_length (priv->marks);
1536   values = g_new (gdouble, n);
1537   for (m = priv->marks, i = 0; m; m = m->next, i++)
1538     {
1539       mark = m->data;
1540       values[i] = mark->value;
1541       if (mark->position == GTK_POS_TOP)
1542         all_pos |= MARKS_ABOVE;
1543       else
1544         all_pos |= MARKS_BELOW;
1545     }
1546
1547   _gtk_range_set_stop_values (GTK_RANGE (scale), values, n);
1548
1549   g_free (values);
1550
1551   /* Set the style classes for the slider, so it could
1552    * point to the right direction when marks are present
1553    */
1554   context = gtk_widget_get_style_context (GTK_WIDGET (scale));
1555
1556   if (all_pos & MARKS_ABOVE)
1557     gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
1558   else
1559     gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
1560   if (all_pos & MARKS_BELOW)
1561     gtk_style_context_add_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
1562   else
1563     gtk_style_context_remove_class (context, GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
1564
1565   gtk_widget_queue_resize (GTK_WIDGET (scale));
1566 }
1567
1568 static GtkBuildableIface *parent_buildable_iface;
1569
1570 static void
1571 gtk_scale_buildable_interface_init (GtkBuildableIface *iface)
1572 {
1573   parent_buildable_iface = g_type_interface_peek_parent (iface);
1574   iface->custom_tag_start = gtk_scale_buildable_custom_tag_start;
1575   iface->custom_finished = gtk_scale_buildable_custom_finished;
1576 }
1577
1578 typedef struct
1579 {
1580   GtkScale *scale;
1581   GtkBuilder *builder;
1582   GSList *marks;
1583 } MarksSubparserData;
1584
1585 typedef struct
1586 {
1587   gdouble value;
1588   GtkPositionType position;
1589   GString *markup;
1590   gchar *context;
1591   gboolean translatable;
1592 } MarkData;
1593
1594 static void
1595 mark_data_free (MarkData *data)
1596 {
1597   g_string_free (data->markup, TRUE);
1598   g_free (data->context);
1599   g_slice_free (MarkData, data);
1600 }
1601
1602 static void
1603 marks_start_element (GMarkupParseContext *context,
1604                      const gchar         *element_name,
1605                      const gchar        **names,
1606                      const gchar        **values,
1607                      gpointer             user_data,
1608                      GError             **error)
1609 {
1610   MarksSubparserData *parser_data = (MarksSubparserData*)user_data;
1611   guint i;
1612   gint line_number, char_number;
1613
1614   if (strcmp (element_name, "marks") == 0)
1615    ;
1616   else if (strcmp (element_name, "mark") == 0)
1617     {
1618       gdouble value = 0;
1619       gboolean has_value = FALSE;
1620       GtkPositionType position = GTK_POS_BOTTOM;
1621       const gchar *msg_context = NULL;
1622       gboolean translatable = FALSE;
1623       MarkData *mark;
1624
1625       for (i = 0; names[i]; i++)
1626         {
1627           if (strcmp (names[i], "translatable") == 0)
1628             {
1629               if (!_gtk_builder_boolean_from_string (values[i], &translatable, error))
1630                 return;
1631             }
1632           else if (strcmp (names[i], "comments") == 0)
1633             {
1634               /* do nothing, comments are for translators */
1635             }
1636           else if (strcmp (names[i], "context") == 0)
1637             msg_context = values[i];
1638           else if (strcmp (names[i], "value") == 0)
1639             {
1640               GValue gvalue = { 0, };
1641
1642               if (!gtk_builder_value_from_string_type (parser_data->builder, G_TYPE_DOUBLE, values[i], &gvalue, error))
1643                 return;
1644
1645               value = g_value_get_double (&gvalue);
1646               has_value = TRUE;
1647             }
1648           else if (strcmp (names[i], "position") == 0)
1649             {
1650               GValue gvalue = { 0, };
1651
1652               if (!gtk_builder_value_from_string_type (parser_data->builder, GTK_TYPE_POSITION_TYPE, values[i], &gvalue, error))
1653                 return;
1654
1655               position = g_value_get_enum (&gvalue);
1656             }
1657           else
1658             {
1659               g_markup_parse_context_get_position (context,
1660                                                    &line_number,
1661                                                    &char_number);
1662               g_set_error (error,
1663                            GTK_BUILDER_ERROR,
1664                            GTK_BUILDER_ERROR_INVALID_ATTRIBUTE,
1665                            "%s:%d:%d '%s' is not a valid attribute of <%s>",
1666                            "<input>",
1667                            line_number, char_number, names[i], "mark");
1668               return;
1669             }
1670         }
1671
1672       if (!has_value)
1673         {
1674           g_markup_parse_context_get_position (context,
1675                                                &line_number,
1676                                                &char_number);
1677           g_set_error (error,
1678                        GTK_BUILDER_ERROR,
1679                        GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
1680                        "%s:%d:%d <%s> requires attribute \"%s\"",
1681                        "<input>",
1682                        line_number, char_number, "mark",
1683                        "value");
1684           return;
1685         }
1686
1687       mark = g_slice_new (MarkData);
1688       mark->value = value;
1689       if (position == GTK_POS_LEFT ||
1690           position == GTK_POS_TOP)
1691         mark->position = GTK_POS_TOP;
1692       else
1693         mark->position = GTK_POS_BOTTOM;
1694       mark->markup = g_string_new ("");
1695       mark->context = g_strdup (msg_context);
1696       mark->translatable = translatable;
1697
1698       parser_data->marks = g_slist_prepend (parser_data->marks, mark);
1699     }
1700   else
1701     {
1702       g_markup_parse_context_get_position (context,
1703                                            &line_number,
1704                                            &char_number);
1705       g_set_error (error,
1706                    GTK_BUILDER_ERROR,
1707                    GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
1708                    "%s:%d:%d unsupported tag for GtkScale: \"%s\"",
1709                    "<input>",
1710                    line_number, char_number, element_name);
1711       return;
1712     }
1713 }
1714
1715 static void
1716 marks_text (GMarkupParseContext  *context,
1717             const gchar          *text,
1718             gsize                 text_len,
1719             gpointer              user_data,
1720             GError              **error)
1721 {
1722   MarksSubparserData *data = (MarksSubparserData*)user_data;
1723
1724   if (strcmp (g_markup_parse_context_get_element (context), "mark") == 0)
1725     {
1726       MarkData *mark = data->marks->data;
1727
1728       g_string_append_len (mark->markup, text, text_len);
1729     }
1730 }
1731
1732 static const GMarkupParser marks_parser =
1733   {
1734     marks_start_element,
1735     NULL,
1736     marks_text,
1737   };
1738
1739
1740 static gboolean
1741 gtk_scale_buildable_custom_tag_start (GtkBuildable  *buildable,
1742                                       GtkBuilder    *builder,
1743                                       GObject       *child,
1744                                       const gchar   *tagname,
1745                                       GMarkupParser *parser,
1746                                       gpointer      *data)
1747 {
1748   MarksSubparserData *parser_data;
1749
1750   if (child)
1751     return FALSE;
1752
1753   if (strcmp (tagname, "marks") == 0)
1754     {
1755       parser_data = g_slice_new0 (MarksSubparserData);
1756       parser_data->scale = GTK_SCALE (buildable);
1757       parser_data->marks = NULL;
1758
1759       *parser = marks_parser;
1760       *data = parser_data;
1761       return TRUE;
1762     }
1763
1764   return parent_buildable_iface->custom_tag_start (buildable, builder, child,
1765                                                    tagname, parser, data);
1766 }
1767
1768 static void
1769 gtk_scale_buildable_custom_finished (GtkBuildable *buildable,
1770                                      GtkBuilder   *builder,
1771                                      GObject      *child,
1772                                      const gchar  *tagname,
1773                                      gpointer      user_data)
1774 {
1775   GtkScale *scale = GTK_SCALE (buildable);
1776   MarksSubparserData *marks_data;
1777
1778   if (strcmp (tagname, "marks") == 0)
1779     {
1780       GSList *m;
1781       gchar *markup;
1782
1783       marks_data = (MarksSubparserData *)user_data;
1784
1785       for (m = marks_data->marks; m; m = m->next)
1786         {
1787           MarkData *mdata = m->data;
1788
1789           if (mdata->translatable && mdata->markup->len)
1790             markup = _gtk_builder_parser_translate (gtk_builder_get_translation_domain (builder),
1791                                                     mdata->context,
1792                                                     mdata->markup->str);
1793           else
1794             markup = mdata->markup->str;
1795
1796           gtk_scale_add_mark (scale, mdata->value, mdata->position, markup);
1797
1798           mark_data_free (mdata);
1799         }
1800
1801       g_slist_free (marks_data->marks);
1802       g_slice_free (MarksSubparserData, marks_data);
1803     }
1804 }