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