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