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 "gtkrangeprivate.h"
32 
33 #include "gtkadjustment.h"
34 #include "gtkbindings.h"
35 #include "gtkbuildable.h"
36 #include "gtkbuilderprivate.h"
37 #include "gtkcsscustomgadgetprivate.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 #include "gtkstylecontextprivate.h"
45 #include "gtkstylepropertyprivate.h"
46 #include "gtkwidgetprivate.h"
47 #include "gtkcsswidgetnodeprivate.h"
48 
49 #include "a11y/gtkscaleaccessible.h"
50 
51 
52 /**
53  * SECTION:gtkscale
54  * @Short_description: A slider widget for selecting a value from a range
55  * @Title: GtkScale
56  *
57  * A GtkScale is a slider control used to select a numeric value.
58  * To use it, you’ll probably want to investigate the methods on
59  * its base class, #GtkRange, in addition to the methods for GtkScale itself.
60  * To set the value of a scale, you would normally use gtk_range_set_value().
61  * To detect changes to the value, you would normally use the
62  * #GtkRange::value-changed signal.
63  *
64  * Note that using the same upper and lower bounds for the #GtkScale (through
65  * the #GtkRange methods) will hide the slider itself. This is useful for
66  * applications that want to show an undeterminate value on the scale, without
67  * changing the layout of the application (such as movie or music players).
68  *
69  * # GtkScale as GtkBuildable
70  *
71  * GtkScale supports a custom `<marks>` element, which can contain multiple
72  * `<mark>` elements. The “value” and “position” attributes have the same
73  * meaning as gtk_scale_add_mark() parameters of the same name. If the
74  * element is not empty, its content is taken as the markup to show at
75  * the mark. It can be translated with the usual ”translatable” and
76  * “context” attributes.
77  *
78  * # CSS nodes
79  *
80  * |[<!-- language="plain" -->
81  * scale[.fine-tune][.marks-before][.marks-after]
82  * ├── marks.top
83  * │   ├── mark
84  * │   ┊    ├── [label]
85  * │   ┊    ╰── indicator
86  * ┊   ┊
87  * │   ╰── mark
88  * ├── [value]
89  * ├── contents
90  * │   ╰── trough
91  * │       ├── slider
92  * │       ├── [highlight]
93  * │       ╰── [fill]
94  * ╰── marks.bottom
95  *     ├── mark
96  *     ┊    ├── indicator
97  *     ┊    ╰── [label]
98  *     ╰── mark
99  * ]|
100  *
101  * GtkScale has a main CSS node with name scale and a subnode for its contents,
102  * with subnodes named trough and slider.
103  *
104  * The main node gets the style class .fine-tune added when the scale is in
105  * 'fine-tuning' mode.
106  *
107  * If the scale has an origin (see gtk_scale_set_has_origin()), there is a
108  * subnode with name highlight below the trough node that is used for rendering
109  * the highlighted part of the trough.
110  *
111  * If the scale is showing a fill level (see gtk_range_set_show_fill_level()),
112  * there is a subnode with name fill below the trough node that is used for
113  * rendering the filled in part of the trough.
114  *
115  * If marks are present, there is a marks subnode before or after the contents
116  * node, below which each mark gets a node with name mark. The marks nodes get
117  * either the .top or .bottom style class.
118  *
119  * The mark node has a subnode named indicator. If the mark has text, it also
120  * has a subnode named label. When the mark is either above or left of the
121  * scale, the label subnode is the first when present. Otherwise, the indicator
122  * subnode is the first.
123  *
124  * The main CSS node gets the 'marks-before' and/or 'marks-after' style classes
125  * added depending on what marks are present.
126  *
127  * If the scale is displaying the value (see #GtkScale:draw-value), there is
128  * subnode with name value.
129  */
130 
131 
132 #define	MAX_DIGITS	(64)	/* don't change this,
133 				 * a) you don't need to and
134 				 * b) you might cause buffer owerflows in
135 				 *    unrelated code portions otherwise
136 				 */
137 
138 typedef struct _GtkScaleMark GtkScaleMark;
139 
140 struct _GtkScalePrivate
141 {
142   PangoLayout  *layout;
143 
144   GSList       *marks;
145 
146   GtkCssGadget *top_marks_gadget;
147   GtkCssGadget *bottom_marks_gadget;
148   GtkCssGadget *value_gadget;
149 
150   gint          digits;
151 
152   guint         draw_value : 1;
153   guint         value_pos  : 2;
154 };
155 
156 struct _GtkScaleMark
157 {
158   gdouble          value;
159   int              stop_position;
160   gchar           *markup;
161   PangoLayout     *layout;
162   GtkCssGadget    *gadget;
163   GtkCssGadget    *indicator_gadget;
164   GtkCssGadget    *label_gadget;
165   GtkPositionType  position; /* always GTK_POS_TOP or GTK_POS_BOTTOM */
166 };
167 
168 enum {
169   PROP_0,
170   PROP_DIGITS,
171   PROP_DRAW_VALUE,
172   PROP_HAS_ORIGIN,
173   PROP_VALUE_POS,
174   LAST_PROP
175 };
176 
177 enum {
178   FORMAT_VALUE,
179   LAST_SIGNAL
180 };
181 
182 static GParamSpec *properties[LAST_PROP];
183 static guint signals[LAST_SIGNAL];
184 
185 static void     gtk_scale_set_property            (GObject        *object,
186                                                    guint           prop_id,
187                                                    const GValue   *value,
188                                                    GParamSpec     *pspec);
189 static void     gtk_scale_get_property            (GObject        *object,
190                                                    guint           prop_id,
191                                                    GValue         *value,
192                                                    GParamSpec     *pspec);
193 static void     gtk_scale_get_preferred_width     (GtkWidget      *widget,
194                                                    gint           *minimum,
195                                                    gint           *natural);
196 static void     gtk_scale_get_preferred_height    (GtkWidget      *widget,
197                                                    gint           *minimum,
198                                                    gint           *natural);
199 static void     gtk_scale_get_range_border        (GtkRange       *range,
200                                                    GtkBorder      *border);
201 static void     gtk_scale_get_range_size_request  (GtkRange       *range,
202                                                    GtkOrientation  orientation,
203                                                    gint           *minimum,
204                                                    gint           *natural);
205 static void     gtk_scale_finalize                (GObject        *object);
206 static void     gtk_scale_value_style_changed     (GtkCssNode        *node,
207                                                    GtkCssStyleChange *change,
208                                                    GtkScale          *scale);
209 static void     gtk_scale_screen_changed          (GtkWidget      *widget,
210                                                    GdkScreen      *old_screen);
211 static gboolean gtk_scale_draw                    (GtkWidget      *widget,
212                                                    cairo_t        *cr);
213 static void     gtk_scale_real_get_layout_offsets (GtkScale       *scale,
214                                                    gint           *x,
215                                                    gint           *y);
216 static void     gtk_scale_buildable_interface_init   (GtkBuildableIface *iface);
217 static gboolean gtk_scale_buildable_custom_tag_start (GtkBuildable  *buildable,
218                                                       GtkBuilder    *builder,
219                                                       GObject       *child,
220                                                       const gchar   *tagname,
221                                                       GMarkupParser *parser,
222                                                       gpointer      *data);
223 static void     gtk_scale_buildable_custom_finished  (GtkBuildable  *buildable,
224                                                       GtkBuilder    *builder,
225                                                       GObject       *child,
226                                                       const gchar   *tagname,
227                                                       gpointer       user_data);
228 static void     gtk_scale_clear_value_layout         (GtkScale      *scale);
229 static void     gtk_scale_clear_mark_layouts         (GtkScale      *scale);
230 static gchar  * gtk_scale_format_value               (GtkScale      *scale,
231                                                       gdouble        value);
232 
233 
G_DEFINE_TYPE_WITH_CODE(GtkScale,gtk_scale,GTK_TYPE_RANGE,G_ADD_PRIVATE (GtkScale)G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,gtk_scale_buildable_interface_init))234 G_DEFINE_TYPE_WITH_CODE (GtkScale, gtk_scale, GTK_TYPE_RANGE,
235                          G_ADD_PRIVATE (GtkScale)
236                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
237                                                 gtk_scale_buildable_interface_init))
238 
239 static gint
240 compare_marks (gconstpointer a, gconstpointer b, gpointer data)
241 {
242   gboolean inverted = GPOINTER_TO_INT (data);
243   gint val;
244   const GtkScaleMark *ma, *mb;
245 
246   val = inverted ? -1 : 1;
247 
248   ma = a; mb = b;
249 
250   return (ma->value > mb->value) ? val : ((ma->value < mb->value) ? -val : 0);
251 }
252 
253 static void
gtk_scale_notify(GObject * object,GParamSpec * pspec)254 gtk_scale_notify (GObject    *object,
255                   GParamSpec *pspec)
256 {
257   if (strcmp (pspec->name, "inverted") == 0)
258     {
259       GtkScale *scale = GTK_SCALE (object);
260       GtkScaleMark *mark;
261       GSList *m;
262       gint i, n;
263       gdouble *values;
264 
265       scale->priv->marks = g_slist_sort_with_data (scale->priv->marks,
266                                                    compare_marks,
267                                                    GINT_TO_POINTER (gtk_range_get_inverted (GTK_RANGE (scale))));
268 
269       n = g_slist_length (scale->priv->marks);
270       values = g_new (gdouble, n);
271       for (m = scale->priv->marks, i = 0; m; m = m->next, i++)
272         {
273           mark = m->data;
274           values[i] = mark->value;
275         }
276 
277       _gtk_range_set_stop_values (GTK_RANGE (scale), values, n);
278 
279       g_free (values);
280     }
281 
282   if (G_OBJECT_CLASS (gtk_scale_parent_class)->notify)
283     G_OBJECT_CLASS (gtk_scale_parent_class)->notify (object, pspec);
284 }
285 
286 static void
gtk_scale_allocate_value(GtkScale * scale,GtkAllocation * out_clip)287 gtk_scale_allocate_value (GtkScale      *scale,
288                           GtkAllocation *out_clip)
289 {
290   GtkScalePrivate *priv = scale->priv;
291   GtkWidget *widget = GTK_WIDGET (scale);
292   GtkRange *range = GTK_RANGE (widget);
293   GtkCssGadget *range_gadget, *slider_gadget;
294   GtkAllocation range_alloc, slider_alloc, value_alloc;
295 
296   range_gadget = gtk_range_get_gadget (range);
297   gtk_css_gadget_get_margin_allocation (range_gadget, &range_alloc, NULL);
298 
299   slider_gadget = gtk_range_get_slider_gadget (range);
300   gtk_css_gadget_get_border_allocation (slider_gadget, &slider_alloc, NULL);
301 
302   gtk_css_gadget_get_preferred_size (priv->value_gadget,
303                                      GTK_ORIENTATION_HORIZONTAL, -1,
304                                      &value_alloc.width, NULL,
305                                      NULL, NULL);
306   gtk_css_gadget_get_preferred_size (priv->value_gadget,
307                                      GTK_ORIENTATION_VERTICAL, -1,
308                                      &value_alloc.height, NULL,
309                                      NULL, NULL);
310 
311   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL)
312     {
313       switch (priv->value_pos)
314         {
315         case GTK_POS_LEFT:
316           value_alloc.x = range_alloc.x;
317           value_alloc.y = range_alloc.y + (range_alloc.height - value_alloc.height) / 2;
318           break;
319 
320         case GTK_POS_RIGHT:
321           value_alloc.x = range_alloc.x + range_alloc.width - value_alloc.width;
322           value_alloc.y = range_alloc.y + (range_alloc.height - value_alloc.height) / 2;
323           break;
324 
325         case GTK_POS_TOP:
326           value_alloc.x = slider_alloc.x + (slider_alloc.width - value_alloc.width) / 2;
327           value_alloc.x = CLAMP (value_alloc.x, range_alloc.x, range_alloc.x + range_alloc.width - value_alloc.width);
328           value_alloc.y = range_alloc.y;
329           break;
330 
331         case GTK_POS_BOTTOM:
332           value_alloc.x = slider_alloc.x + (slider_alloc.width - value_alloc.width) / 2;
333           value_alloc.x = CLAMP (value_alloc.x, range_alloc.x, range_alloc.x + range_alloc.width - value_alloc.width);
334           value_alloc.y = range_alloc.y + range_alloc.height - value_alloc.height;
335           break;
336 
337         default:
338           g_return_if_reached ();
339           break;
340         }
341     }
342   else
343     {
344       switch (priv->value_pos)
345         {
346         case GTK_POS_LEFT:
347           value_alloc.x = range_alloc.x;
348           value_alloc.y = slider_alloc.y + (slider_alloc.height - value_alloc.height) / 2;
349           value_alloc.y = CLAMP (value_alloc.y, range_alloc.y, range_alloc.y + range_alloc.height - value_alloc.height);
350           break;
351 
352         case GTK_POS_RIGHT:
353           value_alloc.x = range_alloc.x + range_alloc.width - value_alloc.width;
354           value_alloc.y = slider_alloc.y + (slider_alloc.height - value_alloc.height) / 2;
355           value_alloc.y = CLAMP (value_alloc.y, range_alloc.y, range_alloc.y + range_alloc.height - value_alloc.height);
356           break;
357 
358         case GTK_POS_TOP:
359           value_alloc.x = range_alloc.x + (range_alloc.width - value_alloc.width) / 2;
360           value_alloc.y = range_alloc.y;
361           break;
362 
363         case GTK_POS_BOTTOM:
364           value_alloc.x = range_alloc.x + (range_alloc.width - value_alloc.width) / 2;
365           value_alloc.y = range_alloc.y + range_alloc.height - value_alloc.height;
366           break;
367 
368         default:
369           g_return_if_reached ();
370         }
371     }
372 
373   gtk_css_gadget_allocate (priv->value_gadget,
374                            &value_alloc, -1,
375                            out_clip);
376 }
377 
378 static void
gtk_scale_allocate_mark(GtkCssGadget * gadget,const GtkAllocation * allocation,int baseline,GtkAllocation * out_clip,gpointer user_data)379 gtk_scale_allocate_mark (GtkCssGadget        *gadget,
380                          const GtkAllocation *allocation,
381                          int                  baseline,
382                          GtkAllocation       *out_clip,
383                          gpointer             user_data)
384 {
385   GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
386   GtkScaleMark *mark = user_data;
387   GtkAllocation indicator_alloc, widget_alloc;
388   int indicator_width, indicator_height;
389   GtkOrientation orientation;
390 
391   orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
392   gtk_widget_get_allocation (widget, &widget_alloc);
393   gtk_css_gadget_get_preferred_size (mark->indicator_gadget,
394                                      GTK_ORIENTATION_HORIZONTAL, -1,
395                                      &indicator_width, NULL,
396                                      NULL, NULL);
397   gtk_css_gadget_get_preferred_size (mark->indicator_gadget,
398                                      GTK_ORIENTATION_VERTICAL, -1,
399                                      &indicator_height, NULL,
400                                      NULL, NULL);
401 
402   if (orientation == GTK_ORIENTATION_HORIZONTAL)
403     {
404       indicator_alloc.x = mark->stop_position + widget_alloc.x - indicator_width / 2;
405       if (mark->position == GTK_POS_TOP)
406         indicator_alloc.y = allocation->y + allocation->height - indicator_height;
407       else
408         indicator_alloc.y = allocation->y;
409       indicator_alloc.width = indicator_width;
410       indicator_alloc.height = indicator_height;
411     }
412   else
413     {
414       if (mark->position == GTK_POS_TOP)
415         indicator_alloc.x = allocation->x + allocation->width - indicator_width;
416       else
417         indicator_alloc.x = allocation->x;
418       indicator_alloc.y = mark->stop_position + widget_alloc.y - indicator_height / 2;
419       indicator_alloc.width = indicator_width;
420       indicator_alloc.height = indicator_height;
421     }
422 
423   gtk_css_gadget_allocate (mark->indicator_gadget,
424                            &indicator_alloc, baseline,
425                            out_clip);
426 
427   if (mark->label_gadget)
428     {
429       GtkAllocation label_alloc, label_clip;
430 
431       label_alloc = *allocation;
432 
433       if (orientation == GTK_ORIENTATION_HORIZONTAL)
434         {
435           label_alloc.height = allocation->height - indicator_alloc.height;
436           if (mark->position == GTK_POS_BOTTOM)
437             label_alloc.y = indicator_alloc.y + indicator_alloc.height;
438         }
439       else
440         {
441           label_alloc.width = allocation->width - indicator_alloc.width;
442           if (mark->position == GTK_POS_BOTTOM)
443             label_alloc.x = indicator_alloc.x + indicator_alloc.width;
444         }
445 
446       gtk_css_gadget_allocate (mark->label_gadget,
447                                &label_alloc, baseline,
448                                &label_clip);
449       gdk_rectangle_union (out_clip, &label_clip, out_clip);
450     }
451 }
452 
453 static gint
find_next_pos(GtkWidget * widget,GSList * list,gint * marks,GtkPositionType pos)454 find_next_pos (GtkWidget       *widget,
455                GSList          *list,
456                gint            *marks,
457                GtkPositionType  pos)
458 {
459   GtkAllocation allocation;
460   GSList *m;
461   gint i;
462 
463   for (m = list->next, i = 1; m; m = m->next, i++)
464     {
465       GtkScaleMark *mark = m->data;
466 
467       if (mark->position == pos)
468         return marks[i];
469     }
470 
471   gtk_widget_get_allocation (widget, &allocation);
472   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
473     return allocation.width;
474   else
475     return allocation.height;
476 }
477 
478 static void
gtk_scale_allocate_marks(GtkCssGadget * gadget,const GtkAllocation * allocation,int baseline,GtkAllocation * out_clip,gpointer data)479 gtk_scale_allocate_marks (GtkCssGadget        *gadget,
480                           const GtkAllocation *allocation,
481                           int                  baseline,
482                           GtkAllocation       *out_clip,
483                           gpointer             data)
484 {
485   GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
486   GtkScale *scale = GTK_SCALE (widget);
487   GtkScalePrivate *priv = scale->priv;
488   GtkOrientation orientation;
489   int *marks;
490   int min_pos_before, min_pos_after;
491   int min_sep = 4;
492   int i;
493   int min_pos, max_pos;
494   GSList *m;
495   GtkAllocation widget_alloc;
496 
497   orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (scale));
498   _gtk_range_get_stop_positions (GTK_RANGE (scale), &marks);
499   gtk_widget_get_allocation (widget, &widget_alloc);
500 
501   if (orientation == GTK_ORIENTATION_HORIZONTAL)
502     min_pos_before = min_pos_after = widget_alloc.x;
503   else
504     min_pos_before = min_pos_after = widget_alloc.y;
505 
506   for (m = priv->marks, i = 0; m; m = m->next, i++)
507     {
508       GtkScaleMark *mark = m->data;
509       GtkAllocation mark_alloc, mark_clip;
510       int mark_size;
511 
512       if ((mark->position == GTK_POS_TOP && gadget == priv->bottom_marks_gadget) ||
513           (mark->position == GTK_POS_BOTTOM && gadget == priv->top_marks_gadget))
514         continue;
515 
516       gtk_css_gadget_get_preferred_size (mark->gadget,
517                                          orientation, -1,
518                                          &mark_size, NULL,
519                                          NULL, NULL);
520       mark->stop_position = marks[i];
521 
522       if (orientation == GTK_ORIENTATION_HORIZONTAL)
523         {
524           mark_alloc.x = mark->stop_position + widget_alloc.x;
525           mark_alloc.y = allocation->y;
526           mark_alloc.width = mark_size;
527           mark_alloc.height = allocation->height;
528 
529           if (mark->position == GTK_POS_TOP)
530             {
531               min_pos = min_pos_before;
532               max_pos = find_next_pos (widget, m, marks + i, GTK_POS_TOP) - min_sep + widget_alloc.x;
533             }
534           else
535             {
536               min_pos = min_pos_after;
537               max_pos = find_next_pos (widget, m, marks + i, GTK_POS_BOTTOM) - min_sep + widget_alloc.x;
538             }
539 
540           mark_alloc.x -= mark_size / 2;
541 
542           if (mark_alloc.x < min_pos)
543             mark_alloc.x = min_pos;
544           if (mark_alloc.x + mark_size > max_pos)
545             mark_alloc.x = max_pos - mark_size;
546           if (mark_alloc.x < 0)
547             mark_alloc.x = 0;
548 
549           if (mark->position == GTK_POS_TOP)
550             min_pos_before = mark_alloc.x + mark_size + min_sep;
551           else
552             min_pos_after = mark_alloc.x + mark_size + min_sep;
553         }
554       else
555         {
556           mark_alloc.x = allocation->x;
557           mark_alloc.y = mark->stop_position + widget_alloc.y;
558           mark_alloc.width = allocation->width;
559           mark_alloc.height = mark_size;
560 
561           if (mark->position == GTK_POS_TOP)
562             {
563               min_pos = min_pos_before;
564               max_pos = find_next_pos (widget, m, marks + i, GTK_POS_TOP) - min_sep + widget_alloc.y;
565             }
566           else
567             {
568               min_pos = min_pos_after;
569               max_pos = find_next_pos (widget, m, marks + i, GTK_POS_BOTTOM) - min_sep + widget_alloc.y;
570             }
571 
572           mark_alloc.y -= mark_size / 2;
573 
574           if (mark_alloc.y < min_pos)
575             mark_alloc.y = min_pos;
576           if (mark_alloc.y + mark_size > max_pos)
577             mark_alloc.y = max_pos - mark_size;
578           if (mark_alloc.y < 0)
579             mark_alloc.y = 0;
580 
581           if (mark->position == GTK_POS_TOP)
582             min_pos_before = mark_alloc.y + mark_size + min_sep;
583           else
584             min_pos_after = mark_alloc.y + mark_size + min_sep;
585         }
586 
587       gtk_css_gadget_allocate (mark->gadget, &mark_alloc, baseline, &mark_clip);
588       gdk_rectangle_union (out_clip, &mark_clip, out_clip);
589     }
590 
591   g_free (marks);
592 }
593 
594 static void
gtk_scale_size_allocate(GtkWidget * widget,GtkAllocation * allocation)595 gtk_scale_size_allocate (GtkWidget     *widget,
596                          GtkAllocation *allocation)
597 {
598   GtkScale *scale = GTK_SCALE (widget);
599   GtkScalePrivate *priv = scale->priv;
600   GtkAllocation clip, marks_clip, range_rect, marks_rect;
601   GtkOrientation orientation;
602 
603   GTK_WIDGET_CLASS (gtk_scale_parent_class)->size_allocate (widget, allocation);
604 
605   gtk_widget_get_clip (widget, &clip);
606   orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
607   gtk_range_get_range_rect (GTK_RANGE (scale), &range_rect);
608 
609   range_rect.x += allocation->x;
610   range_rect.y += allocation->y;
611 
612   if (orientation == GTK_ORIENTATION_HORIZONTAL)
613     {
614       int marks_height = 0;
615 
616       if (priv->top_marks_gadget)
617         {
618           gtk_css_gadget_get_preferred_size (priv->top_marks_gadget,
619                                              GTK_ORIENTATION_VERTICAL, -1,
620                                              &marks_height, NULL,
621                                              NULL, NULL);
622           marks_rect = range_rect;
623           marks_rect.y -= marks_height;
624           marks_rect.height = marks_height;
625           gtk_css_gadget_allocate (priv->top_marks_gadget,
626                                    &marks_rect,
627                                    -1,
628                                    &marks_clip);
629           gdk_rectangle_union (&clip, &marks_clip, &clip);
630         }
631 
632       if (priv->bottom_marks_gadget)
633         {
634           gtk_css_gadget_get_preferred_size (priv->bottom_marks_gadget,
635                                              GTK_ORIENTATION_VERTICAL, -1,
636                                              &marks_height, NULL,
637                                              NULL, NULL);
638           marks_rect = range_rect;
639           marks_rect.y += range_rect.height;
640           marks_rect.height = marks_height;
641           gtk_css_gadget_allocate (priv->bottom_marks_gadget,
642                                    &marks_rect,
643                                    -1,
644                                    &marks_clip);
645           gdk_rectangle_union (&clip, &marks_clip, &clip);
646         }
647     }
648   else
649     {
650       int marks_width = 0;
651 
652       if (priv->top_marks_gadget)
653         {
654           gtk_css_gadget_get_preferred_size (priv->top_marks_gadget,
655                                              GTK_ORIENTATION_HORIZONTAL, -1,
656                                              &marks_width, NULL,
657                                              NULL, NULL);
658           marks_rect = range_rect;
659           marks_rect.x -= marks_width;
660           marks_rect.width = marks_width;
661           gtk_css_gadget_allocate (priv->top_marks_gadget,
662                                    &marks_rect,
663                                    -1,
664                                    &marks_clip);
665           gdk_rectangle_union (&clip, &marks_clip, &clip);
666         }
667 
668       if (priv->bottom_marks_gadget)
669         {
670           gtk_css_gadget_get_preferred_size (priv->bottom_marks_gadget,
671                                              GTK_ORIENTATION_HORIZONTAL, -1,
672                                              &marks_width, NULL,
673                                              NULL, NULL);
674           marks_rect = range_rect;
675           marks_rect.x += range_rect.width;
676           marks_rect.width = marks_width;
677           gtk_css_gadget_allocate (priv->bottom_marks_gadget,
678                                    &marks_rect,
679                                    -1,
680                                    &marks_clip);
681           gdk_rectangle_union (&clip, &marks_clip, &clip);
682         }
683     }
684 
685   if (priv->value_gadget)
686     {
687       GtkAllocation value_clip;
688 
689       gtk_scale_allocate_value (scale, &value_clip);
690       gdk_rectangle_union (&clip, &value_clip, &clip);
691     }
692 
693   gtk_widget_set_clip (widget, &clip);
694 }
695 
696 #define add_slider_binding(binding_set, keyval, mask, scroll)              \
697   gtk_binding_entry_add_signal (binding_set, keyval, mask,                 \
698                                 I_("move-slider"), 1, \
699                                 GTK_TYPE_SCROLL_TYPE, scroll)
700 
701 static void
gtk_scale_class_init(GtkScaleClass * class)702 gtk_scale_class_init (GtkScaleClass *class)
703 {
704   GObjectClass   *gobject_class;
705   GtkWidgetClass *widget_class;
706   GtkRangeClass  *range_class;
707   GtkBindingSet  *binding_set;
708 
709   gobject_class = G_OBJECT_CLASS (class);
710   range_class = (GtkRangeClass*) class;
711   widget_class = (GtkWidgetClass*) class;
712 
713   gobject_class->set_property = gtk_scale_set_property;
714   gobject_class->get_property = gtk_scale_get_property;
715   gobject_class->notify = gtk_scale_notify;
716   gobject_class->finalize = gtk_scale_finalize;
717 
718   widget_class->screen_changed = gtk_scale_screen_changed;
719   widget_class->draw = gtk_scale_draw;
720   widget_class->size_allocate = gtk_scale_size_allocate;
721   widget_class->get_preferred_width = gtk_scale_get_preferred_width;
722   widget_class->get_preferred_height = gtk_scale_get_preferred_height;
723 
724   range_class->get_range_border = gtk_scale_get_range_border;
725   range_class->get_range_size_request = gtk_scale_get_range_size_request;
726 
727   class->get_layout_offsets = gtk_scale_real_get_layout_offsets;
728 
729   /**
730    * GtkScale::format-value:
731    * @scale: the object which received the signal
732    * @value: the value to format
733    *
734    * Signal which allows you to change how the scale value is displayed.
735    * Connect a signal handler which returns an allocated string representing
736    * @value. That string will then be used to display the scale's value.
737    *
738    * If no user-provided handlers are installed, the value will be displayed on
739    * its own, rounded according to the value of the #GtkScale:digits property.
740    *
741    * Here's an example signal handler which displays a value 1.0 as
742    * with "-->1.0<--".
743    * |[<!-- language="C" -->
744    * static gchar*
745    * format_value_callback (GtkScale *scale,
746    *                        gdouble   value)
747    * {
748    *   return g_strdup_printf ("-->\%0.*g<--",
749    *                           gtk_scale_get_digits (scale), value);
750    *  }
751    * ]|
752    *
753    * Returns: allocated string representing @value
754    */
755   signals[FORMAT_VALUE] =
756     g_signal_new (I_("format-value"),
757                   G_TYPE_FROM_CLASS (gobject_class),
758                   G_SIGNAL_RUN_LAST,
759                   G_STRUCT_OFFSET (GtkScaleClass, format_value),
760                   _gtk_single_string_accumulator, NULL,
761                   _gtk_marshal_STRING__DOUBLE,
762                   G_TYPE_STRING, 1,
763                   G_TYPE_DOUBLE);
764 
765   properties[PROP_DIGITS] =
766       g_param_spec_int ("digits",
767                         P_("Digits"),
768                         P_("The number of decimal places that are displayed in the value"),
769                         -1, MAX_DIGITS,
770                         1,
771                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
772 
773   properties[PROP_DRAW_VALUE] =
774       g_param_spec_boolean ("draw-value",
775                             P_("Draw Value"),
776                             P_("Whether the current value is displayed as a string next to the slider"),
777                             TRUE,
778                             GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
779 
780   properties[PROP_HAS_ORIGIN] =
781       g_param_spec_boolean ("has-origin",
782                             P_("Has Origin"),
783                             P_("Whether the scale has an origin"),
784                             TRUE,
785                             GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
786 
787   properties[PROP_VALUE_POS] =
788       g_param_spec_enum ("value-pos",
789                          P_("Value Position"),
790                          P_("The position in which the current value is displayed"),
791                          GTK_TYPE_POSITION_TYPE,
792                          GTK_POS_TOP,
793                          GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
794 
795   g_object_class_install_properties (gobject_class, LAST_PROP, properties);
796 
797   /**
798    * GtkScale:slider-length:
799    *
800    * Length of scale's slider.
801    *
802    * Deprecated: 3.20: Use min-height/min-width CSS properties on the slider
803    *   element instead. The value of this style property is ignored.
804    */
805   gtk_widget_class_install_style_property (widget_class,
806                                            g_param_spec_int ("slider-length",
807                                                              P_("Slider Length"),
808                                                              P_("Length of scale's slider"),
809                                                              0, G_MAXINT, 31,
810                                                              GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
811 
812   /**
813    * GtkScale:value-spacing:
814    *
815    * Space between value text and the slider/trough area.
816    *
817    * Deprecated: 3.20: Use min-height/min-width CSS properties on the value
818    *   element instead. The value of this style property is ignored.
819    */
820   gtk_widget_class_install_style_property (widget_class,
821 					   g_param_spec_int ("value-spacing",
822 							     P_("Value spacing"),
823 							     P_("Space between value text and the slider/trough area"),
824 							     0,
825 							     G_MAXINT,
826 							     2,
827 							     GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
828 
829   /* All bindings (even arrow keys) are on both h/v scale, because
830    * blind users etc. don't care about scale orientation.
831    */
832 
833   binding_set = gtk_binding_set_by_class (class);
834 
835   add_slider_binding (binding_set, GDK_KEY_Left, 0,
836                       GTK_SCROLL_STEP_LEFT);
837 
838   add_slider_binding (binding_set, GDK_KEY_Left, GDK_CONTROL_MASK,
839                       GTK_SCROLL_PAGE_LEFT);
840 
841   add_slider_binding (binding_set, GDK_KEY_KP_Left, 0,
842                       GTK_SCROLL_STEP_LEFT);
843 
844   add_slider_binding (binding_set, GDK_KEY_KP_Left, GDK_CONTROL_MASK,
845                       GTK_SCROLL_PAGE_LEFT);
846 
847   add_slider_binding (binding_set, GDK_KEY_Right, 0,
848                       GTK_SCROLL_STEP_RIGHT);
849 
850   add_slider_binding (binding_set, GDK_KEY_Right, GDK_CONTROL_MASK,
851                       GTK_SCROLL_PAGE_RIGHT);
852 
853   add_slider_binding (binding_set, GDK_KEY_KP_Right, 0,
854                       GTK_SCROLL_STEP_RIGHT);
855 
856   add_slider_binding (binding_set, GDK_KEY_KP_Right, GDK_CONTROL_MASK,
857                       GTK_SCROLL_PAGE_RIGHT);
858 
859   add_slider_binding (binding_set, GDK_KEY_Up, 0,
860                       GTK_SCROLL_STEP_UP);
861 
862   add_slider_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK,
863                       GTK_SCROLL_PAGE_UP);
864 
865   add_slider_binding (binding_set, GDK_KEY_KP_Up, 0,
866                       GTK_SCROLL_STEP_UP);
867 
868   add_slider_binding (binding_set, GDK_KEY_KP_Up, GDK_CONTROL_MASK,
869                       GTK_SCROLL_PAGE_UP);
870 
871   add_slider_binding (binding_set, GDK_KEY_Down, 0,
872                       GTK_SCROLL_STEP_DOWN);
873 
874   add_slider_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK,
875                       GTK_SCROLL_PAGE_DOWN);
876 
877   add_slider_binding (binding_set, GDK_KEY_KP_Down, 0,
878                       GTK_SCROLL_STEP_DOWN);
879 
880   add_slider_binding (binding_set, GDK_KEY_KP_Down, GDK_CONTROL_MASK,
881                       GTK_SCROLL_PAGE_DOWN);
882 
883   add_slider_binding (binding_set, GDK_KEY_Page_Up, GDK_CONTROL_MASK,
884                       GTK_SCROLL_PAGE_LEFT);
885 
886   add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, GDK_CONTROL_MASK,
887                       GTK_SCROLL_PAGE_LEFT);
888 
889   add_slider_binding (binding_set, GDK_KEY_Page_Up, 0,
890                       GTK_SCROLL_PAGE_UP);
891 
892   add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, 0,
893                       GTK_SCROLL_PAGE_UP);
894 
895   add_slider_binding (binding_set, GDK_KEY_Page_Down, GDK_CONTROL_MASK,
896                       GTK_SCROLL_PAGE_RIGHT);
897 
898   add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, GDK_CONTROL_MASK,
899                       GTK_SCROLL_PAGE_RIGHT);
900 
901   add_slider_binding (binding_set, GDK_KEY_Page_Down, 0,
902                       GTK_SCROLL_PAGE_DOWN);
903 
904   add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, 0,
905                       GTK_SCROLL_PAGE_DOWN);
906 
907   /* Logical bindings (vs. visual bindings above) */
908 
909   add_slider_binding (binding_set, GDK_KEY_plus, 0,
910                       GTK_SCROLL_STEP_FORWARD);
911 
912   add_slider_binding (binding_set, GDK_KEY_minus, 0,
913                       GTK_SCROLL_STEP_BACKWARD);
914 
915   add_slider_binding (binding_set, GDK_KEY_plus, GDK_CONTROL_MASK,
916                       GTK_SCROLL_PAGE_FORWARD);
917 
918   add_slider_binding (binding_set, GDK_KEY_minus, GDK_CONTROL_MASK,
919                       GTK_SCROLL_PAGE_BACKWARD);
920 
921 
922   add_slider_binding (binding_set, GDK_KEY_KP_Add, 0,
923                       GTK_SCROLL_STEP_FORWARD);
924 
925   add_slider_binding (binding_set, GDK_KEY_KP_Subtract, 0,
926                       GTK_SCROLL_STEP_BACKWARD);
927 
928   add_slider_binding (binding_set, GDK_KEY_KP_Add, GDK_CONTROL_MASK,
929                       GTK_SCROLL_PAGE_FORWARD);
930 
931   add_slider_binding (binding_set, GDK_KEY_KP_Subtract, GDK_CONTROL_MASK,
932                       GTK_SCROLL_PAGE_BACKWARD);
933 
934 
935   add_slider_binding (binding_set, GDK_KEY_Home, 0,
936                       GTK_SCROLL_START);
937 
938   add_slider_binding (binding_set, GDK_KEY_KP_Home, 0,
939                       GTK_SCROLL_START);
940 
941   add_slider_binding (binding_set, GDK_KEY_End, 0,
942                       GTK_SCROLL_END);
943 
944   add_slider_binding (binding_set, GDK_KEY_KP_End, 0,
945                       GTK_SCROLL_END);
946 
947   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SCALE_ACCESSIBLE);
948   gtk_widget_class_set_css_name (widget_class, "scale");
949 }
950 
951 static void
gtk_scale_init(GtkScale * scale)952 gtk_scale_init (GtkScale *scale)
953 {
954   GtkScalePrivate *priv;
955   GtkRange *range = GTK_RANGE (scale);
956 
957   scale->priv = gtk_scale_get_instance_private (scale);
958   priv = scale->priv;
959 
960   priv->value_pos = GTK_POS_TOP;
961   priv->digits = 1;
962 
963   gtk_widget_set_can_focus (GTK_WIDGET (scale), TRUE);
964 
965   gtk_range_set_slider_size_fixed (range, TRUE);
966   gtk_range_set_slider_use_min_size (range, TRUE);
967 
968   _gtk_range_set_has_origin (range, TRUE);
969 
970   gtk_scale_set_draw_value (scale, TRUE);
971   gtk_range_set_round_digits (range, priv->digits);
972 
973   gtk_range_set_flippable (range, TRUE);
974 }
975 
976 static void
gtk_scale_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)977 gtk_scale_set_property (GObject      *object,
978 			guint         prop_id,
979 			const GValue *value,
980 			GParamSpec   *pspec)
981 {
982   GtkScale *scale;
983 
984   scale = GTK_SCALE (object);
985 
986   switch (prop_id)
987     {
988     case PROP_DIGITS:
989       gtk_scale_set_digits (scale, g_value_get_int (value));
990       break;
991     case PROP_DRAW_VALUE:
992       gtk_scale_set_draw_value (scale, g_value_get_boolean (value));
993       break;
994     case PROP_HAS_ORIGIN:
995       gtk_scale_set_has_origin (scale, g_value_get_boolean (value));
996       break;
997     case PROP_VALUE_POS:
998       gtk_scale_set_value_pos (scale, g_value_get_enum (value));
999       break;
1000     default:
1001       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1002       break;
1003     }
1004 }
1005 
1006 static void
gtk_scale_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1007 gtk_scale_get_property (GObject      *object,
1008 			guint         prop_id,
1009 			GValue       *value,
1010 			GParamSpec   *pspec)
1011 {
1012   GtkScale *scale = GTK_SCALE (object);
1013   GtkScalePrivate *priv = scale->priv;
1014 
1015   switch (prop_id)
1016     {
1017     case PROP_DIGITS:
1018       g_value_set_int (value, priv->digits);
1019       break;
1020     case PROP_DRAW_VALUE:
1021       g_value_set_boolean (value, priv->draw_value);
1022       break;
1023     case PROP_HAS_ORIGIN:
1024       g_value_set_boolean (value, gtk_scale_get_has_origin (scale));
1025       break;
1026     case PROP_VALUE_POS:
1027       g_value_set_enum (value, priv->value_pos);
1028       break;
1029     default:
1030       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1031       break;
1032     }
1033 }
1034 
1035 /**
1036  * gtk_scale_new:
1037  * @orientation: the scale’s orientation.
1038  * @adjustment: (nullable): the #GtkAdjustment which sets the range
1039  *              of the scale, or %NULL to create a new adjustment.
1040  *
1041  * Creates a new #GtkScale.
1042  *
1043  * Returns: a new #GtkScale
1044  *
1045  * Since: 3.0
1046  **/
1047 GtkWidget *
gtk_scale_new(GtkOrientation orientation,GtkAdjustment * adjustment)1048 gtk_scale_new (GtkOrientation  orientation,
1049                GtkAdjustment  *adjustment)
1050 {
1051   g_return_val_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment),
1052                         NULL);
1053 
1054   return g_object_new (GTK_TYPE_SCALE,
1055                        "orientation", orientation,
1056                        "adjustment",  adjustment,
1057                        NULL);
1058 }
1059 
1060 /**
1061  * gtk_scale_new_with_range:
1062  * @orientation: the scale’s orientation.
1063  * @min: minimum value
1064  * @max: maximum value
1065  * @step: step increment (tick size) used with keyboard shortcuts
1066  *
1067  * Creates a new scale widget with the given orientation that lets the
1068  * user input a number between @min and @max (including @min and @max)
1069  * with the increment @step.  @step must be nonzero; it’s the distance
1070  * the slider moves when using the arrow keys to adjust the scale
1071  * value.
1072  *
1073  * Note that the way in which the precision is derived works best if @step
1074  * is a power of ten. If the resulting precision is not suitable for your
1075  * needs, use gtk_scale_set_digits() to correct it.
1076  *
1077  * Returns: a new #GtkScale
1078  *
1079  * Since: 3.0
1080  */
1081 GtkWidget *
gtk_scale_new_with_range(GtkOrientation orientation,gdouble min,gdouble max,gdouble step)1082 gtk_scale_new_with_range (GtkOrientation orientation,
1083                           gdouble        min,
1084                           gdouble        max,
1085                           gdouble        step)
1086 {
1087   GtkAdjustment *adj;
1088   gint digits;
1089 
1090   g_return_val_if_fail (min < max, NULL);
1091   g_return_val_if_fail (step != 0.0, NULL);
1092 
1093   adj = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
1094 
1095   if (fabs (step) >= 1.0 || step == 0.0)
1096     {
1097       digits = 0;
1098     }
1099   else
1100     {
1101       digits = abs ((gint) floor (log10 (fabs (step))));
1102       if (digits > 5)
1103         digits = 5;
1104     }
1105 
1106   return g_object_new (GTK_TYPE_SCALE,
1107                        "orientation", orientation,
1108                        "adjustment",  adj,
1109                        "digits",      digits,
1110                        NULL);
1111 }
1112 
1113 /**
1114  * gtk_scale_set_digits:
1115  * @scale: a #GtkScale
1116  * @digits: the number of decimal places to display,
1117  *     e.g. use 1 to display 1.0, 2 to display 1.00, etc
1118  *
1119  * Sets the number of decimal places that are displayed in the value. Also
1120  * causes the value of the adjustment to be rounded to this number of digits,
1121  * so the retrieved value matches the displayed one, if #GtkScale:draw-value is
1122  * %TRUE when the value changes. If you want to enforce rounding the value when
1123  * #GtkScale:draw-value is %FALSE, you can set #GtkRange:round-digits instead.
1124  *
1125  * Note that rounding to a small number of digits can interfere with
1126  * the smooth autoscrolling that is built into #GtkScale. As an alternative,
1127  * you can use the #GtkScale::format-value signal to format the displayed
1128  * value yourself.
1129  */
1130 void
gtk_scale_set_digits(GtkScale * scale,gint digits)1131 gtk_scale_set_digits (GtkScale *scale,
1132 		      gint      digits)
1133 {
1134   GtkScalePrivate *priv;
1135   GtkRange *range;
1136 
1137   g_return_if_fail (GTK_IS_SCALE (scale));
1138 
1139   priv = scale->priv;
1140   range = GTK_RANGE (scale);
1141 
1142   digits = CLAMP (digits, -1, MAX_DIGITS);
1143 
1144   if (priv->digits != digits)
1145     {
1146       priv->digits = digits;
1147       if (priv->draw_value)
1148         gtk_range_set_round_digits (range, digits);
1149 
1150       gtk_scale_clear_value_layout (scale);
1151       gtk_widget_queue_resize (GTK_WIDGET (scale));
1152 
1153       g_object_notify_by_pspec (G_OBJECT (scale), properties[PROP_DIGITS]);
1154     }
1155 }
1156 
1157 /**
1158  * gtk_scale_get_digits:
1159  * @scale: a #GtkScale
1160  *
1161  * Gets the number of decimal places that are displayed in the value.
1162  *
1163  * Returns: the number of decimal places that are displayed
1164  */
1165 gint
gtk_scale_get_digits(GtkScale * scale)1166 gtk_scale_get_digits (GtkScale *scale)
1167 {
1168   g_return_val_if_fail (GTK_IS_SCALE (scale), -1);
1169 
1170   return scale->priv->digits;
1171 }
1172 
1173 static gboolean
gtk_scale_render_value(GtkCssGadget * gadget,cairo_t * cr,int x,int y,int width,int height,gpointer user_data)1174 gtk_scale_render_value (GtkCssGadget *gadget,
1175                         cairo_t      *cr,
1176                         int           x,
1177                         int           y,
1178                         int           width,
1179                         int           height,
1180                         gpointer      user_data)
1181 {
1182   GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
1183   GtkScale *scale = GTK_SCALE (widget);
1184   GtkStyleContext *context;
1185   PangoLayout *layout;
1186 
1187   context = gtk_widget_get_style_context (widget);
1188   gtk_style_context_save_to_node (context, gtk_css_gadget_get_node (gadget));
1189 
1190   layout = gtk_scale_get_layout (scale);
1191   gtk_render_layout (context, cr, x, y, layout);
1192 
1193   gtk_style_context_restore (context);
1194 
1195   return FALSE;
1196 }
1197 
1198 static void
gtk_css_node_update_layout_attributes(GtkCssNode * node,PangoLayout * layout)1199 gtk_css_node_update_layout_attributes (GtkCssNode  *node,
1200                                        PangoLayout *layout)
1201 {
1202   GtkCssStyle *style;
1203   PangoAttrList *attrs;
1204   PangoFontDescription *desc;
1205 
1206   style = gtk_css_node_get_style (node);
1207 
1208   attrs = gtk_css_style_get_pango_attributes (style);
1209   desc = gtk_css_style_get_pango_font (style);
1210 
1211   pango_layout_set_attributes (layout, attrs);
1212   pango_layout_set_font_description (layout, desc);
1213 
1214   if (attrs)
1215     pango_attr_list_unref (attrs);
1216   pango_font_description_free (desc);
1217 }
1218 
1219 static void
gtk_scale_measure_value(GtkCssGadget * gadget,GtkOrientation orientation,gint for_size,gint * minimum,gint * natural,gint * minimum_baseline,gint * natural_baseline,gpointer user_data)1220 gtk_scale_measure_value (GtkCssGadget   *gadget,
1221                          GtkOrientation  orientation,
1222                          gint            for_size,
1223                          gint           *minimum,
1224                          gint           *natural,
1225                          gint           *minimum_baseline,
1226                          gint           *natural_baseline,
1227                          gpointer        user_data)
1228 {
1229   GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
1230   GtkScale *scale = GTK_SCALE (widget);
1231   GtkScalePrivate *priv = scale->priv;
1232   int width, height;
1233 
1234   width = height = 0;
1235 
1236   if (priv->draw_value)
1237     {
1238       GtkAdjustment *adjustment;
1239       PangoLayout *layout;
1240       PangoRectangle logical_rect;
1241       gchar *txt;
1242 
1243       layout = gtk_widget_create_pango_layout (widget, NULL);
1244       gtk_css_node_update_layout_attributes (gtk_css_gadget_get_node (priv->value_gadget), layout);
1245 
1246       adjustment = gtk_range_get_adjustment (GTK_RANGE (scale));
1247 
1248       txt = gtk_scale_format_value (scale, gtk_adjustment_get_lower (adjustment));
1249       pango_layout_set_text (layout, txt, -1);
1250       g_free (txt);
1251 
1252       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1253 
1254       width = logical_rect.width;
1255       height = logical_rect.height;
1256 
1257       txt = gtk_scale_format_value (scale, gtk_adjustment_get_upper (adjustment));
1258       pango_layout_set_text (layout, txt, -1);
1259       g_free (txt);
1260 
1261       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1262 
1263       width = MAX (width, logical_rect.width);
1264       height = MAX (height, logical_rect.height);
1265 
1266       g_object_unref (layout);
1267     }
1268 
1269   if (orientation == GTK_ORIENTATION_HORIZONTAL)
1270     *minimum = *natural = width;
1271   else
1272     *minimum = *natural = height;
1273 }
1274 
1275 static void
update_value_position(GtkScale * scale)1276 update_value_position (GtkScale *scale)
1277 {
1278   GtkScalePrivate *priv = scale->priv;
1279 
1280   if (!priv->value_gadget)
1281     return;
1282 
1283   if (priv->value_pos == GTK_POS_TOP || priv->value_pos == GTK_POS_LEFT)
1284     {
1285       gtk_css_gadget_remove_class (priv->value_gadget, GTK_STYLE_CLASS_BOTTOM);
1286       gtk_css_gadget_add_class (priv->value_gadget, GTK_STYLE_CLASS_TOP);
1287     }
1288   else
1289     {
1290       gtk_css_gadget_remove_class (priv->value_gadget, GTK_STYLE_CLASS_TOP);
1291       gtk_css_gadget_add_class (priv->value_gadget, GTK_STYLE_CLASS_BOTTOM);
1292     }
1293 }
1294 
1295 /**
1296  * gtk_scale_set_draw_value:
1297  * @scale: a #GtkScale
1298  * @draw_value: %TRUE to draw the value
1299  *
1300  * Specifies whether the current value is displayed as a string next
1301  * to the slider.
1302  */
1303 void
gtk_scale_set_draw_value(GtkScale * scale,gboolean draw_value)1304 gtk_scale_set_draw_value (GtkScale *scale,
1305 			  gboolean  draw_value)
1306 {
1307   GtkScalePrivate *priv;
1308   GtkWidget *widget;
1309 
1310   g_return_if_fail (GTK_IS_SCALE (scale));
1311 
1312   priv = scale->priv;
1313   widget = GTK_WIDGET (scale);
1314 
1315   draw_value = draw_value != FALSE;
1316 
1317   if (priv->draw_value != draw_value)
1318     {
1319       priv->draw_value = draw_value;
1320       if (draw_value)
1321         {
1322           GtkCssNode *widget_node;
1323 
1324           widget_node = gtk_widget_get_css_node (widget);
1325           priv->value_gadget = gtk_css_custom_gadget_new ("value",
1326                                                           widget, NULL, NULL,
1327                                                           gtk_scale_measure_value,
1328                                                           NULL,
1329                                                           gtk_scale_render_value,
1330                                                           NULL, NULL);
1331           g_signal_connect (gtk_css_gadget_get_node (priv->value_gadget), "style-changed",
1332                             G_CALLBACK (gtk_scale_value_style_changed), scale);
1333 
1334           if (priv->value_pos == GTK_POS_TOP || priv->value_pos == GTK_POS_LEFT)
1335             gtk_css_node_insert_after (widget_node, gtk_css_gadget_get_node (priv->value_gadget), NULL);
1336           else
1337             gtk_css_node_insert_before (widget_node, gtk_css_gadget_get_node (priv->value_gadget), NULL);
1338 
1339           gtk_range_set_round_digits (GTK_RANGE (scale), priv->digits);
1340           update_value_position (scale);
1341         }
1342       else
1343         {
1344           if (priv->value_gadget)
1345             gtk_css_node_set_parent (gtk_css_gadget_get_node (priv->value_gadget), NULL);
1346           g_clear_object (&priv->value_gadget);
1347 
1348           gtk_range_set_round_digits (GTK_RANGE (scale), -1);
1349         }
1350 
1351       gtk_scale_clear_value_layout (scale);
1352 
1353       gtk_widget_queue_resize (widget);
1354 
1355       g_object_notify_by_pspec (G_OBJECT (scale), properties[PROP_DRAW_VALUE]);
1356     }
1357 }
1358 
1359 /**
1360  * gtk_scale_get_draw_value:
1361  * @scale: a #GtkScale
1362  *
1363  * Returns whether the current value is displayed as a string
1364  * next to the slider.
1365  *
1366  * Returns: whether the current value is displayed as a string
1367  */
1368 gboolean
gtk_scale_get_draw_value(GtkScale * scale)1369 gtk_scale_get_draw_value (GtkScale *scale)
1370 {
1371   g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE);
1372 
1373   return scale->priv->draw_value;
1374 }
1375 
1376 /**
1377  * gtk_scale_set_has_origin:
1378  * @scale: a #GtkScale
1379  * @has_origin: %TRUE if the scale has an origin
1380  *
1381  * If #GtkScale:has-origin is set to %TRUE (the default), the scale will
1382  * highlight the part of the trough between the origin (bottom or left side)
1383  * and the current value.
1384  *
1385  * Since: 3.4
1386  */
1387 void
gtk_scale_set_has_origin(GtkScale * scale,gboolean has_origin)1388 gtk_scale_set_has_origin (GtkScale *scale,
1389                           gboolean  has_origin)
1390 {
1391   g_return_if_fail (GTK_IS_SCALE (scale));
1392 
1393   has_origin = has_origin != FALSE;
1394 
1395   if (_gtk_range_get_has_origin (GTK_RANGE (scale)) != has_origin)
1396     {
1397       _gtk_range_set_has_origin (GTK_RANGE (scale), has_origin);
1398 
1399       gtk_widget_queue_draw (GTK_WIDGET (scale));
1400 
1401       g_object_notify_by_pspec (G_OBJECT (scale), properties[PROP_HAS_ORIGIN]);
1402     }
1403 }
1404 
1405 /**
1406  * gtk_scale_get_has_origin:
1407  * @scale: a #GtkScale
1408  *
1409  * Returns whether the scale has an origin.
1410  *
1411  * Returns: %TRUE if the scale has an origin.
1412  *
1413  * Since: 3.4
1414  */
1415 gboolean
gtk_scale_get_has_origin(GtkScale * scale)1416 gtk_scale_get_has_origin (GtkScale *scale)
1417 {
1418   g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE);
1419 
1420   return _gtk_range_get_has_origin (GTK_RANGE (scale));
1421 }
1422 
1423 /**
1424  * gtk_scale_set_value_pos:
1425  * @scale: a #GtkScale
1426  * @pos: the position in which the current value is displayed
1427  *
1428  * Sets the position in which the current value is displayed.
1429  */
1430 void
gtk_scale_set_value_pos(GtkScale * scale,GtkPositionType pos)1431 gtk_scale_set_value_pos (GtkScale        *scale,
1432 			 GtkPositionType  pos)
1433 {
1434   GtkScalePrivate *priv;
1435   GtkWidget *widget;
1436 
1437   g_return_if_fail (GTK_IS_SCALE (scale));
1438 
1439   priv = scale->priv;
1440 
1441   if (priv->value_pos != pos)
1442     {
1443       priv->value_pos = pos;
1444       widget = GTK_WIDGET (scale);
1445 
1446       gtk_scale_clear_value_layout (scale);
1447       update_value_position (scale);
1448 
1449       if (gtk_widget_get_visible (widget) && gtk_widget_get_mapped (widget))
1450 	gtk_widget_queue_resize (widget);
1451 
1452       g_object_notify_by_pspec (G_OBJECT (scale), properties[PROP_VALUE_POS]);
1453     }
1454 }
1455 
1456 /**
1457  * gtk_scale_get_value_pos:
1458  * @scale: a #GtkScale
1459  *
1460  * Gets the position in which the current value is displayed.
1461  *
1462  * Returns: the position in which the current value is displayed
1463  */
1464 GtkPositionType
gtk_scale_get_value_pos(GtkScale * scale)1465 gtk_scale_get_value_pos (GtkScale *scale)
1466 {
1467   g_return_val_if_fail (GTK_IS_SCALE (scale), 0);
1468 
1469   return scale->priv->value_pos;
1470 }
1471 
1472 static void
gtk_scale_get_range_border(GtkRange * range,GtkBorder * border)1473 gtk_scale_get_range_border (GtkRange  *range,
1474                             GtkBorder *border)
1475 {
1476   GtkScalePrivate *priv;
1477   GtkScale *scale;
1478 
1479   scale = GTK_SCALE (range);
1480   priv = scale->priv;
1481 
1482   border->left = 0;
1483   border->right = 0;
1484   border->top = 0;
1485   border->bottom = 0;
1486 
1487   if (priv->value_gadget)
1488     {
1489       int value_size;
1490       GtkOrientation value_orientation;
1491 
1492       if (priv->value_pos == GTK_POS_LEFT || priv->value_pos == GTK_POS_RIGHT)
1493         value_orientation = GTK_ORIENTATION_HORIZONTAL;
1494       else
1495         value_orientation = GTK_ORIENTATION_VERTICAL;
1496 
1497       gtk_css_gadget_get_preferred_size (priv->value_gadget,
1498                                          value_orientation, -1,
1499                                          &value_size, NULL,
1500                                          NULL, NULL);
1501 
1502       switch (priv->value_pos)
1503         {
1504         case GTK_POS_LEFT:
1505           border->left += value_size;
1506           break;
1507         case GTK_POS_RIGHT:
1508           border->right += value_size;
1509           break;
1510         case GTK_POS_TOP:
1511           border->top += value_size;
1512           break;
1513         case GTK_POS_BOTTOM:
1514           border->bottom += value_size;
1515           break;
1516         }
1517     }
1518 
1519   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL)
1520     {
1521       int height;
1522 
1523       if (priv->top_marks_gadget)
1524         {
1525           gtk_css_gadget_get_preferred_size (priv->top_marks_gadget,
1526                                              GTK_ORIENTATION_VERTICAL, -1,
1527                                              &height, NULL,
1528                                              NULL, NULL);
1529           if (height > 0)
1530             border->top += height;
1531         }
1532 
1533       if (priv->bottom_marks_gadget)
1534         {
1535           gtk_css_gadget_get_preferred_size (priv->bottom_marks_gadget,
1536                                              GTK_ORIENTATION_VERTICAL, -1,
1537                                              &height, NULL,
1538                                              NULL, NULL);
1539           if (height > 0)
1540             border->bottom += height;
1541         }
1542     }
1543   else
1544     {
1545       int width;
1546 
1547       if (priv->top_marks_gadget)
1548         {
1549           gtk_css_gadget_get_preferred_size (priv->top_marks_gadget,
1550                                              GTK_ORIENTATION_HORIZONTAL, -1,
1551                                              &width, NULL,
1552                                              NULL, NULL);
1553           if (width > 0)
1554             border->left += width;
1555         }
1556 
1557       if (priv->bottom_marks_gadget)
1558         {
1559           gtk_css_gadget_get_preferred_size (priv->bottom_marks_gadget,
1560                                              GTK_ORIENTATION_HORIZONTAL, -1,
1561                                              &width, NULL,
1562                                              NULL, NULL);
1563           if (width > 0)
1564             border->right += width;
1565         }
1566     }
1567 }
1568 
1569 static void
gtk_scale_get_range_size_request(GtkRange * range,GtkOrientation orientation,gint * minimum,gint * natural)1570 gtk_scale_get_range_size_request (GtkRange       *range,
1571                                   GtkOrientation  orientation,
1572                                   gint           *minimum,
1573                                   gint           *natural)
1574 {
1575   GtkScalePrivate *priv = GTK_SCALE (range)->priv;
1576 
1577   /* Ensure the range requests enough size for our value */
1578   if (priv->value_gadget)
1579     gtk_css_gadget_get_preferred_size (priv->value_gadget,
1580                                        orientation, -1,
1581                                        minimum, natural,
1582                                        NULL, NULL);
1583   else
1584     {
1585       *minimum = 0;
1586       *natural = 0;
1587     }
1588 }
1589 
1590 static void
gtk_scale_value_style_changed(GtkCssNode * node,GtkCssStyleChange * change,GtkScale * scale)1591 gtk_scale_value_style_changed (GtkCssNode        *node,
1592                                GtkCssStyleChange *change,
1593                                GtkScale          *scale)
1594 {
1595   if (change == NULL ||
1596       gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_TEXT_ATTRS) ||
1597       gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_FONT))
1598     {
1599       gtk_scale_clear_value_layout (scale);
1600       gtk_widget_queue_resize (GTK_WIDGET (scale));
1601     }
1602 }
1603 
1604 static void
gtk_scale_mark_style_changed(GtkCssNode * node,GtkCssStyleChange * change,GtkScaleMark * mark)1605 gtk_scale_mark_style_changed (GtkCssNode        *node,
1606                               GtkCssStyleChange *change,
1607                               GtkScaleMark      *mark)
1608 {
1609   if (change == NULL ||
1610       gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_TEXT_ATTRS) ||
1611       gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_FONT))
1612     {
1613       GtkCssNode *widget_node;
1614       GtkWidget *scale;
1615 
1616       g_clear_object (&mark->layout);
1617 
1618       widget_node = gtk_css_node_get_parent (gtk_css_node_get_parent (gtk_css_node_get_parent (node)));
1619       scale = gtk_css_widget_node_get_widget (GTK_CSS_WIDGET_NODE (widget_node));
1620       gtk_widget_queue_resize (GTK_WIDGET (scale));
1621     }
1622 }
1623 
1624 static void
gtk_scale_screen_changed(GtkWidget * widget,GdkScreen * old_screen)1625 gtk_scale_screen_changed (GtkWidget *widget,
1626                           GdkScreen *old_screen)
1627 {
1628   gtk_scale_clear_value_layout (GTK_SCALE (widget));
1629   gtk_scale_clear_mark_layouts (GTK_SCALE (widget));
1630 }
1631 
1632 static void
gtk_scale_measure_mark_label(GtkCssGadget * gadget,GtkOrientation orientation,gint for_size,gint * minimum,gint * natural,gint * minimum_baseline,gint * natural_baseline,gpointer user_data)1633 gtk_scale_measure_mark_label (GtkCssGadget   *gadget,
1634                               GtkOrientation  orientation,
1635                               gint            for_size,
1636                               gint           *minimum,
1637                               gint           *natural,
1638                               gint           *minimum_baseline,
1639                               gint           *natural_baseline,
1640                               gpointer        user_data)
1641 {
1642   GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
1643   GtkScaleMark *mark = user_data;
1644   PangoRectangle logical_rect;
1645 
1646   *minimum = *natural = 0;
1647 
1648   if (!mark->layout)
1649     {
1650       mark->layout = gtk_widget_create_pango_layout (widget, NULL);
1651       pango_layout_set_markup (mark->layout, mark->markup, -1);
1652       gtk_css_node_update_layout_attributes (gtk_css_gadget_get_node (gadget), mark->layout);
1653     }
1654 
1655   pango_layout_get_pixel_extents (mark->layout, NULL, &logical_rect);
1656 
1657   if (orientation == GTK_ORIENTATION_HORIZONTAL)
1658     *minimum = *natural = logical_rect.width;
1659   else
1660     *minimum = *natural = logical_rect.height;
1661 }
1662 
1663 static void
gtk_scale_measure_mark(GtkCssGadget * gadget,GtkOrientation orientation,gint for_size,gint * minimum,gint * natural,gint * minimum_baseline,gint * natural_baseline,gpointer user_data)1664 gtk_scale_measure_mark (GtkCssGadget   *gadget,
1665                         GtkOrientation  orientation,
1666                         gint            for_size,
1667                         gint           *minimum,
1668                         gint           *natural,
1669                         gint           *minimum_baseline,
1670                         gint           *natural_baseline,
1671                         gpointer        user_data)
1672 {
1673   GtkScaleMark *mark = user_data;
1674 
1675   gtk_css_gadget_get_preferred_size (mark->indicator_gadget,
1676                                      orientation, -1,
1677                                      minimum, natural,
1678                                      NULL, NULL);
1679 
1680   if (mark->label_gadget)
1681     {
1682       int label_min, label_nat;
1683 
1684       gtk_css_gadget_get_preferred_size (mark->label_gadget,
1685                                          orientation, -1,
1686                                          &label_min, &label_nat,
1687                                          NULL, NULL);
1688       *minimum += label_min;
1689       *natural += label_nat;
1690     }
1691 }
1692 
1693 static void
gtk_scale_measure_marks(GtkCssGadget * gadget,GtkOrientation orientation,gint for_size,gint * minimum,gint * natural,gint * minimum_baseline,gint * natural_baseline,gpointer user_data)1694 gtk_scale_measure_marks (GtkCssGadget   *gadget,
1695                          GtkOrientation  orientation,
1696                          gint            for_size,
1697                          gint           *minimum,
1698                          gint           *natural,
1699                          gint           *minimum_baseline,
1700                          gint           *natural_baseline,
1701                          gpointer        user_data)
1702 {
1703   GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
1704   GtkScale *scale = GTK_SCALE (widget);
1705   GtkScalePrivate *priv = scale->priv;
1706   GtkOrientation scale_orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (scale));
1707   GSList *m;
1708 
1709   *minimum = *natural = 0;
1710 
1711   for (m = priv->marks; m; m = m->next)
1712     {
1713       GtkScaleMark *mark = m->data;
1714       int mark_size;
1715 
1716       if ((mark->position == GTK_POS_TOP && gadget == priv->bottom_marks_gadget) ||
1717           (mark->position == GTK_POS_BOTTOM && gadget == priv->top_marks_gadget))
1718         continue;
1719 
1720       gtk_css_gadget_get_preferred_size (mark->gadget,
1721                                          orientation, -1,
1722                                          &mark_size, NULL,
1723                                          NULL, NULL);
1724 
1725       if (scale_orientation == orientation)
1726         {
1727           *minimum += mark_size;
1728           *natural += mark_size;
1729         }
1730       else
1731         {
1732           *minimum = MAX (*minimum, mark_size);
1733           *natural = MAX (*natural, mark_size);
1734         }
1735     }
1736 }
1737 
1738 static void
gtk_scale_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)1739 gtk_scale_get_preferred_width (GtkWidget *widget,
1740                                gint      *minimum,
1741                                gint      *natural)
1742 {
1743   GtkScale *scale = GTK_SCALE (widget);
1744   GtkScalePrivate *priv = scale->priv;
1745 
1746   GTK_WIDGET_CLASS (gtk_scale_parent_class)->get_preferred_width (widget, minimum, natural);
1747 
1748   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_HORIZONTAL)
1749     {
1750       int top_marks_width = 0, bottom_marks_width = 0, marks_width;
1751 
1752       if (priv->top_marks_gadget)
1753         gtk_css_gadget_get_preferred_size (priv->top_marks_gadget,
1754                                            GTK_ORIENTATION_HORIZONTAL, -1,
1755                                            &top_marks_width, NULL,
1756                                            NULL, NULL);
1757       if (priv->bottom_marks_gadget)
1758         gtk_css_gadget_get_preferred_size (priv->bottom_marks_gadget,
1759                                            GTK_ORIENTATION_HORIZONTAL, -1,
1760                                            &bottom_marks_width, NULL,
1761                                            NULL, NULL);
1762 
1763       marks_width = MAX (top_marks_width, bottom_marks_width);
1764 
1765       *minimum = MAX (*minimum, marks_width);
1766       *natural = MAX (*natural, marks_width);
1767     }
1768 }
1769 
1770 static void
gtk_scale_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)1771 gtk_scale_get_preferred_height (GtkWidget *widget,
1772                                 gint      *minimum,
1773                                 gint      *natural)
1774 {
1775   GtkScale *scale = GTK_SCALE (widget);
1776   GtkScalePrivate *priv = scale->priv;
1777 
1778   GTK_WIDGET_CLASS (gtk_scale_parent_class)->get_preferred_height (widget, minimum, natural);
1779 
1780   if (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_VERTICAL)
1781     {
1782       int top_marks_height = 0, bottom_marks_height = 0, marks_height;
1783 
1784       if (priv->top_marks_gadget)
1785         gtk_css_gadget_get_preferred_size (priv->top_marks_gadget,
1786                                            GTK_ORIENTATION_VERTICAL, -1,
1787                                            &top_marks_height, NULL,
1788                                            NULL, NULL);
1789       if (priv->bottom_marks_gadget)
1790         gtk_css_gadget_get_preferred_size (priv->bottom_marks_gadget,
1791                                            GTK_ORIENTATION_VERTICAL, -1,
1792                                            &bottom_marks_height, NULL,
1793                                            NULL, NULL);
1794 
1795       marks_height = MAX (top_marks_height, bottom_marks_height);
1796 
1797       *minimum = MAX (*minimum, marks_height);
1798       *natural = MAX (*natural, marks_height);
1799     }
1800 }
1801 
1802 static gboolean
gtk_scale_render_mark_indicator(GtkCssGadget * gadget,cairo_t * cr,int x,int y,int width,int height,gpointer user_data)1803 gtk_scale_render_mark_indicator (GtkCssGadget *gadget,
1804                                  cairo_t      *cr,
1805                                  int           x,
1806                                  int           y,
1807                                  int           width,
1808                                  int           height,
1809                                  gpointer      user_data)
1810 {
1811   GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
1812   GtkStyleContext *context;
1813   GtkOrientation orientation;
1814 
1815   orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
1816   context = gtk_widget_get_style_context (widget);
1817   gtk_style_context_save_to_node (context, gtk_css_gadget_get_node (gadget));
1818 
1819   if (orientation == GTK_ORIENTATION_HORIZONTAL)
1820     gtk_render_line (context, cr,
1821                      x + width / 2, y,
1822                      x + width / 2, y + height);
1823   else
1824     gtk_render_line (context, cr,
1825                      x, y + height / 2,
1826                      x + width, y + height / 2);
1827 
1828   gtk_style_context_restore (context);
1829 
1830   return FALSE;
1831 }
1832 
1833 static gboolean
gtk_scale_render_mark_label(GtkCssGadget * gadget,cairo_t * cr,int x,int y,int width,int height,gpointer user_data)1834 gtk_scale_render_mark_label (GtkCssGadget *gadget,
1835                              cairo_t      *cr,
1836                              int           x,
1837                              int           y,
1838                              int           width,
1839                              int           height,
1840                              gpointer      user_data)
1841 {
1842   GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
1843   GtkScaleMark *mark = user_data;
1844   GtkStyleContext *context;
1845 
1846   context = gtk_widget_get_style_context (widget);
1847   gtk_style_context_save_to_node (context, gtk_css_gadget_get_node (gadget));
1848   gtk_css_node_update_layout_attributes (gtk_css_gadget_get_node (gadget), mark->layout);
1849 
1850   gtk_render_layout (context, cr, x, y, mark->layout);
1851 
1852   gtk_style_context_restore (context);
1853 
1854   return FALSE;
1855 }
1856 
1857 static gboolean
gtk_scale_render_mark(GtkCssGadget * gadget,cairo_t * cr,int x,int y,int width,int height,gpointer user_data)1858 gtk_scale_render_mark (GtkCssGadget *gadget,
1859                        cairo_t      *cr,
1860                        int           x,
1861                        int           y,
1862                        int           width,
1863                        int           height,
1864                        gpointer      user_data)
1865 {
1866   GtkScaleMark *mark = user_data;
1867 
1868   gtk_css_gadget_draw (mark->indicator_gadget, cr);
1869   if (mark->label_gadget)
1870     gtk_css_gadget_draw (mark->label_gadget, cr);
1871 
1872   return FALSE;
1873 }
1874 
1875 static gboolean
gtk_scale_render_marks(GtkCssGadget * gadget,cairo_t * cr,int x,int y,int width,int height,gpointer user_data)1876 gtk_scale_render_marks (GtkCssGadget *gadget,
1877                         cairo_t      *cr,
1878                         int           x,
1879                         int           y,
1880                         int           width,
1881                         int           height,
1882                         gpointer      user_data)
1883 {
1884   GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
1885   GtkScale *scale = GTK_SCALE (widget);
1886   GtkScalePrivate *priv = scale->priv;
1887   GSList *m;
1888 
1889   for (m = priv->marks; m; m = m->next)
1890     {
1891       GtkScaleMark *mark = m->data;
1892 
1893       if ((mark->position == GTK_POS_TOP && gadget == priv->bottom_marks_gadget) ||
1894           (mark->position == GTK_POS_BOTTOM && gadget == priv->top_marks_gadget))
1895         continue;
1896 
1897       gtk_css_gadget_draw (mark->gadget, cr);
1898     }
1899 
1900   return FALSE;
1901 }
1902 
1903 static gboolean
gtk_scale_draw(GtkWidget * widget,cairo_t * cr)1904 gtk_scale_draw (GtkWidget *widget,
1905                 cairo_t   *cr)
1906 {
1907   GtkScale *scale = GTK_SCALE (widget);
1908   GtkScalePrivate *priv = scale->priv;
1909 
1910   if (priv->top_marks_gadget)
1911     gtk_css_gadget_draw (priv->top_marks_gadget, cr);
1912   if (priv->bottom_marks_gadget)
1913     gtk_css_gadget_draw (priv->bottom_marks_gadget, cr);
1914 
1915   GTK_WIDGET_CLASS (gtk_scale_parent_class)->draw (widget, cr);
1916 
1917   if (priv->value_gadget)
1918     gtk_css_gadget_draw (priv->value_gadget, cr);
1919 
1920   return FALSE;
1921 }
1922 
1923 static void
gtk_scale_real_get_layout_offsets(GtkScale * scale,gint * x,gint * y)1924 gtk_scale_real_get_layout_offsets (GtkScale *scale,
1925                                    gint     *x,
1926                                    gint     *y)
1927 {
1928   GtkScalePrivate *priv = scale->priv;
1929   GtkAllocation value_alloc;
1930 
1931   if (!priv->value_gadget)
1932     {
1933       *x = 0;
1934       *y = 0;
1935 
1936       return;
1937     }
1938 
1939   gtk_css_gadget_get_content_allocation (priv->value_gadget, &value_alloc, NULL);
1940 
1941   *x = value_alloc.x;
1942   *y = value_alloc.y;
1943 }
1944 
1945 static gchar *
weed_out_neg_zero(gchar * str,gint digits)1946 weed_out_neg_zero (gchar *str,
1947                    gint   digits)
1948 {
1949   if (str[0] == '-')
1950     {
1951       gchar neg_zero[8];
1952       g_snprintf (neg_zero, 8, "%0.*f", digits, -0.0);
1953       if (strcmp (neg_zero, str) == 0)
1954         memmove (str, str + 1, strlen (str));
1955     }
1956   return str;
1957 }
1958 
1959 /*
1960  * Emits #GtkScale:format-value signal to format the value;
1961  * if no user signal handlers, falls back to a default format.
1962  *
1963  * Returns: formatted value
1964  */
1965 static gchar *
gtk_scale_format_value(GtkScale * scale,gdouble value)1966 gtk_scale_format_value (GtkScale *scale,
1967                         gdouble   value)
1968 {
1969   gchar *fmt = NULL;
1970 
1971   g_signal_emit (scale, signals[FORMAT_VALUE], 0, value, &fmt);
1972 
1973   if (fmt)
1974     return fmt;
1975   else
1976     {
1977       fmt = g_strdup_printf ("%0.*f", scale->priv->digits, value);
1978       return weed_out_neg_zero (fmt, scale->priv->digits);
1979     }
1980 }
1981 
1982 static void
gtk_scale_finalize(GObject * object)1983 gtk_scale_finalize (GObject *object)
1984 {
1985   GtkScale *scale = GTK_SCALE (object);
1986   GtkScalePrivate *priv = scale->priv;
1987 
1988   gtk_scale_clear_value_layout (scale);
1989   gtk_scale_clear_marks (scale);
1990 
1991   if (priv->value_gadget)
1992     gtk_css_node_set_parent (gtk_css_gadget_get_node (priv->value_gadget), NULL);
1993   g_clear_object (&priv->value_gadget);
1994 
1995   G_OBJECT_CLASS (gtk_scale_parent_class)->finalize (object);
1996 }
1997 
1998 /**
1999  * gtk_scale_get_layout:
2000  * @scale: A #GtkScale
2001  *
2002  * Gets the #PangoLayout used to display the scale. The returned
2003  * object is owned by the scale so does not need to be freed by
2004  * the caller.
2005  *
2006  * Returns: (transfer none) (nullable): the #PangoLayout for this scale,
2007  *     or %NULL if the #GtkScale:draw-value property is %FALSE.
2008  *
2009  * Since: 2.4
2010  */
2011 PangoLayout *
gtk_scale_get_layout(GtkScale * scale)2012 gtk_scale_get_layout (GtkScale *scale)
2013 {
2014   GtkScalePrivate *priv;
2015   gchar *txt;
2016 
2017   g_return_val_if_fail (GTK_IS_SCALE (scale), NULL);
2018 
2019   priv = scale->priv;
2020 
2021   if (!priv->layout && priv->draw_value)
2022     {
2023       PangoLayout *layout;
2024       int min_layout_width;
2025 
2026       layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
2027       gtk_css_node_update_layout_attributes (gtk_css_gadget_get_node (priv->value_gadget), layout);
2028       gtk_css_gadget_get_preferred_size (priv->value_gadget,
2029                                          GTK_ORIENTATION_HORIZONTAL, -1,
2030                                          &min_layout_width, NULL,
2031                                          NULL, NULL);
2032 
2033       pango_layout_set_width (layout, min_layout_width * PANGO_SCALE);
2034 
2035       if (priv->value_pos == GTK_POS_LEFT)
2036         pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
2037       else if (priv->value_pos == GTK_POS_RIGHT)
2038         pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
2039       else
2040         pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
2041 
2042       priv->layout = layout;
2043     }
2044 
2045   if (priv->draw_value)
2046     {
2047       txt = gtk_scale_format_value (scale,
2048                                     gtk_adjustment_get_value (gtk_range_get_adjustment (GTK_RANGE (scale))));
2049       pango_layout_set_text (priv->layout, txt, -1);
2050       g_free (txt);
2051     }
2052 
2053   return priv->layout;
2054 }
2055 
2056 /**
2057  * gtk_scale_get_layout_offsets:
2058  * @scale: a #GtkScale
2059  * @x: (out) (allow-none): location to store X offset of layout, or %NULL
2060  * @y: (out) (allow-none): location to store Y offset of layout, or %NULL
2061  *
2062  * Obtains the coordinates where the scale will draw the
2063  * #PangoLayout representing the text in the scale. Remember
2064  * when using the #PangoLayout function you need to convert to
2065  * and from pixels using PANGO_PIXELS() or #PANGO_SCALE.
2066  *
2067  * If the #GtkScale:draw-value property is %FALSE, the return
2068  * values are undefined.
2069  *
2070  * Since: 2.4
2071  */
2072 void
gtk_scale_get_layout_offsets(GtkScale * scale,gint * x,gint * y)2073 gtk_scale_get_layout_offsets (GtkScale *scale,
2074                               gint     *x,
2075                               gint     *y)
2076 {
2077   gint local_x = 0;
2078   gint local_y = 0;
2079 
2080   g_return_if_fail (GTK_IS_SCALE (scale));
2081 
2082   if (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets)
2083     (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets) (scale, &local_x, &local_y);
2084 
2085   if (x)
2086     *x = local_x;
2087 
2088   if (y)
2089     *y = local_y;
2090 }
2091 
2092 static void
gtk_scale_clear_value_layout(GtkScale * scale)2093 gtk_scale_clear_value_layout (GtkScale *scale)
2094 {
2095   g_set_object (&scale->priv->layout, NULL);
2096 }
2097 
2098 static void
gtk_scale_mark_free(gpointer data)2099 gtk_scale_mark_free (gpointer data)
2100 {
2101   GtkScaleMark *mark = data;
2102 
2103   if (mark->label_gadget)
2104     gtk_css_node_set_parent (gtk_css_gadget_get_node (mark->label_gadget), NULL);
2105   g_clear_object (&mark->label_gadget);
2106   gtk_css_node_set_parent (gtk_css_gadget_get_node (mark->indicator_gadget), NULL);
2107   g_object_unref (mark->indicator_gadget);
2108   gtk_css_node_set_parent (gtk_css_gadget_get_node (mark->gadget), NULL);
2109   g_object_unref (mark->gadget);
2110   g_clear_object (&mark->layout);
2111   g_free (mark->markup);
2112   g_free (mark);
2113 }
2114 
2115 static void
gtk_scale_clear_mark_layouts(GtkScale * scale)2116 gtk_scale_clear_mark_layouts (GtkScale *scale)
2117 {
2118   GSList *m;
2119 
2120   for (m = scale->priv->marks; m; m = m->next)
2121     {
2122       GtkScaleMark *mark = m->data;
2123       g_clear_object (&mark->layout);
2124     }
2125 }
2126 
2127 /**
2128  * gtk_scale_clear_marks:
2129  * @scale: a #GtkScale
2130  *
2131  * Removes any marks that have been added with gtk_scale_add_mark().
2132  *
2133  * Since: 2.16
2134  */
2135 void
gtk_scale_clear_marks(GtkScale * scale)2136 gtk_scale_clear_marks (GtkScale *scale)
2137 {
2138   GtkScalePrivate *priv;
2139   GtkStyleContext *context;
2140 
2141   g_return_if_fail (GTK_IS_SCALE (scale));
2142 
2143   priv = scale->priv;
2144 
2145   g_slist_free_full (priv->marks, gtk_scale_mark_free);
2146   priv->marks = NULL;
2147 
2148   if (priv->top_marks_gadget)
2149     gtk_css_node_set_parent (gtk_css_gadget_get_node (priv->top_marks_gadget), NULL);
2150   g_clear_object (&priv->top_marks_gadget);
2151   if (priv->bottom_marks_gadget)
2152     gtk_css_node_set_parent (gtk_css_gadget_get_node (priv->bottom_marks_gadget), NULL);
2153   g_clear_object (&priv->bottom_marks_gadget);
2154 
2155   context = gtk_widget_get_style_context (GTK_WIDGET (scale));
2156   gtk_style_context_remove_class (context, "marks-before");
2157   gtk_style_context_remove_class (context, "marks-after");
2158 
2159   _gtk_range_set_stop_values (GTK_RANGE (scale), NULL, 0);
2160 
2161   gtk_widget_queue_resize (GTK_WIDGET (scale));
2162 }
2163 
2164 /**
2165  * gtk_scale_add_mark:
2166  * @scale: a #GtkScale
2167  * @value: the value at which the mark is placed, must be between
2168  *   the lower and upper limits of the scales’ adjustment
2169  * @position: where to draw the mark. For a horizontal scale, #GTK_POS_TOP
2170  *   and %GTK_POS_LEFT are drawn above the scale, anything else below.
2171  *   For a vertical scale, #GTK_POS_LEFT and %GTK_POS_TOP are drawn to
2172  *   the left of the scale, anything else to the right.
2173  * @markup: (allow-none): Text to be shown at the mark, using [Pango markup][PangoMarkupFormat], or %NULL
2174  *
2175  *
2176  * Adds a mark at @value.
2177  *
2178  * A mark is indicated visually by drawing a tick mark next to the scale,
2179  * and GTK+ makes it easy for the user to position the scale exactly at the
2180  * marks value.
2181  *
2182  * If @markup is not %NULL, text is shown next to the tick mark.
2183  *
2184  * To remove marks from a scale, use gtk_scale_clear_marks().
2185  *
2186  * Since: 2.16
2187  */
2188 void
gtk_scale_add_mark(GtkScale * scale,gdouble value,GtkPositionType position,const gchar * markup)2189 gtk_scale_add_mark (GtkScale        *scale,
2190                     gdouble          value,
2191                     GtkPositionType  position,
2192                     const gchar     *markup)
2193 {
2194   GtkWidget *widget;
2195   GtkScalePrivate *priv;
2196   GtkScaleMark *mark;
2197   GSList *m;
2198   gdouble *values;
2199   gint n, i;
2200   GtkCssNode *widget_node, *marks_node;
2201   GtkStyleContext *context;
2202 
2203   g_return_if_fail (GTK_IS_SCALE (scale));
2204 
2205   priv = scale->priv;
2206   widget = GTK_WIDGET (scale);
2207   widget_node = gtk_widget_get_css_node (widget);
2208 
2209   mark = g_new0 (GtkScaleMark, 1);
2210   mark->value = value;
2211   mark->markup = g_strdup (markup);
2212   if (position == GTK_POS_LEFT ||
2213       position == GTK_POS_TOP)
2214     mark->position = GTK_POS_TOP;
2215   else
2216     mark->position = GTK_POS_BOTTOM;
2217 
2218   priv->marks = g_slist_insert_sorted_with_data (priv->marks, mark,
2219                                                  compare_marks,
2220                                                  GINT_TO_POINTER (gtk_range_get_inverted (GTK_RANGE (scale))));
2221 
2222   if (mark->position == GTK_POS_TOP)
2223     {
2224       if (!priv->top_marks_gadget)
2225         {
2226           priv->top_marks_gadget =
2227             gtk_css_custom_gadget_new ("marks",
2228                                        widget, NULL, NULL,
2229                                        gtk_scale_measure_marks,
2230                                        gtk_scale_allocate_marks,
2231                                        gtk_scale_render_marks,
2232                                        NULL, NULL);
2233           gtk_css_node_insert_after (widget_node,
2234                                      gtk_css_gadget_get_node (priv->top_marks_gadget),
2235                                      (priv->value_gadget &&
2236                                       (priv->value_pos == GTK_POS_TOP || priv->value_pos == GTK_POS_LEFT)) ?
2237                                      gtk_css_gadget_get_node (priv->value_gadget) : NULL);
2238           gtk_css_gadget_add_class (priv->top_marks_gadget, GTK_STYLE_CLASS_TOP);
2239           gtk_css_gadget_set_state (priv->top_marks_gadget, gtk_css_node_get_state (widget_node));
2240         }
2241       marks_node = gtk_css_gadget_get_node (priv->top_marks_gadget);
2242     }
2243   else
2244     {
2245       if (!priv->bottom_marks_gadget)
2246         {
2247           priv->bottom_marks_gadget =
2248             gtk_css_custom_gadget_new ("marks",
2249                                        widget, NULL, NULL,
2250                                        gtk_scale_measure_marks,
2251                                        gtk_scale_allocate_marks,
2252                                        gtk_scale_render_marks,
2253                                        NULL, NULL);
2254           gtk_css_node_insert_before (widget_node,
2255                                       gtk_css_gadget_get_node (priv->bottom_marks_gadget),
2256                                       (priv->value_gadget &&
2257                                        (priv->value_pos == GTK_POS_BOTTOM || priv->value_pos == GTK_POS_RIGHT)) ?
2258                                       gtk_css_gadget_get_node (priv->value_gadget) : NULL);
2259           gtk_css_gadget_add_class (priv->bottom_marks_gadget, GTK_STYLE_CLASS_BOTTOM);
2260           gtk_css_gadget_set_state (priv->bottom_marks_gadget, gtk_css_node_get_state (widget_node));
2261         }
2262       marks_node = gtk_css_gadget_get_node (priv->bottom_marks_gadget);
2263     }
2264 
2265   mark->gadget =
2266     gtk_css_custom_gadget_new ("mark",
2267                                widget, NULL, NULL,
2268                                gtk_scale_measure_mark,
2269                                gtk_scale_allocate_mark,
2270                                gtk_scale_render_mark,
2271                                mark, NULL);
2272   gtk_css_gadget_set_state (mark->gadget, gtk_css_node_get_state (marks_node));
2273 
2274   mark->indicator_gadget =
2275     gtk_css_custom_gadget_new ("indicator",
2276                                widget, mark->gadget, NULL,
2277                                NULL,
2278                                NULL,
2279                                gtk_scale_render_mark_indicator,
2280                                mark, NULL);
2281   if (mark->markup && *mark->markup)
2282     {
2283       mark->label_gadget =
2284         gtk_css_custom_gadget_new ("label",
2285                                    widget, mark->gadget,
2286                                    mark->position == GTK_POS_TOP ?
2287                                    NULL : mark->indicator_gadget,
2288                                    gtk_scale_measure_mark_label,
2289                                    NULL,
2290                                    gtk_scale_render_mark_label,
2291                                    mark, NULL);
2292       g_signal_connect (gtk_css_gadget_get_node (mark->label_gadget), "style-changed",
2293                         G_CALLBACK (gtk_scale_mark_style_changed), mark);
2294     }
2295 
2296   m = g_slist_find (priv->marks, mark);
2297   m = m->next;
2298   while (m)
2299     {
2300       GtkScaleMark *next = m->data;
2301       if (next->position == mark->position)
2302         break;
2303       m = m->next;
2304     }
2305 
2306   if (m)
2307     {
2308       GtkScaleMark *next = m->data;
2309       gtk_css_node_insert_before (marks_node,
2310                                   gtk_css_gadget_get_node (mark->gadget),
2311                                   gtk_css_gadget_get_node (next->gadget));
2312     }
2313   else
2314     {
2315       gtk_css_node_set_parent (gtk_css_gadget_get_node (mark->gadget), marks_node);
2316     }
2317 
2318   n = g_slist_length (priv->marks);
2319   values = g_new (gdouble, n);
2320   for (m = priv->marks, i = 0; m; m = m->next, i++)
2321     {
2322       mark = m->data;
2323       values[i] = mark->value;
2324     }
2325 
2326   _gtk_range_set_stop_values (GTK_RANGE (scale), values, n);
2327 
2328   g_free (values);
2329 
2330   context = gtk_widget_get_style_context (GTK_WIDGET (scale));
2331   if (priv->top_marks_gadget)
2332     gtk_style_context_add_class (context, "marks-before");
2333   if (priv->bottom_marks_gadget)
2334     gtk_style_context_add_class (context, "marks-after");
2335 
2336   gtk_widget_queue_resize (widget);
2337 }
2338 
2339 static GtkBuildableIface *parent_buildable_iface;
2340 
2341 static void
gtk_scale_buildable_interface_init(GtkBuildableIface * iface)2342 gtk_scale_buildable_interface_init (GtkBuildableIface *iface)
2343 {
2344   parent_buildable_iface = g_type_interface_peek_parent (iface);
2345   iface->custom_tag_start = gtk_scale_buildable_custom_tag_start;
2346   iface->custom_finished = gtk_scale_buildable_custom_finished;
2347 }
2348 
2349 typedef struct
2350 {
2351   GtkScale *scale;
2352   GtkBuilder *builder;
2353   GSList *marks;
2354 } MarksSubparserData;
2355 
2356 typedef struct
2357 {
2358   gdouble value;
2359   GtkPositionType position;
2360   GString *markup;
2361   gchar *context;
2362   gboolean translatable;
2363 } MarkData;
2364 
2365 static void
mark_data_free(MarkData * data)2366 mark_data_free (MarkData *data)
2367 {
2368   g_string_free (data->markup, TRUE);
2369   g_free (data->context);
2370   g_slice_free (MarkData, data);
2371 }
2372 
2373 static void
marks_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)2374 marks_start_element (GMarkupParseContext *context,
2375                      const gchar         *element_name,
2376                      const gchar        **names,
2377                      const gchar        **values,
2378                      gpointer             user_data,
2379                      GError             **error)
2380 {
2381   MarksSubparserData *data = (MarksSubparserData*)user_data;
2382 
2383   if (strcmp (element_name, "marks") == 0)
2384     {
2385       if (!_gtk_builder_check_parent (data->builder, context, "object", error))
2386         return;
2387 
2388       if (!g_markup_collect_attributes (element_name, names, values, error,
2389                                         G_MARKUP_COLLECT_INVALID, NULL, NULL,
2390                                         G_MARKUP_COLLECT_INVALID))
2391         _gtk_builder_prefix_error (data->builder, context, error);
2392     }
2393   else if (strcmp (element_name, "mark") == 0)
2394     {
2395       const gchar *value_str;
2396       gdouble value = 0;
2397       const gchar *position_str = NULL;
2398       GtkPositionType position = GTK_POS_BOTTOM;
2399       const gchar *msg_context = NULL;
2400       gboolean translatable = FALSE;
2401       MarkData *mark;
2402 
2403       if (!_gtk_builder_check_parent (data->builder, context, "marks", error))
2404         return;
2405 
2406       if (!g_markup_collect_attributes (element_name, names, values, error,
2407                                         G_MARKUP_COLLECT_STRING, "value", &value_str,
2408                                         G_MARKUP_COLLECT_BOOLEAN|G_MARKUP_COLLECT_OPTIONAL, "translatable", &translatable,
2409                                         G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "comments", NULL,
2410                                         G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "context", &msg_context,
2411                                         G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "position", &position_str,
2412                                         G_MARKUP_COLLECT_INVALID))
2413         {
2414           _gtk_builder_prefix_error (data->builder, context, error);
2415           return;
2416         }
2417 
2418       if (value_str != NULL)
2419         {
2420           GValue gvalue = G_VALUE_INIT;
2421 
2422           if (!gtk_builder_value_from_string_type (data->builder, G_TYPE_DOUBLE, value_str, &gvalue, error))
2423             {
2424               _gtk_builder_prefix_error (data->builder, context, error);
2425               return;
2426             }
2427 
2428           value = g_value_get_double (&gvalue);
2429         }
2430 
2431       if (position_str != NULL)
2432         {
2433           GValue gvalue = G_VALUE_INIT;
2434 
2435           if (!gtk_builder_value_from_string_type (data->builder, GTK_TYPE_POSITION_TYPE, position_str, &gvalue, error))
2436             {
2437               _gtk_builder_prefix_error (data->builder, context, error);
2438               return;
2439             }
2440 
2441           position = g_value_get_enum (&gvalue);
2442         }
2443 
2444       mark = g_slice_new (MarkData);
2445       mark->value = value;
2446       if (position == GTK_POS_LEFT || position == GTK_POS_TOP)
2447         mark->position = GTK_POS_TOP;
2448       else
2449         mark->position = GTK_POS_BOTTOM;
2450       mark->markup = g_string_new ("");
2451       mark->context = g_strdup (msg_context);
2452       mark->translatable = translatable;
2453 
2454       data->marks = g_slist_prepend (data->marks, mark);
2455     }
2456   else
2457     {
2458       _gtk_builder_error_unhandled_tag (data->builder, context,
2459                                         "GtkScale", element_name,
2460                                         error);
2461     }
2462 }
2463 
2464 static void
marks_text(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)2465 marks_text (GMarkupParseContext  *context,
2466             const gchar          *text,
2467             gsize                 text_len,
2468             gpointer              user_data,
2469             GError              **error)
2470 {
2471   MarksSubparserData *data = (MarksSubparserData*)user_data;
2472 
2473   if (strcmp (g_markup_parse_context_get_element (context), "mark") == 0)
2474     {
2475       MarkData *mark = data->marks->data;
2476 
2477       g_string_append_len (mark->markup, text, text_len);
2478     }
2479 }
2480 
2481 static const GMarkupParser marks_parser =
2482   {
2483     marks_start_element,
2484     NULL,
2485     marks_text,
2486   };
2487 
2488 
2489 static gboolean
gtk_scale_buildable_custom_tag_start(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * tagname,GMarkupParser * parser,gpointer * parser_data)2490 gtk_scale_buildable_custom_tag_start (GtkBuildable  *buildable,
2491                                       GtkBuilder    *builder,
2492                                       GObject       *child,
2493                                       const gchar   *tagname,
2494                                       GMarkupParser *parser,
2495                                       gpointer      *parser_data)
2496 {
2497   MarksSubparserData *data;
2498 
2499   if (child)
2500     return FALSE;
2501 
2502   if (strcmp (tagname, "marks") == 0)
2503     {
2504       data = g_slice_new0 (MarksSubparserData);
2505       data->scale = GTK_SCALE (buildable);
2506       data->builder = builder;
2507       data->marks = NULL;
2508 
2509       *parser = marks_parser;
2510       *parser_data = data;
2511 
2512       return TRUE;
2513     }
2514 
2515   return parent_buildable_iface->custom_tag_start (buildable, builder, child,
2516                                                    tagname, parser, parser_data);
2517 }
2518 
2519 static void
gtk_scale_buildable_custom_finished(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * tagname,gpointer user_data)2520 gtk_scale_buildable_custom_finished (GtkBuildable *buildable,
2521                                      GtkBuilder   *builder,
2522                                      GObject      *child,
2523                                      const gchar  *tagname,
2524                                      gpointer      user_data)
2525 {
2526   GtkScale *scale = GTK_SCALE (buildable);
2527   MarksSubparserData *marks_data;
2528 
2529   if (strcmp (tagname, "marks") == 0)
2530     {
2531       GSList *m;
2532       const gchar *markup;
2533 
2534       marks_data = (MarksSubparserData *)user_data;
2535 
2536       for (m = marks_data->marks; m; m = m->next)
2537         {
2538           MarkData *mdata = m->data;
2539 
2540           if (mdata->translatable && mdata->markup->len)
2541             markup = _gtk_builder_parser_translate (gtk_builder_get_translation_domain (builder),
2542                                                     mdata->context,
2543                                                     mdata->markup->str);
2544           else
2545             markup = mdata->markup->str;
2546 
2547           gtk_scale_add_mark (scale, mdata->value, mdata->position, markup);
2548 
2549           mark_data_free (mdata);
2550         }
2551 
2552       g_slist_free (marks_data->marks);
2553       g_slice_free (MarksSubparserData, marks_data);
2554     }
2555   else
2556     {
2557       parent_buildable_iface->custom_finished (buildable, builder, child,
2558 					       tagname, user_data);
2559     }
2560 
2561 }
2562