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