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 "gtkscale.h" |
29 | |
30 | #include "gtkadjustment.h" |
31 | #include "gtkbuildable.h" |
32 | #include "gtkbuilderprivate.h" |
33 | #include "gtkgizmoprivate.h" |
34 | #include "gtkicontheme.h" |
35 | #include "gtkintl.h" |
36 | #include "gtklabel.h" |
37 | #include "gtkmarshalers.h" |
38 | #include "gtkorientable.h" |
39 | #include "gtkprivate.h" |
40 | #include "gtkrangeprivate.h" |
41 | #include "gtktypebuiltins.h" |
42 | #include "gtkwidgetprivate.h" |
43 | |
44 | #include <math.h> |
45 | #include <stdlib.h> |
46 | |
47 | |
48 | /** |
49 | * GtkScale: |
50 | * |
51 | * A `GtkScale` is a slider control used to select a numeric value. |
52 | * |
53 | * ![An example GtkScale](scales.png) |
54 | * |
55 | * To use it, you’ll probably want to investigate the methods on its base |
56 | * class, [class@GtkRange], in addition to the methods for `GtkScale` itself. |
57 | * To set the value of a scale, you would normally use [method@Gtk.Range.set_value]. |
58 | * To detect changes to the value, you would normally use the |
59 | * [signal@Gtk.Range::value-changed] signal. |
60 | * |
61 | * Note that using the same upper and lower bounds for the `GtkScale` (through |
62 | * the `GtkRange` methods) will hide the slider itself. This is useful for |
63 | * applications that want to show an undeterminate value on the scale, without |
64 | * changing the layout of the application (such as movie or music players). |
65 | * |
66 | * # GtkScale as GtkBuildable |
67 | * |
68 | * `GtkScale` supports a custom <marks> element, which can contain multiple |
69 | * <mark\> elements. The “value” and “position” attributes have the same |
70 | * meaning as [method@Gtk.Scale.add_mark] parameters of the same name. If |
71 | * the element is not empty, its content is taken as the markup to show at |
72 | * the mark. It can be translated with the usual ”translatable” and |
73 | * “context” attributes. |
74 | * |
75 | * # CSS nodes |
76 | * |
77 | * ``` |
78 | * scale[.fine-tune][.marks-before][.marks-after] |
79 | * ├── [value][.top][.right][.bottom][.left] |
80 | * ├── marks.top |
81 | * │ ├── mark |
82 | * │ ┊ ├── [label] |
83 | * │ ┊ ╰── indicator |
84 | * ┊ ┊ |
85 | * │ ╰── mark |
86 | * ├── marks.bottom |
87 | * │ ├── mark |
88 | * │ ┊ ├── indicator |
89 | * │ ┊ ╰── [label] |
90 | * ┊ ┊ |
91 | * │ ╰── mark |
92 | * ╰── trough |
93 | * ├── [fill] |
94 | * ├── [highlight] |
95 | * ╰── slider |
96 | * ``` |
97 | * |
98 | * `GtkScale` has a main CSS node with name scale and a subnode for its contents, |
99 | * with subnodes named trough and slider. |
100 | * |
101 | * The main node gets the style class .fine-tune added when the scale is in |
102 | * 'fine-tuning' mode. |
103 | * |
104 | * If the scale has an origin (see [method@Gtk.Scale.set_has_origin]), there is |
105 | * a subnode with name highlight below the trough node that is used for rendering |
106 | * the highlighted part of the trough. |
107 | * |
108 | * If the scale is showing a fill level (see [method@Gtk.Range.set_show_fill_level]), |
109 | * there is a subnode with name fill below the trough node that is used for |
110 | * rendering the filled in part of the trough. |
111 | * |
112 | * If marks are present, there is a marks subnode before or after the trough |
113 | * node, below which each mark gets a node with name mark. The marks nodes get |
114 | * either the .top or .bottom style class. |
115 | * |
116 | * The mark node has a subnode named indicator. If the mark has text, it also |
117 | * has a subnode named label. When the mark is either above or left of the |
118 | * scale, the label subnode is the first when present. Otherwise, the indicator |
119 | * subnode is the first. |
120 | * |
121 | * The main CSS node gets the 'marks-before' and/or 'marks-after' style classes |
122 | * added depending on what marks are present. |
123 | * |
124 | * If the scale is displaying the value (see [property@Gtk.Scale:draw-value]), |
125 | * there is subnode with name value. This node will get the .top or .bottom style |
126 | * classes similar to the marks node. |
127 | * |
128 | * # Accessibility |
129 | * |
130 | * `GtkScale` uses the %GTK_ACCESSIBLE_ROLE_SLIDER role. |
131 | */ |
132 | |
133 | |
134 | #define MAX_DIGITS (64) /* don't change this, |
135 | * a) you don't need to and |
136 | * b) you might cause buffer owerflows in |
137 | * unrelated code portions otherwise |
138 | */ |
139 | |
140 | typedef struct _GtkScaleMark GtkScaleMark; |
141 | |
142 | typedef struct _GtkScalePrivate GtkScalePrivate; |
143 | struct _GtkScalePrivate |
144 | { |
145 | GSList *marks; |
146 | |
147 | GtkWidget *value_widget; |
148 | GtkWidget *top_marks_widget; |
149 | GtkWidget *bottom_marks_widget; |
150 | |
151 | int digits; |
152 | |
153 | guint draw_value : 1; |
154 | guint value_pos : 2; |
155 | |
156 | GtkScaleFormatValueFunc format_value_func; |
157 | gpointer format_value_func_user_data; |
158 | GDestroyNotify format_value_func_destroy_notify; |
159 | }; |
160 | |
161 | struct _GtkScaleMark |
162 | { |
163 | double value; |
164 | int stop_position; |
165 | GtkPositionType position; /* always GTK_POS_TOP or GTK_POS_BOTTOM */ |
166 | char *markup; |
167 | GtkWidget *label_widget; |
168 | GtkWidget *indicator_widget; |
169 | GtkWidget *widget; |
170 | }; |
171 | |
172 | enum { |
173 | PROP_0, |
174 | PROP_DIGITS, |
175 | PROP_DRAW_VALUE, |
176 | PROP_HAS_ORIGIN, |
177 | PROP_VALUE_POS, |
178 | LAST_PROP |
179 | }; |
180 | |
181 | |
182 | static GParamSpec *properties[LAST_PROP]; |
183 | |
184 | static void gtk_scale_set_property (GObject *object, |
185 | guint prop_id, |
186 | const GValue *value, |
187 | GParamSpec *pspec); |
188 | static void gtk_scale_get_property (GObject *object, |
189 | guint prop_id, |
190 | GValue *value, |
191 | GParamSpec *pspec); |
192 | static void gtk_scale_measure (GtkWidget *widget, |
193 | GtkOrientation orientation, |
194 | int for_size, |
195 | int *minimum, |
196 | int *natural, |
197 | int *minimum_baseline, |
198 | int *natural_baseline); |
199 | static void gtk_scale_get_range_border (GtkRange *range, |
200 | GtkBorder *border); |
201 | static void gtk_scale_finalize (GObject *object); |
202 | static void gtk_scale_real_get_layout_offsets (GtkScale *scale, |
203 | int *x, |
204 | int *y); |
205 | static void gtk_scale_buildable_interface_init (GtkBuildableIface *iface); |
206 | static gboolean gtk_scale_buildable_custom_tag_start (GtkBuildable *buildable, |
207 | GtkBuilder *builder, |
208 | GObject *child, |
209 | const char *tagname, |
210 | GtkBuildableParser *parser, |
211 | gpointer *data); |
212 | static void gtk_scale_buildable_custom_finished (GtkBuildable *buildable, |
213 | GtkBuilder *builder, |
214 | GObject *child, |
215 | const char *tagname, |
216 | gpointer user_data); |
217 | static char * gtk_scale_format_value (GtkScale *scale, |
218 | double value); |
219 | |
220 | |
221 | G_DEFINE_TYPE_WITH_CODE (GtkScale, gtk_scale, GTK_TYPE_RANGE, |
222 | G_ADD_PRIVATE (GtkScale) |
223 | G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, |
224 | gtk_scale_buildable_interface_init)) |
225 | |
226 | static int |
227 | compare_marks (gconstpointer a, gconstpointer b, gpointer data) |
228 | { |
229 | gboolean inverted = GPOINTER_TO_INT (data); |
230 | int val; |
231 | const GtkScaleMark *ma, *mb; |
232 | |
233 | val = inverted ? -1 : 1; |
234 | |
235 | ma = a; mb = b; |
236 | |
237 | return (ma->value > mb->value) ? val : ((ma->value < mb->value) ? -val : 0); |
238 | } |
239 | |
240 | static void |
241 | update_label_request (GtkScale *scale) |
242 | { |
243 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
244 | GtkAdjustment *adjustment = gtk_range_get_adjustment (GTK_RANGE (scale)); |
245 | double lowest_value, highest_value; |
246 | char *old_text; |
247 | char *text; |
248 | int size = 0; |
249 | int min; |
250 | |
251 | g_assert (priv->value_widget != NULL); |
252 | |
253 | lowest_value = gtk_adjustment_get_lower (adjustment); |
254 | highest_value = gtk_adjustment_get_upper (adjustment); |
255 | |
256 | old_text = g_strdup (str: gtk_label_get_label (GTK_LABEL (priv->value_widget))); |
257 | gtk_widget_set_size_request (widget: priv->value_widget, width: -1, height: -1); |
258 | |
259 | text = gtk_scale_format_value (scale, value: lowest_value); |
260 | gtk_label_set_label (GTK_LABEL (priv->value_widget), str: text); |
261 | |
262 | gtk_widget_measure (widget: priv->value_widget, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, |
263 | minimum: &min, NULL, NULL, NULL); |
264 | size = MAX (size, min); |
265 | g_free (mem: text); |
266 | |
267 | text = gtk_scale_format_value (scale, value: highest_value); |
268 | gtk_label_set_label (GTK_LABEL (priv->value_widget), str: text); |
269 | |
270 | gtk_widget_measure (widget: priv->value_widget, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, |
271 | minimum: &min, NULL, NULL, NULL); |
272 | size = MAX (size, min); |
273 | g_free (mem: text); |
274 | |
275 | gtk_widget_set_size_request (widget: priv->value_widget, width: size, height: -1); |
276 | gtk_label_set_label (GTK_LABEL (priv->value_widget), str: old_text); |
277 | g_free (mem: old_text); |
278 | } |
279 | |
280 | static void |
281 | gtk_scale_notify (GObject *object, |
282 | GParamSpec *pspec) |
283 | { |
284 | GtkScale *scale = GTK_SCALE (object); |
285 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
286 | |
287 | if (strcmp (s1: pspec->name, s2: "inverted" ) == 0) |
288 | { |
289 | GtkScaleMark *mark; |
290 | GSList *m; |
291 | int i, n; |
292 | double *values; |
293 | |
294 | priv->marks = g_slist_sort_with_data (list: priv->marks, |
295 | compare_func: compare_marks, |
296 | GINT_TO_POINTER (gtk_range_get_inverted (GTK_RANGE (scale)))); |
297 | |
298 | n = g_slist_length (list: priv->marks); |
299 | values = g_new (double, n); |
300 | for (m = priv->marks, i = 0; m; m = m->next, i++) |
301 | { |
302 | mark = m->data; |
303 | values[i] = mark->value; |
304 | } |
305 | |
306 | _gtk_range_set_stop_values (GTK_RANGE (scale), values, n_values: n); |
307 | |
308 | if (priv->top_marks_widget) |
309 | gtk_widget_queue_resize (widget: priv->top_marks_widget); |
310 | |
311 | if (priv->bottom_marks_widget) |
312 | gtk_widget_queue_resize (widget: priv->bottom_marks_widget); |
313 | |
314 | g_free (mem: values); |
315 | } |
316 | else if (strcmp (s1: pspec->name, s2: "adjustment" )) |
317 | { |
318 | if (priv->value_widget) |
319 | update_label_request (scale); |
320 | } |
321 | |
322 | if (G_OBJECT_CLASS (gtk_scale_parent_class)->notify) |
323 | G_OBJECT_CLASS (gtk_scale_parent_class)->notify (object, pspec); |
324 | } |
325 | |
326 | static void |
327 | gtk_scale_allocate_value (GtkScale *scale) |
328 | { |
329 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
330 | GtkWidget *widget = GTK_WIDGET (scale); |
331 | GtkRange *range = GTK_RANGE (widget); |
332 | GtkWidget *slider_widget; |
333 | GtkAllocation value_alloc; |
334 | int range_width, range_height; |
335 | graphene_rect_t slider_bounds; |
336 | |
337 | range_width = gtk_widget_get_width (widget); |
338 | range_height = gtk_widget_get_height (widget); |
339 | |
340 | slider_widget = gtk_range_get_slider_widget (range); |
341 | if (!gtk_widget_compute_bounds (widget: slider_widget, target: widget, out_bounds: &slider_bounds)) |
342 | graphene_rect_init (r: &slider_bounds, x: 0, y: 0, width: gtk_widget_get_width (widget), height: gtk_widget_get_height (widget)); |
343 | |
344 | gtk_widget_measure (widget: priv->value_widget, |
345 | orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, |
346 | minimum: &value_alloc.width, NULL, |
347 | NULL, NULL); |
348 | gtk_widget_measure (widget: priv->value_widget, |
349 | orientation: GTK_ORIENTATION_VERTICAL, for_size: -1, |
350 | minimum: &value_alloc.height, NULL, |
351 | NULL, NULL); |
352 | |
353 | if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL) |
354 | { |
355 | switch (priv->value_pos) |
356 | { |
357 | case GTK_POS_LEFT: |
358 | value_alloc.x = 0; |
359 | value_alloc.y = (range_height - value_alloc.height) / 2; |
360 | break; |
361 | |
362 | case GTK_POS_RIGHT: |
363 | value_alloc.x = range_width - value_alloc.width; |
364 | value_alloc.y = (range_height - value_alloc.height) / 2; |
365 | break; |
366 | |
367 | case GTK_POS_TOP: |
368 | value_alloc.x = slider_bounds.origin.x + (slider_bounds.size.width - value_alloc.width) / 2; |
369 | value_alloc.y = slider_bounds.origin.y - value_alloc.height; |
370 | break; |
371 | |
372 | case GTK_POS_BOTTOM: |
373 | value_alloc.x = slider_bounds.origin.x + (slider_bounds.size.width - value_alloc.width) / 2; |
374 | value_alloc.y = range_height - value_alloc.height; |
375 | break; |
376 | |
377 | default: |
378 | g_return_if_reached (); |
379 | break; |
380 | } |
381 | } |
382 | else /* VERTICAL */ |
383 | { |
384 | switch (priv->value_pos) |
385 | { |
386 | case GTK_POS_LEFT: |
387 | value_alloc.x = 0; |
388 | value_alloc.y = (slider_bounds.origin.y + (slider_bounds.size.height / 2)) - value_alloc.height / 2; |
389 | break; |
390 | |
391 | case GTK_POS_RIGHT: |
392 | value_alloc.x = range_width - value_alloc.width; |
393 | value_alloc.y = (slider_bounds.origin.y + (slider_bounds.size.height / 2)) - value_alloc.height / 2; |
394 | break; |
395 | |
396 | case GTK_POS_TOP: |
397 | value_alloc.x = (range_width - value_alloc.width) / 2; |
398 | value_alloc.y = 0; |
399 | break; |
400 | |
401 | case GTK_POS_BOTTOM: |
402 | value_alloc.x = (range_width - value_alloc.width) / 2; |
403 | value_alloc.y = range_height - value_alloc.height; |
404 | break; |
405 | |
406 | default: |
407 | g_return_if_reached (); |
408 | } |
409 | } |
410 | |
411 | gtk_widget_size_allocate (widget: priv->value_widget, allocation: &value_alloc, baseline: -1); |
412 | } |
413 | |
414 | static void |
415 | gtk_scale_allocate_mark (GtkGizmo *gizmo, |
416 | int width, |
417 | int height, |
418 | int baseline) |
419 | { |
420 | GtkWidget *widget = GTK_WIDGET (gizmo); |
421 | GtkScale *scale = GTK_SCALE (gtk_widget_get_parent (gtk_widget_get_parent (widget))); |
422 | GtkScaleMark *mark = g_object_get_data (G_OBJECT (gizmo), key: "mark" ); |
423 | int indicator_width, indicator_height; |
424 | GtkAllocation indicator_alloc; |
425 | GtkOrientation orientation; |
426 | |
427 | orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (scale)); |
428 | gtk_widget_measure (widget: mark->indicator_widget, |
429 | orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, |
430 | minimum: &indicator_width, NULL, |
431 | NULL, NULL); |
432 | gtk_widget_measure (widget: mark->indicator_widget, |
433 | orientation: GTK_ORIENTATION_VERTICAL, for_size: -1, |
434 | minimum: &indicator_height, NULL, |
435 | NULL, NULL); |
436 | |
437 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
438 | { |
439 | indicator_alloc.x = (width - indicator_width) / 2; |
440 | if (mark->position == GTK_POS_TOP) |
441 | indicator_alloc.y = height - indicator_height; |
442 | else |
443 | indicator_alloc.y = 0; |
444 | |
445 | indicator_alloc.width = indicator_width; |
446 | indicator_alloc.height = indicator_height; |
447 | } |
448 | else |
449 | { |
450 | if (mark->position == GTK_POS_TOP) |
451 | indicator_alloc.x = width - indicator_width; |
452 | else |
453 | indicator_alloc.x = 0; |
454 | indicator_alloc.y = (height - indicator_height) / 2; |
455 | indicator_alloc.width = indicator_width; |
456 | indicator_alloc.height = indicator_height; |
457 | } |
458 | |
459 | gtk_widget_size_allocate (widget: mark->indicator_widget, allocation: &indicator_alloc, baseline); |
460 | |
461 | if (mark->label_widget) |
462 | { |
463 | GtkAllocation label_alloc; |
464 | |
465 | label_alloc = (GtkAllocation) {0, 0, width, height}; |
466 | |
467 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
468 | { |
469 | label_alloc.height = height - indicator_alloc.height; |
470 | if (mark->position == GTK_POS_BOTTOM) |
471 | label_alloc.y = indicator_alloc.y + indicator_alloc.height; |
472 | } |
473 | else |
474 | { |
475 | label_alloc.width = width - indicator_alloc.width; |
476 | if (mark->position == GTK_POS_BOTTOM) |
477 | label_alloc.x = indicator_alloc.x + indicator_alloc.width; |
478 | } |
479 | |
480 | gtk_widget_size_allocate (widget: mark->label_widget, allocation: &label_alloc, baseline); |
481 | } |
482 | } |
483 | |
484 | static void |
485 | gtk_scale_allocate_marks (GtkGizmo *gizmo, |
486 | int width, |
487 | int height, |
488 | int baseline) |
489 | { |
490 | GtkWidget *widget = GTK_WIDGET (gizmo); |
491 | GtkScale *scale = GTK_SCALE (gtk_widget_get_parent (widget)); |
492 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
493 | GtkOrientation orientation; |
494 | int *marks; |
495 | int i; |
496 | GSList *m; |
497 | |
498 | orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (scale)); |
499 | _gtk_range_get_stop_positions (GTK_RANGE (scale), values: &marks); |
500 | |
501 | for (m = priv->marks, i = 0; m; m = m->next, i++) |
502 | { |
503 | GtkScaleMark *mark = m->data; |
504 | GtkAllocation mark_alloc; |
505 | int mark_size; |
506 | |
507 | if ((mark->position == GTK_POS_TOP && widget == priv->bottom_marks_widget) || |
508 | (mark->position == GTK_POS_BOTTOM && widget == priv->top_marks_widget)) |
509 | continue; |
510 | |
511 | gtk_widget_measure (widget: mark->widget, |
512 | orientation, for_size: -1, |
513 | minimum: &mark_size, NULL, |
514 | NULL, NULL); |
515 | mark->stop_position = marks[i]; |
516 | |
517 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
518 | { |
519 | mark_alloc.x = mark->stop_position; |
520 | mark_alloc.y = 0; |
521 | mark_alloc.width = mark_size; |
522 | mark_alloc.height = height; |
523 | |
524 | mark_alloc.x -= mark_size / 2; |
525 | } |
526 | else |
527 | { |
528 | mark_alloc.x = 0; |
529 | mark_alloc.y = mark->stop_position; |
530 | mark_alloc.width = width; |
531 | mark_alloc.height = mark_size; |
532 | |
533 | mark_alloc.y -= mark_size / 2; |
534 | } |
535 | |
536 | gtk_widget_size_allocate (widget: mark->widget, allocation: &mark_alloc, baseline); |
537 | } |
538 | |
539 | g_free (mem: marks); |
540 | } |
541 | |
542 | static void |
543 | gtk_scale_size_allocate (GtkWidget *widget, |
544 | int width, |
545 | int height, |
546 | int baseline) |
547 | { |
548 | GtkScale *scale = GTK_SCALE (widget); |
549 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
550 | GtkAllocation range_rect, marks_rect; |
551 | GtkOrientation orientation; |
552 | |
553 | GTK_WIDGET_CLASS (gtk_scale_parent_class)->size_allocate (widget, width, height, baseline); |
554 | |
555 | orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)); |
556 | gtk_range_get_range_rect (GTK_RANGE (scale), range_rect: &range_rect); |
557 | |
558 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
559 | { |
560 | int marks_height = 0; |
561 | |
562 | if (priv->top_marks_widget) |
563 | { |
564 | gtk_widget_measure (widget: priv->top_marks_widget, |
565 | orientation: GTK_ORIENTATION_VERTICAL, for_size: -1, |
566 | minimum: &marks_height, NULL, |
567 | NULL, NULL); |
568 | marks_rect.x = 0; |
569 | marks_rect.y = 0; |
570 | marks_rect.width = range_rect.width; |
571 | marks_rect.height = marks_height; |
572 | gtk_widget_size_allocate (widget: priv->top_marks_widget, allocation: &marks_rect, baseline: -1); |
573 | } |
574 | |
575 | if (priv->bottom_marks_widget) |
576 | { |
577 | gtk_widget_measure (widget: priv->bottom_marks_widget, |
578 | orientation: GTK_ORIENTATION_VERTICAL, for_size: -1, |
579 | minimum: &marks_height, NULL, |
580 | NULL, NULL); |
581 | marks_rect.x = 0; |
582 | marks_rect.y = range_rect.y + range_rect.height; |
583 | marks_rect.width = range_rect.width; |
584 | marks_rect.height = marks_height; |
585 | gtk_widget_size_allocate (widget: priv->bottom_marks_widget, allocation: &marks_rect, baseline: -1); |
586 | } |
587 | } |
588 | else |
589 | { |
590 | int marks_width = 0; |
591 | |
592 | if (priv->top_marks_widget) |
593 | { |
594 | gtk_widget_measure (widget: priv->top_marks_widget, |
595 | orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, |
596 | minimum: &marks_width, NULL, |
597 | NULL, NULL); |
598 | marks_rect.x = range_rect.x - marks_width; |
599 | marks_rect.y = 0; |
600 | marks_rect.width = marks_width; |
601 | marks_rect.height = range_rect.height; |
602 | gtk_widget_size_allocate (widget: priv->top_marks_widget, allocation: &marks_rect, baseline: -1); |
603 | } |
604 | |
605 | if (priv->bottom_marks_widget) |
606 | { |
607 | gtk_widget_measure (widget: priv->bottom_marks_widget, |
608 | orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, |
609 | minimum: &marks_width, NULL, |
610 | NULL, NULL); |
611 | marks_rect = range_rect; |
612 | marks_rect.x = range_rect.x + range_rect.width; |
613 | marks_rect.y = 0; |
614 | marks_rect.width = marks_width; |
615 | marks_rect.height = range_rect.height; |
616 | gtk_widget_size_allocate (widget: priv->bottom_marks_widget, allocation: &marks_rect, baseline: -1); |
617 | } |
618 | } |
619 | |
620 | if (priv->value_widget) |
621 | { |
622 | gtk_scale_allocate_value (scale); |
623 | } |
624 | } |
625 | |
626 | #define add_slider_binding(binding_set, keyval, mask, scroll) \ |
627 | gtk_widget_class_add_binding_signal (widget_class, \ |
628 | keyval, mask, \ |
629 | I_("move-slider"), \ |
630 | "(i)", scroll) |
631 | |
632 | static void |
633 | gtk_scale_value_changed (GtkRange *range) |
634 | { |
635 | GtkScalePrivate *priv = gtk_scale_get_instance_private (GTK_SCALE (range)); |
636 | GtkAdjustment *adjustment = gtk_range_get_adjustment (range); |
637 | |
638 | if (priv->value_widget) |
639 | { |
640 | char *text = gtk_scale_format_value (GTK_SCALE (range), |
641 | value: gtk_adjustment_get_value (adjustment)); |
642 | gtk_label_set_label (GTK_LABEL (priv->value_widget), str: text); |
643 | |
644 | g_free (mem: text); |
645 | } |
646 | } |
647 | |
648 | static void |
649 | gtk_scale_class_init (GtkScaleClass *class) |
650 | { |
651 | GObjectClass *gobject_class; |
652 | GtkWidgetClass *widget_class; |
653 | GtkRangeClass *range_class; |
654 | |
655 | gobject_class = G_OBJECT_CLASS (class); |
656 | range_class = (GtkRangeClass*) class; |
657 | widget_class = (GtkWidgetClass*) class; |
658 | |
659 | gobject_class->set_property = gtk_scale_set_property; |
660 | gobject_class->get_property = gtk_scale_get_property; |
661 | gobject_class->notify = gtk_scale_notify; |
662 | gobject_class->finalize = gtk_scale_finalize; |
663 | |
664 | widget_class->size_allocate = gtk_scale_size_allocate; |
665 | widget_class->measure = gtk_scale_measure; |
666 | widget_class->grab_focus = gtk_widget_grab_focus_self; |
667 | widget_class->focus = gtk_widget_focus_self; |
668 | |
669 | range_class->get_range_border = gtk_scale_get_range_border; |
670 | range_class->value_changed = gtk_scale_value_changed; |
671 | |
672 | class->get_layout_offsets = gtk_scale_real_get_layout_offsets; |
673 | |
674 | /** |
675 | * GtkScale:digits: (attributes org.gtk.Method.get=gtk_scale_get_digits org.gtk.Method.set=gtk_scale_set_digits) |
676 | * |
677 | * The number of decimal places that are displayed in the value. |
678 | */ |
679 | properties[PROP_DIGITS] = |
680 | g_param_spec_int (name: "digits" , |
681 | P_("Digits" ), |
682 | P_("The number of decimal places that are displayed in the value" ), |
683 | minimum: -1, MAX_DIGITS, |
684 | default_value: 1, |
685 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
686 | |
687 | /** |
688 | * GtkScale:draw-value: (attributes org.gtk.Method.get=gtk_scale_get_draw_value org.gtk.Method.set=gtk_scale_set_draw_value) |
689 | * |
690 | * Whether the current value is displayed as a string next to the slider. |
691 | */ |
692 | properties[PROP_DRAW_VALUE] = |
693 | g_param_spec_boolean (name: "draw-value" , |
694 | P_("Draw Value" ), |
695 | P_("Whether the current value is displayed as a string next to the slider" ), |
696 | FALSE, |
697 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
698 | |
699 | /** |
700 | * GtkScale:has-origin: (attributes org.gtk.Method.get=gtk_scale_get_has_origin org.gtk.Method.set=gtk_scale_set_has_origin) |
701 | * |
702 | * Whether the scale has an origin. |
703 | */ |
704 | properties[PROP_HAS_ORIGIN] = |
705 | g_param_spec_boolean (name: "has-origin" , |
706 | P_("Has Origin" ), |
707 | P_("Whether the scale has an origin" ), |
708 | TRUE, |
709 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
710 | |
711 | /** |
712 | * GtkScale:value-pos: (attributes org.gtk.Method.get=gtk_scale_get_value_pos org.gtk.Method.set=gtk_scale_set_value_pos) |
713 | * |
714 | * The position in which the current value is displayed. |
715 | */ |
716 | properties[PROP_VALUE_POS] = |
717 | g_param_spec_enum (name: "value-pos" , |
718 | P_("Value Position" ), |
719 | P_("The position in which the current value is displayed" ), |
720 | enum_type: GTK_TYPE_POSITION_TYPE, |
721 | default_value: GTK_POS_TOP, |
722 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
723 | |
724 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: LAST_PROP, pspecs: properties); |
725 | |
726 | /* All bindings (even arrow keys) are on both h/v scale, because |
727 | * blind users etc. don't care about scale orientation. |
728 | */ |
729 | |
730 | add_slider_binding (binding_set, GDK_KEY_Left, 0, |
731 | GTK_SCROLL_STEP_LEFT); |
732 | |
733 | add_slider_binding (binding_set, GDK_KEY_Left, GDK_CONTROL_MASK, |
734 | GTK_SCROLL_PAGE_LEFT); |
735 | |
736 | add_slider_binding (binding_set, GDK_KEY_KP_Left, 0, |
737 | GTK_SCROLL_STEP_LEFT); |
738 | |
739 | add_slider_binding (binding_set, GDK_KEY_KP_Left, GDK_CONTROL_MASK, |
740 | GTK_SCROLL_PAGE_LEFT); |
741 | |
742 | add_slider_binding (binding_set, GDK_KEY_Right, 0, |
743 | GTK_SCROLL_STEP_RIGHT); |
744 | |
745 | add_slider_binding (binding_set, GDK_KEY_Right, GDK_CONTROL_MASK, |
746 | GTK_SCROLL_PAGE_RIGHT); |
747 | |
748 | add_slider_binding (binding_set, GDK_KEY_KP_Right, 0, |
749 | GTK_SCROLL_STEP_RIGHT); |
750 | |
751 | add_slider_binding (binding_set, GDK_KEY_KP_Right, GDK_CONTROL_MASK, |
752 | GTK_SCROLL_PAGE_RIGHT); |
753 | |
754 | add_slider_binding (binding_set, GDK_KEY_Up, 0, |
755 | GTK_SCROLL_STEP_UP); |
756 | |
757 | add_slider_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK, |
758 | GTK_SCROLL_PAGE_UP); |
759 | |
760 | add_slider_binding (binding_set, GDK_KEY_KP_Up, 0, |
761 | GTK_SCROLL_STEP_UP); |
762 | |
763 | add_slider_binding (binding_set, GDK_KEY_KP_Up, GDK_CONTROL_MASK, |
764 | GTK_SCROLL_PAGE_UP); |
765 | |
766 | add_slider_binding (binding_set, GDK_KEY_Down, 0, |
767 | GTK_SCROLL_STEP_DOWN); |
768 | |
769 | add_slider_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK, |
770 | GTK_SCROLL_PAGE_DOWN); |
771 | |
772 | add_slider_binding (binding_set, GDK_KEY_KP_Down, 0, |
773 | GTK_SCROLL_STEP_DOWN); |
774 | |
775 | add_slider_binding (binding_set, GDK_KEY_KP_Down, GDK_CONTROL_MASK, |
776 | GTK_SCROLL_PAGE_DOWN); |
777 | |
778 | add_slider_binding (binding_set, GDK_KEY_Page_Up, GDK_CONTROL_MASK, |
779 | GTK_SCROLL_PAGE_LEFT); |
780 | |
781 | add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, GDK_CONTROL_MASK, |
782 | GTK_SCROLL_PAGE_LEFT); |
783 | |
784 | add_slider_binding (binding_set, GDK_KEY_Page_Up, 0, |
785 | GTK_SCROLL_PAGE_UP); |
786 | |
787 | add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, 0, |
788 | GTK_SCROLL_PAGE_UP); |
789 | |
790 | add_slider_binding (binding_set, GDK_KEY_Page_Down, GDK_CONTROL_MASK, |
791 | GTK_SCROLL_PAGE_RIGHT); |
792 | |
793 | add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, GDK_CONTROL_MASK, |
794 | GTK_SCROLL_PAGE_RIGHT); |
795 | |
796 | add_slider_binding (binding_set, GDK_KEY_Page_Down, 0, |
797 | GTK_SCROLL_PAGE_DOWN); |
798 | |
799 | add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, 0, |
800 | GTK_SCROLL_PAGE_DOWN); |
801 | |
802 | /* Logical bindings (vs. visual bindings above) */ |
803 | |
804 | add_slider_binding (binding_set, GDK_KEY_plus, 0, |
805 | GTK_SCROLL_STEP_FORWARD); |
806 | |
807 | add_slider_binding (binding_set, GDK_KEY_minus, 0, |
808 | GTK_SCROLL_STEP_BACKWARD); |
809 | |
810 | add_slider_binding (binding_set, GDK_KEY_plus, GDK_CONTROL_MASK, |
811 | GTK_SCROLL_PAGE_FORWARD); |
812 | |
813 | add_slider_binding (binding_set, GDK_KEY_minus, GDK_CONTROL_MASK, |
814 | GTK_SCROLL_PAGE_BACKWARD); |
815 | |
816 | |
817 | add_slider_binding (binding_set, GDK_KEY_KP_Add, 0, |
818 | GTK_SCROLL_STEP_FORWARD); |
819 | |
820 | add_slider_binding (binding_set, GDK_KEY_KP_Subtract, 0, |
821 | GTK_SCROLL_STEP_BACKWARD); |
822 | |
823 | add_slider_binding (binding_set, GDK_KEY_KP_Add, GDK_CONTROL_MASK, |
824 | GTK_SCROLL_PAGE_FORWARD); |
825 | |
826 | add_slider_binding (binding_set, GDK_KEY_KP_Subtract, GDK_CONTROL_MASK, |
827 | GTK_SCROLL_PAGE_BACKWARD); |
828 | |
829 | add_slider_binding (binding_set, GDK_KEY_Home, 0, |
830 | GTK_SCROLL_START); |
831 | |
832 | add_slider_binding (binding_set, GDK_KEY_KP_Home, 0, |
833 | GTK_SCROLL_START); |
834 | |
835 | add_slider_binding (binding_set, GDK_KEY_End, 0, |
836 | GTK_SCROLL_END); |
837 | |
838 | add_slider_binding (binding_set, GDK_KEY_KP_End, 0, |
839 | GTK_SCROLL_END); |
840 | |
841 | gtk_widget_class_set_css_name (widget_class, I_("scale" )); |
842 | |
843 | gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_SLIDER); |
844 | } |
845 | |
846 | static void |
847 | gtk_scale_init (GtkScale *scale) |
848 | { |
849 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
850 | GtkRange *range = GTK_RANGE (scale); |
851 | |
852 | gtk_widget_set_focusable (GTK_WIDGET (scale), TRUE); |
853 | |
854 | priv->value_pos = GTK_POS_TOP; |
855 | priv->digits = 1; |
856 | |
857 | gtk_range_set_slider_size_fixed (range, TRUE); |
858 | |
859 | _gtk_range_set_has_origin (range, TRUE); |
860 | |
861 | gtk_range_set_round_digits (range, round_digits: -1); |
862 | |
863 | gtk_range_set_flippable (range, TRUE); |
864 | } |
865 | |
866 | static void |
867 | gtk_scale_set_property (GObject *object, |
868 | guint prop_id, |
869 | const GValue *value, |
870 | GParamSpec *pspec) |
871 | { |
872 | GtkScale *scale; |
873 | |
874 | scale = GTK_SCALE (object); |
875 | |
876 | switch (prop_id) |
877 | { |
878 | case PROP_DIGITS: |
879 | gtk_scale_set_digits (scale, digits: g_value_get_int (value)); |
880 | break; |
881 | case PROP_DRAW_VALUE: |
882 | gtk_scale_set_draw_value (scale, draw_value: g_value_get_boolean (value)); |
883 | break; |
884 | case PROP_HAS_ORIGIN: |
885 | gtk_scale_set_has_origin (scale, has_origin: g_value_get_boolean (value)); |
886 | break; |
887 | case PROP_VALUE_POS: |
888 | gtk_scale_set_value_pos (scale, pos: g_value_get_enum (value)); |
889 | break; |
890 | default: |
891 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
892 | break; |
893 | } |
894 | } |
895 | |
896 | static void |
897 | gtk_scale_get_property (GObject *object, |
898 | guint prop_id, |
899 | GValue *value, |
900 | GParamSpec *pspec) |
901 | { |
902 | GtkScale *scale = GTK_SCALE (object); |
903 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
904 | |
905 | switch (prop_id) |
906 | { |
907 | case PROP_DIGITS: |
908 | g_value_set_int (value, v_int: priv->digits); |
909 | break; |
910 | case PROP_DRAW_VALUE: |
911 | g_value_set_boolean (value, v_boolean: priv->draw_value); |
912 | break; |
913 | case PROP_HAS_ORIGIN: |
914 | g_value_set_boolean (value, v_boolean: gtk_scale_get_has_origin (scale)); |
915 | break; |
916 | case PROP_VALUE_POS: |
917 | g_value_set_enum (value, v_enum: priv->value_pos); |
918 | break; |
919 | default: |
920 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
921 | break; |
922 | } |
923 | } |
924 | |
925 | /** |
926 | * gtk_scale_new: |
927 | * @orientation: the scale’s orientation. |
928 | * @adjustment: (nullable): the [class@Gtk.Adjustment] which sets |
929 | * the range of the scale, or %NULL to create a new adjustment. |
930 | * |
931 | * Creates a new `GtkScale`. |
932 | * |
933 | * Returns: a new `GtkScale` |
934 | */ |
935 | GtkWidget * |
936 | gtk_scale_new (GtkOrientation orientation, |
937 | GtkAdjustment *adjustment) |
938 | { |
939 | g_return_val_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment), |
940 | NULL); |
941 | |
942 | return g_object_new (GTK_TYPE_SCALE, |
943 | first_property_name: "orientation" , orientation, |
944 | "adjustment" , adjustment, |
945 | NULL); |
946 | } |
947 | |
948 | /** |
949 | * gtk_scale_new_with_range: |
950 | * @orientation: the scale’s orientation. |
951 | * @min: minimum value |
952 | * @max: maximum value |
953 | * @step: step increment (tick size) used with keyboard shortcuts |
954 | * |
955 | * Creates a new scale widget with a range from @min to @max. |
956 | * |
957 | * The returns scale will have the given orientation and will let the |
958 | * user input a number between @min and @max (including @min and @max) |
959 | * with the increment @step. @step must be nonzero; it’s the distance |
960 | * the slider moves when using the arrow keys to adjust the scale |
961 | * value. |
962 | * |
963 | * Note that the way in which the precision is derived works best if |
964 | * @step is a power of ten. If the resulting precision is not suitable |
965 | * for your needs, use [method@Gtk.Scale.set_digits] to correct it. |
966 | * |
967 | * Returns: a new `GtkScale` |
968 | */ |
969 | GtkWidget * |
970 | gtk_scale_new_with_range (GtkOrientation orientation, |
971 | double min, |
972 | double max, |
973 | double step) |
974 | { |
975 | GtkAdjustment *adj; |
976 | int digits; |
977 | |
978 | g_return_val_if_fail (min < max, NULL); |
979 | g_return_val_if_fail (step != 0.0, NULL); |
980 | |
981 | adj = gtk_adjustment_new (value: min, lower: min, upper: max, step_increment: step, page_increment: 10 * step, page_size: 0); |
982 | |
983 | if (fabs (x: step) >= 1.0 || step == 0.0) |
984 | { |
985 | digits = 0; |
986 | } |
987 | else |
988 | { |
989 | digits = abs (x: (int) floor (x: log10 (x: fabs (x: step)))); |
990 | if (digits > 5) |
991 | digits = 5; |
992 | } |
993 | |
994 | return g_object_new (GTK_TYPE_SCALE, |
995 | first_property_name: "orientation" , orientation, |
996 | "adjustment" , adj, |
997 | "digits" , digits, |
998 | NULL); |
999 | } |
1000 | |
1001 | /** |
1002 | * gtk_scale_set_digits: (attributes org.gtk.Method.set_property=digits) |
1003 | * @scale: a `GtkScale` |
1004 | * @digits: the number of decimal places to display, |
1005 | * e.g. use 1 to display 1.0, 2 to display 1.00, etc |
1006 | * |
1007 | * Sets the number of decimal places that are displayed in the value. |
1008 | * |
1009 | * Also causes the value of the adjustment to be rounded to this number |
1010 | * of digits, so the retrieved value matches the displayed one, if |
1011 | * [property@GtkScale:draw-value] is %TRUE when the value changes. If |
1012 | * you want to enforce rounding the value when [property@GtkScale:draw-value] |
1013 | * is %FALSE, you can set [property@GtkRange:round-digits] instead. |
1014 | * |
1015 | * Note that rounding to a small number of digits can interfere with |
1016 | * the smooth autoscrolling that is built into `GtkScale`. As an alternative, |
1017 | * you can use [method@Gtk.Scale.set_format_value_func] to format the displayed |
1018 | * value yourself. |
1019 | */ |
1020 | void |
1021 | gtk_scale_set_digits (GtkScale *scale, |
1022 | int digits) |
1023 | { |
1024 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1025 | GtkRange *range; |
1026 | |
1027 | g_return_if_fail (GTK_IS_SCALE (scale)); |
1028 | |
1029 | range = GTK_RANGE (scale); |
1030 | |
1031 | digits = CLAMP (digits, -1, MAX_DIGITS); |
1032 | |
1033 | if (priv->digits != digits) |
1034 | { |
1035 | priv->digits = digits; |
1036 | if (priv->draw_value) |
1037 | gtk_range_set_round_digits (range, round_digits: digits); |
1038 | |
1039 | gtk_widget_queue_resize (GTK_WIDGET (scale)); |
1040 | |
1041 | g_object_notify_by_pspec (G_OBJECT (scale), pspec: properties[PROP_DIGITS]); |
1042 | } |
1043 | } |
1044 | |
1045 | /** |
1046 | * gtk_scale_get_digits: (attributes org.gtk.Method.get_property=digits) |
1047 | * @scale: a `GtkScale` |
1048 | * |
1049 | * Gets the number of decimal places that are displayed in the value. |
1050 | * |
1051 | * Returns: the number of decimal places that are displayed |
1052 | */ |
1053 | int |
1054 | gtk_scale_get_digits (GtkScale *scale) |
1055 | { |
1056 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1057 | |
1058 | g_return_val_if_fail (GTK_IS_SCALE (scale), -1); |
1059 | |
1060 | return priv->digits; |
1061 | } |
1062 | |
1063 | static void |
1064 | update_value_position (GtkScale *scale) |
1065 | { |
1066 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1067 | |
1068 | if (!priv->value_widget) |
1069 | return; |
1070 | |
1071 | gtk_widget_remove_css_class (widget: priv->value_widget, css_class: "top" ); |
1072 | gtk_widget_remove_css_class (widget: priv->value_widget, css_class: "right" ); |
1073 | gtk_widget_remove_css_class (widget: priv->value_widget, css_class: "bottom" ); |
1074 | gtk_widget_remove_css_class (widget: priv->value_widget, css_class: "left" ); |
1075 | |
1076 | switch (priv->value_pos) |
1077 | { |
1078 | case GTK_POS_TOP: |
1079 | gtk_widget_add_css_class (widget: priv->value_widget, css_class: "top" ); |
1080 | break; |
1081 | case GTK_POS_RIGHT: |
1082 | gtk_widget_add_css_class (widget: priv->value_widget, css_class: "right" ); |
1083 | break; |
1084 | case GTK_POS_BOTTOM: |
1085 | gtk_widget_add_css_class (widget: priv->value_widget, css_class: "bottom" ); |
1086 | break; |
1087 | case GTK_POS_LEFT: |
1088 | gtk_widget_add_css_class (widget: priv->value_widget, css_class: "left" ); |
1089 | break; |
1090 | default: |
1091 | g_assert_not_reached (); |
1092 | break; |
1093 | } |
1094 | } |
1095 | |
1096 | /** |
1097 | * gtk_scale_set_draw_value: (attributes org.gtk.Method.set_property=draw-value) |
1098 | * @scale: a `GtkScale` |
1099 | * @draw_value: %TRUE to draw the value |
1100 | * |
1101 | * Specifies whether the current value is displayed as a string next |
1102 | * to the slider. |
1103 | */ |
1104 | void |
1105 | gtk_scale_set_draw_value (GtkScale *scale, |
1106 | gboolean draw_value) |
1107 | { |
1108 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1109 | |
1110 | g_return_if_fail (GTK_IS_SCALE (scale)); |
1111 | |
1112 | draw_value = draw_value != FALSE; |
1113 | |
1114 | if (priv->draw_value != draw_value) |
1115 | { |
1116 | priv->draw_value = draw_value; |
1117 | if (draw_value) |
1118 | { |
1119 | char *txt; |
1120 | |
1121 | txt = gtk_scale_format_value (scale, |
1122 | value: gtk_adjustment_get_value (adjustment: gtk_range_get_adjustment (GTK_RANGE (scale)))); |
1123 | |
1124 | priv->value_widget = g_object_new (GTK_TYPE_LABEL, |
1125 | first_property_name: "css-name" , "value" , |
1126 | "label" , txt, |
1127 | NULL); |
1128 | g_free (mem: txt); |
1129 | |
1130 | gtk_widget_insert_after (widget: priv->value_widget, GTK_WIDGET (scale), NULL); |
1131 | gtk_range_set_round_digits (GTK_RANGE (scale), round_digits: priv->digits); |
1132 | update_value_position (scale); |
1133 | update_label_request (scale); |
1134 | } |
1135 | else if (priv->value_widget) |
1136 | { |
1137 | g_clear_pointer (&priv->value_widget, gtk_widget_unparent); |
1138 | gtk_range_set_round_digits (GTK_RANGE (scale), round_digits: -1); |
1139 | } |
1140 | |
1141 | g_object_notify_by_pspec (G_OBJECT (scale), pspec: properties[PROP_DRAW_VALUE]); |
1142 | } |
1143 | } |
1144 | |
1145 | /** |
1146 | * gtk_scale_get_draw_value: (attributes org.gtk.Method.get_property=draw-value) |
1147 | * @scale: a `GtkScale` |
1148 | * |
1149 | * Returns whether the current value is displayed as a string |
1150 | * next to the slider. |
1151 | * |
1152 | * Returns: whether the current value is displayed as a string |
1153 | */ |
1154 | gboolean |
1155 | gtk_scale_get_draw_value (GtkScale *scale) |
1156 | { |
1157 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1158 | |
1159 | g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE); |
1160 | |
1161 | return priv->draw_value; |
1162 | } |
1163 | |
1164 | /** |
1165 | * gtk_scale_set_has_origin: (attributes org.gtk.Method.set_property=has-origin) |
1166 | * @scale: a `GtkScale` |
1167 | * @has_origin: %TRUE if the scale has an origin |
1168 | * |
1169 | * Sets whether the scale has an origin. |
1170 | * |
1171 | * If [property@GtkScale:has-origin] is set to %TRUE (the default), |
1172 | * the scale will highlight the part of the trough between the origin |
1173 | * (bottom or left side) and the current value. |
1174 | */ |
1175 | void |
1176 | gtk_scale_set_has_origin (GtkScale *scale, |
1177 | gboolean has_origin) |
1178 | { |
1179 | g_return_if_fail (GTK_IS_SCALE (scale)); |
1180 | |
1181 | has_origin = has_origin != FALSE; |
1182 | |
1183 | if (_gtk_range_get_has_origin (GTK_RANGE (scale)) != has_origin) |
1184 | { |
1185 | _gtk_range_set_has_origin (GTK_RANGE (scale), has_origin); |
1186 | |
1187 | gtk_widget_queue_draw (GTK_WIDGET (scale)); |
1188 | |
1189 | g_object_notify_by_pspec (G_OBJECT (scale), pspec: properties[PROP_HAS_ORIGIN]); |
1190 | } |
1191 | } |
1192 | |
1193 | /** |
1194 | * gtk_scale_get_has_origin: (attributes org.gtk.Method.get_property=has-origin) |
1195 | * @scale: a `GtkScale` |
1196 | * |
1197 | * Returns whether the scale has an origin. |
1198 | * |
1199 | * Returns: %TRUE if the scale has an origin. |
1200 | */ |
1201 | gboolean |
1202 | gtk_scale_get_has_origin (GtkScale *scale) |
1203 | { |
1204 | g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE); |
1205 | |
1206 | return _gtk_range_get_has_origin (GTK_RANGE (scale)); |
1207 | } |
1208 | |
1209 | /** |
1210 | * gtk_scale_set_value_pos: (attributes org.gtk.Method.set_property=value-pos) |
1211 | * @scale: a `GtkScale` |
1212 | * @pos: the position in which the current value is displayed |
1213 | * |
1214 | * Sets the position in which the current value is displayed. |
1215 | */ |
1216 | void |
1217 | gtk_scale_set_value_pos (GtkScale *scale, |
1218 | GtkPositionType pos) |
1219 | { |
1220 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1221 | |
1222 | g_return_if_fail (GTK_IS_SCALE (scale)); |
1223 | |
1224 | if (priv->value_pos != pos) |
1225 | { |
1226 | priv->value_pos = pos; |
1227 | |
1228 | update_value_position (scale); |
1229 | gtk_widget_queue_resize (GTK_WIDGET (scale)); |
1230 | |
1231 | g_object_notify_by_pspec (G_OBJECT (scale), pspec: properties[PROP_VALUE_POS]); |
1232 | } |
1233 | } |
1234 | |
1235 | /** |
1236 | * gtk_scale_get_value_pos: (attributes org.gtk.Method.get_property=value-pos) |
1237 | * @scale: a `GtkScale` |
1238 | * |
1239 | * Gets the position in which the current value is displayed. |
1240 | * |
1241 | * Returns: the position in which the current value is displayed |
1242 | */ |
1243 | GtkPositionType |
1244 | gtk_scale_get_value_pos (GtkScale *scale) |
1245 | { |
1246 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1247 | |
1248 | g_return_val_if_fail (GTK_IS_SCALE (scale), 0); |
1249 | |
1250 | return priv->value_pos; |
1251 | } |
1252 | |
1253 | static void |
1254 | gtk_scale_get_range_border (GtkRange *range, |
1255 | GtkBorder *border) |
1256 | { |
1257 | GtkScale *scale = GTK_SCALE (range); |
1258 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1259 | |
1260 | border->left = 0; |
1261 | border->right = 0; |
1262 | border->top = 0; |
1263 | border->bottom = 0; |
1264 | |
1265 | if (priv->value_widget) |
1266 | { |
1267 | int value_size; |
1268 | GtkOrientation value_orientation; |
1269 | |
1270 | if (priv->value_pos == GTK_POS_LEFT || priv->value_pos == GTK_POS_RIGHT) |
1271 | value_orientation = GTK_ORIENTATION_HORIZONTAL; |
1272 | else |
1273 | value_orientation = GTK_ORIENTATION_VERTICAL; |
1274 | |
1275 | gtk_widget_measure (widget: priv->value_widget, |
1276 | orientation: value_orientation, for_size: -1, |
1277 | minimum: &value_size, NULL, |
1278 | NULL, NULL); |
1279 | |
1280 | switch (priv->value_pos) |
1281 | { |
1282 | case GTK_POS_LEFT: |
1283 | border->left += value_size; |
1284 | break; |
1285 | case GTK_POS_RIGHT: |
1286 | border->right += value_size; |
1287 | break; |
1288 | case GTK_POS_TOP: |
1289 | border->top += value_size; |
1290 | break; |
1291 | case GTK_POS_BOTTOM: |
1292 | border->bottom += value_size; |
1293 | break; |
1294 | default: |
1295 | g_assert_not_reached (); |
1296 | break; |
1297 | } |
1298 | } |
1299 | |
1300 | if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL) |
1301 | { |
1302 | int height; |
1303 | |
1304 | if (priv->top_marks_widget) |
1305 | { |
1306 | gtk_widget_measure (widget: priv->top_marks_widget, |
1307 | orientation: GTK_ORIENTATION_VERTICAL, for_size: -1, |
1308 | minimum: &height, NULL, |
1309 | NULL, NULL); |
1310 | if (height > 0) |
1311 | border->top += height; |
1312 | } |
1313 | |
1314 | if (priv->bottom_marks_widget) |
1315 | { |
1316 | gtk_widget_measure (widget: priv->bottom_marks_widget, |
1317 | orientation: GTK_ORIENTATION_VERTICAL, for_size: -1, |
1318 | minimum: &height, NULL, |
1319 | NULL, NULL); |
1320 | if (height > 0) |
1321 | border->bottom += height; |
1322 | } |
1323 | } |
1324 | else |
1325 | { |
1326 | int width; |
1327 | |
1328 | if (priv->top_marks_widget) |
1329 | { |
1330 | gtk_widget_measure (widget: priv->top_marks_widget, |
1331 | orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, |
1332 | minimum: &width, NULL, |
1333 | NULL, NULL); |
1334 | if (width > 0) |
1335 | border->left += width; |
1336 | } |
1337 | |
1338 | if (priv->bottom_marks_widget) |
1339 | { |
1340 | gtk_widget_measure (widget: priv->bottom_marks_widget, |
1341 | orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, |
1342 | minimum: &width, NULL, |
1343 | NULL, NULL); |
1344 | if (width > 0) |
1345 | border->right += width; |
1346 | } |
1347 | } |
1348 | } |
1349 | |
1350 | static void |
1351 | gtk_scale_measure_mark (GtkGizmo *gizmo, |
1352 | GtkOrientation orientation, |
1353 | int for_size, |
1354 | int *minimum, |
1355 | int *natural, |
1356 | int *minimum_baseline, |
1357 | int *natural_baseline) |
1358 | { |
1359 | GtkScaleMark *mark = g_object_get_data (G_OBJECT (gizmo), key: "mark" ); |
1360 | |
1361 | gtk_widget_measure (widget: mark->indicator_widget, |
1362 | orientation, for_size: -1, |
1363 | minimum, natural, |
1364 | NULL, NULL); |
1365 | |
1366 | if (mark->label_widget) |
1367 | { |
1368 | int label_min, label_nat; |
1369 | |
1370 | gtk_widget_measure (widget: mark->label_widget, |
1371 | orientation, for_size: -1, |
1372 | minimum: &label_min, natural: &label_nat, |
1373 | NULL, NULL); |
1374 | *minimum += label_min; |
1375 | *natural += label_nat; |
1376 | } |
1377 | } |
1378 | |
1379 | static void |
1380 | gtk_scale_measure_marks (GtkGizmo *gizmo, |
1381 | GtkOrientation orientation, |
1382 | int for_size, |
1383 | int *minimum, |
1384 | int *natural, |
1385 | int *minimum_baseline, |
1386 | int *natural_baseline) |
1387 | { |
1388 | GtkWidget *widget = GTK_WIDGET (gizmo); |
1389 | GtkScale *scale = GTK_SCALE (gtk_widget_get_parent (widget));; |
1390 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1391 | GtkOrientation scale_orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (scale)); |
1392 | GSList *m; |
1393 | |
1394 | *minimum = *natural = 0; |
1395 | |
1396 | for (m = priv->marks; m; m = m->next) |
1397 | { |
1398 | GtkScaleMark *mark = m->data; |
1399 | int mark_size; |
1400 | |
1401 | if ((mark->position == GTK_POS_TOP && widget == priv->bottom_marks_widget) || |
1402 | (mark->position == GTK_POS_BOTTOM && widget == priv->top_marks_widget)) |
1403 | continue; |
1404 | |
1405 | gtk_widget_measure (widget: mark->widget, |
1406 | orientation, for_size: -1, |
1407 | minimum: &mark_size, NULL, |
1408 | NULL, NULL); |
1409 | |
1410 | if (scale_orientation == orientation) |
1411 | { |
1412 | *minimum += mark_size; |
1413 | *natural += mark_size; |
1414 | } |
1415 | else |
1416 | { |
1417 | *minimum = MAX (*minimum, mark_size); |
1418 | *natural = MAX (*natural, mark_size); |
1419 | } |
1420 | } |
1421 | } |
1422 | |
1423 | static void |
1424 | gtk_scale_measure (GtkWidget *widget, |
1425 | GtkOrientation orientation, |
1426 | int for_size, |
1427 | int *minimum, |
1428 | int *natural, |
1429 | int *minimum_baseline, |
1430 | int *natural_baseline) |
1431 | { |
1432 | GtkScale *scale = GTK_SCALE (widget); |
1433 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1434 | GtkOrientation scale_orientation; |
1435 | |
1436 | GTK_WIDGET_CLASS (gtk_scale_parent_class)->measure (widget, |
1437 | orientation, |
1438 | for_size, |
1439 | minimum, natural, |
1440 | minimum_baseline, natural_baseline); |
1441 | |
1442 | scale_orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)); |
1443 | |
1444 | if (scale_orientation == orientation) |
1445 | { |
1446 | int top_marks_size = 0, bottom_marks_size = 0, marks_size; |
1447 | |
1448 | if (priv->top_marks_widget) |
1449 | gtk_widget_measure (widget: priv->top_marks_widget, |
1450 | orientation, for_size, |
1451 | minimum: &top_marks_size, NULL, |
1452 | NULL, NULL); |
1453 | if (priv->bottom_marks_widget) |
1454 | gtk_widget_measure (widget: priv->bottom_marks_widget, |
1455 | orientation, for_size, |
1456 | minimum: &bottom_marks_size, NULL, |
1457 | NULL, NULL); |
1458 | |
1459 | marks_size = MAX (top_marks_size, bottom_marks_size); |
1460 | |
1461 | *minimum = MAX (*minimum, marks_size); |
1462 | *natural = MAX (*natural, marks_size); |
1463 | } |
1464 | |
1465 | if (priv->value_widget) |
1466 | { |
1467 | int min, nat; |
1468 | |
1469 | gtk_widget_measure (widget: priv->value_widget, orientation, for_size: -1, minimum: &min, natural: &nat, NULL, NULL); |
1470 | |
1471 | if (priv->value_pos == GTK_POS_TOP || |
1472 | priv->value_pos == GTK_POS_BOTTOM) |
1473 | { |
1474 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
1475 | { |
1476 | *minimum = MAX (*minimum, min); |
1477 | *natural = MAX (*natural, nat); |
1478 | } |
1479 | else |
1480 | { |
1481 | *minimum += min; |
1482 | *natural += nat; |
1483 | } |
1484 | } |
1485 | else |
1486 | { |
1487 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
1488 | { |
1489 | *minimum += min; |
1490 | *natural += nat; |
1491 | } |
1492 | else |
1493 | { |
1494 | *minimum = MAX (*minimum, min); |
1495 | *natural = MAX (*natural, nat); |
1496 | } |
1497 | } |
1498 | } |
1499 | } |
1500 | |
1501 | static void |
1502 | gtk_scale_real_get_layout_offsets (GtkScale *scale, |
1503 | int *x, |
1504 | int *y) |
1505 | { |
1506 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1507 | graphene_rect_t value_bounds; |
1508 | |
1509 | if (!priv->value_widget || |
1510 | !gtk_widget_compute_bounds (widget: priv->value_widget, GTK_WIDGET (scale), out_bounds: &value_bounds)) |
1511 | { |
1512 | *x = 0; |
1513 | *y = 0; |
1514 | |
1515 | return; |
1516 | } |
1517 | |
1518 | |
1519 | *x = value_bounds.origin.x; |
1520 | *y = value_bounds.origin.y; |
1521 | } |
1522 | |
1523 | static char * |
1524 | weed_out_neg_zero (char *str, |
1525 | int digits) |
1526 | { |
1527 | if (str[0] == '-') |
1528 | { |
1529 | char neg_zero[8]; |
1530 | g_snprintf (string: neg_zero, n: 8, format: "%0.*f" , digits, -0.0); |
1531 | if (strcmp (s1: neg_zero, s2: str) == 0) |
1532 | memmove (dest: str, src: str + 1, n: strlen (s: str)); |
1533 | } |
1534 | return str; |
1535 | } |
1536 | |
1537 | static char * |
1538 | gtk_scale_format_value (GtkScale *scale, |
1539 | double value) |
1540 | { |
1541 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1542 | |
1543 | if (priv->format_value_func) |
1544 | { |
1545 | return priv->format_value_func (scale, value, priv->format_value_func_user_data); |
1546 | } |
1547 | else |
1548 | { |
1549 | char *fmt = g_strdup_printf (format: "%0.*f" , priv->digits, value); |
1550 | return weed_out_neg_zero (str: fmt, digits: priv->digits); |
1551 | } |
1552 | } |
1553 | |
1554 | static void |
1555 | gtk_scale_finalize (GObject *object) |
1556 | { |
1557 | GtkScale *scale = GTK_SCALE (object); |
1558 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1559 | |
1560 | gtk_scale_clear_marks (scale); |
1561 | |
1562 | g_clear_pointer (&priv->value_widget, gtk_widget_unparent); |
1563 | |
1564 | if (priv->format_value_func_destroy_notify) |
1565 | priv->format_value_func_destroy_notify (priv->format_value_func_user_data); |
1566 | |
1567 | G_OBJECT_CLASS (gtk_scale_parent_class)->finalize (object); |
1568 | } |
1569 | |
1570 | /** |
1571 | * gtk_scale_get_layout: |
1572 | * @scale: A `GtkScale` |
1573 | * |
1574 | * Gets the `PangoLayout` used to display the scale. |
1575 | * |
1576 | * The returned object is owned by the scale so does not need |
1577 | * to be freed by the caller. |
1578 | * |
1579 | * Returns: (transfer none) (nullable): the [class@Pango.Layout] |
1580 | * for this scale, or %NULL if the [property@GtkScale:draw-value] |
1581 | * property is %FALSE. |
1582 | */ |
1583 | PangoLayout * |
1584 | gtk_scale_get_layout (GtkScale *scale) |
1585 | { |
1586 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1587 | |
1588 | g_return_val_if_fail (GTK_IS_SCALE (scale), NULL); |
1589 | |
1590 | if (priv->value_widget) |
1591 | return gtk_label_get_layout (GTK_LABEL (priv->value_widget)); |
1592 | |
1593 | return NULL; |
1594 | } |
1595 | |
1596 | /** |
1597 | * gtk_scale_get_layout_offsets: |
1598 | * @scale: a `GtkScale` |
1599 | * @x: (out) (optional): location to store X offset of layout |
1600 | * @y: (out) (optional): location to store Y offset of layout |
1601 | * |
1602 | * Obtains the coordinates where the scale will draw the |
1603 | * `PangoLayout` representing the text in the scale. |
1604 | * |
1605 | * Remember when using the `PangoLayout` function you need to |
1606 | * convert to and from pixels using `PANGO_PIXELS()` or `PANGO_SCALE`. |
1607 | * |
1608 | * If the [property@GtkScale:draw-value] property is %FALSE, the return |
1609 | * values are undefined. |
1610 | */ |
1611 | void |
1612 | gtk_scale_get_layout_offsets (GtkScale *scale, |
1613 | int *x, |
1614 | int *y) |
1615 | { |
1616 | int local_x = 0; |
1617 | int local_y = 0; |
1618 | |
1619 | g_return_if_fail (GTK_IS_SCALE (scale)); |
1620 | |
1621 | if (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets) |
1622 | (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets) (scale, &local_x, &local_y); |
1623 | |
1624 | if (x) |
1625 | *x = local_x; |
1626 | |
1627 | if (y) |
1628 | *y = local_y; |
1629 | } |
1630 | |
1631 | static void |
1632 | gtk_scale_mark_free (gpointer data) |
1633 | { |
1634 | GtkScaleMark *mark = data; |
1635 | |
1636 | if (mark->label_widget) |
1637 | gtk_widget_unparent (widget: mark->label_widget); |
1638 | |
1639 | gtk_widget_unparent (widget: mark->indicator_widget); |
1640 | gtk_widget_unparent (widget: mark->widget); |
1641 | g_free (mem: mark->markup); |
1642 | g_free (mem: mark); |
1643 | } |
1644 | |
1645 | /** |
1646 | * gtk_scale_clear_marks: |
1647 | * @scale: a `GtkScale` |
1648 | * |
1649 | * Removes any marks that have been added. |
1650 | */ |
1651 | void |
1652 | gtk_scale_clear_marks (GtkScale *scale) |
1653 | { |
1654 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1655 | |
1656 | g_return_if_fail (GTK_IS_SCALE (scale)); |
1657 | |
1658 | g_slist_free_full (list: priv->marks, free_func: gtk_scale_mark_free); |
1659 | priv->marks = NULL; |
1660 | |
1661 | g_clear_pointer (&priv->top_marks_widget, gtk_widget_unparent); |
1662 | g_clear_pointer (&priv->bottom_marks_widget, gtk_widget_unparent); |
1663 | |
1664 | gtk_widget_remove_css_class (GTK_WIDGET (scale), css_class: "marks-before" ); |
1665 | gtk_widget_remove_css_class (GTK_WIDGET (scale), css_class: "marks-after" ); |
1666 | |
1667 | _gtk_range_set_stop_values (GTK_RANGE (scale), NULL, n_values: 0); |
1668 | |
1669 | gtk_widget_queue_resize (GTK_WIDGET (scale)); |
1670 | } |
1671 | |
1672 | /** |
1673 | * gtk_scale_add_mark: |
1674 | * @scale: a `GtkScale` |
1675 | * @value: the value at which the mark is placed, must be between |
1676 | * the lower and upper limits of the scales’ adjustment |
1677 | * @position: where to draw the mark. For a horizontal scale, %GTK_POS_TOP |
1678 | * and %GTK_POS_LEFT are drawn above the scale, anything else below. |
1679 | * For a vertical scale, %GTK_POS_LEFT and %GTK_POS_TOP are drawn to |
1680 | * the left of the scale, anything else to the right. |
1681 | * @markup: (nullable): Text to be shown at the mark, using Pango markup |
1682 | * |
1683 | * Adds a mark at @value. |
1684 | * |
1685 | * A mark is indicated visually by drawing a tick mark next to the scale, |
1686 | * and GTK makes it easy for the user to position the scale exactly at the |
1687 | * marks value. |
1688 | * |
1689 | * If @markup is not %NULL, text is shown next to the tick mark. |
1690 | * |
1691 | * To remove marks from a scale, use [method@Gtk.Scale.clear_marks]. |
1692 | */ |
1693 | void |
1694 | gtk_scale_add_mark (GtkScale *scale, |
1695 | double value, |
1696 | GtkPositionType position, |
1697 | const char *markup) |
1698 | { |
1699 | GtkWidget *widget; |
1700 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
1701 | GtkScaleMark *mark; |
1702 | GSList *m; |
1703 | double *values; |
1704 | int n, i; |
1705 | GtkWidget *marks_widget; |
1706 | |
1707 | g_return_if_fail (GTK_IS_SCALE (scale)); |
1708 | |
1709 | widget = GTK_WIDGET (scale); |
1710 | |
1711 | mark = g_new0 (GtkScaleMark, 1); |
1712 | mark->value = value; |
1713 | mark->markup = g_strdup (str: markup); |
1714 | if (position == GTK_POS_LEFT || |
1715 | position == GTK_POS_TOP) |
1716 | mark->position = GTK_POS_TOP; |
1717 | else |
1718 | mark->position = GTK_POS_BOTTOM; |
1719 | |
1720 | priv->marks = g_slist_insert_sorted_with_data (list: priv->marks, data: mark, |
1721 | func: compare_marks, |
1722 | GINT_TO_POINTER (gtk_range_get_inverted (GTK_RANGE (scale)))); |
1723 | |
1724 | if (mark->position == GTK_POS_TOP) |
1725 | { |
1726 | if (!priv->top_marks_widget) |
1727 | { |
1728 | priv->top_marks_widget = gtk_gizmo_new_with_role (css_name: "marks" , |
1729 | role: GTK_ACCESSIBLE_ROLE_NONE, |
1730 | measure_func: gtk_scale_measure_marks, |
1731 | allocate_func: gtk_scale_allocate_marks, |
1732 | NULL, |
1733 | NULL, |
1734 | NULL, NULL); |
1735 | |
1736 | gtk_widget_insert_after (widget: priv->top_marks_widget, |
1737 | GTK_WIDGET (scale), |
1738 | previous_sibling: priv->value_widget); |
1739 | gtk_widget_add_css_class (widget: priv->top_marks_widget, css_class: "top" ); |
1740 | } |
1741 | marks_widget = priv->top_marks_widget; |
1742 | } |
1743 | else |
1744 | { |
1745 | if (!priv->bottom_marks_widget) |
1746 | { |
1747 | priv->bottom_marks_widget = gtk_gizmo_new_with_role (css_name: "marks" , |
1748 | role: GTK_ACCESSIBLE_ROLE_NONE, |
1749 | measure_func: gtk_scale_measure_marks, |
1750 | allocate_func: gtk_scale_allocate_marks, |
1751 | NULL, |
1752 | NULL, |
1753 | NULL, NULL); |
1754 | |
1755 | gtk_widget_insert_before (widget: priv->bottom_marks_widget, |
1756 | GTK_WIDGET (scale), |
1757 | next_sibling: gtk_range_get_trough_widget (GTK_RANGE (scale))); |
1758 | gtk_widget_add_css_class (widget: priv->bottom_marks_widget, css_class: "bottom" ); |
1759 | } |
1760 | marks_widget = priv->bottom_marks_widget; |
1761 | } |
1762 | |
1763 | mark->widget = gtk_gizmo_new (css_name: "mark" , measure_func: gtk_scale_measure_mark, allocate_func: gtk_scale_allocate_mark, NULL, NULL, NULL, NULL); |
1764 | g_object_set_data (G_OBJECT (mark->widget), key: "mark" , data: mark); |
1765 | |
1766 | mark->indicator_widget = gtk_gizmo_new (css_name: "indicator" , NULL, NULL, NULL, NULL, NULL, NULL); |
1767 | gtk_widget_set_parent (widget: mark->indicator_widget, parent: mark->widget); |
1768 | if (mark->markup && *mark->markup) |
1769 | { |
1770 | mark->label_widget = g_object_new (GTK_TYPE_LABEL, |
1771 | first_property_name: "use-markup" , TRUE, |
1772 | "label" , mark->markup, |
1773 | NULL); |
1774 | if (marks_widget == priv->top_marks_widget) |
1775 | gtk_widget_insert_after (widget: mark->label_widget, parent: mark->widget, NULL); |
1776 | else |
1777 | gtk_widget_insert_before (widget: mark->label_widget, parent: mark->widget, NULL); |
1778 | } |
1779 | |
1780 | m = g_slist_find (list: priv->marks, data: mark); |
1781 | m = m->next; |
1782 | while (m) |
1783 | { |
1784 | GtkScaleMark *next = m->data; |
1785 | if (next->position == mark->position) |
1786 | break; |
1787 | m = m->next; |
1788 | } |
1789 | |
1790 | if (m) |
1791 | { |
1792 | GtkScaleMark *next = m->data; |
1793 | gtk_widget_insert_before (widget: mark->widget, parent: marks_widget, next_sibling: next->widget); |
1794 | } |
1795 | else |
1796 | { |
1797 | gtk_widget_set_parent (widget: mark->widget, parent: marks_widget); |
1798 | } |
1799 | |
1800 | n = g_slist_length (list: priv->marks); |
1801 | values = g_new (double, n); |
1802 | for (m = priv->marks, i = 0; m; m = m->next, i++) |
1803 | { |
1804 | mark = m->data; |
1805 | values[i] = mark->value; |
1806 | } |
1807 | |
1808 | _gtk_range_set_stop_values (GTK_RANGE (scale), values, n_values: n); |
1809 | |
1810 | g_free (mem: values); |
1811 | |
1812 | if (priv->top_marks_widget) |
1813 | gtk_widget_add_css_class (GTK_WIDGET (scale), css_class: "marks-before" ); |
1814 | |
1815 | if (priv->bottom_marks_widget) |
1816 | gtk_widget_add_css_class (GTK_WIDGET (scale), css_class: "marks-after" ); |
1817 | |
1818 | gtk_widget_queue_resize (widget); |
1819 | } |
1820 | |
1821 | static GtkBuildableIface *parent_buildable_iface; |
1822 | |
1823 | static void |
1824 | gtk_scale_buildable_interface_init (GtkBuildableIface *iface) |
1825 | { |
1826 | parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface); |
1827 | iface->custom_tag_start = gtk_scale_buildable_custom_tag_start; |
1828 | iface->custom_finished = gtk_scale_buildable_custom_finished; |
1829 | } |
1830 | |
1831 | typedef struct |
1832 | { |
1833 | GtkScale *scale; |
1834 | GtkBuilder *builder; |
1835 | GSList *marks; |
1836 | } MarksSubparserData; |
1837 | |
1838 | typedef struct |
1839 | { |
1840 | double value; |
1841 | GtkPositionType position; |
1842 | GString *markup; |
1843 | char *context; |
1844 | gboolean translatable; |
1845 | } MarkData; |
1846 | |
1847 | static void |
1848 | mark_data_free (MarkData *data) |
1849 | { |
1850 | g_string_free (string: data->markup, TRUE); |
1851 | g_free (mem: data->context); |
1852 | g_slice_free (MarkData, data); |
1853 | } |
1854 | |
1855 | static void |
1856 | marks_start_element (GtkBuildableParseContext *context, |
1857 | const char *element_name, |
1858 | const char **names, |
1859 | const char **values, |
1860 | gpointer user_data, |
1861 | GError **error) |
1862 | { |
1863 | MarksSubparserData *data = (MarksSubparserData*)user_data; |
1864 | |
1865 | if (strcmp (s1: element_name, s2: "marks" ) == 0) |
1866 | { |
1867 | if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "object" , error)) |
1868 | return; |
1869 | |
1870 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
1871 | first_type: G_MARKUP_COLLECT_INVALID, NULL, NULL, |
1872 | G_MARKUP_COLLECT_INVALID)) |
1873 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
1874 | } |
1875 | else if (strcmp (s1: element_name, s2: "mark" ) == 0) |
1876 | { |
1877 | const char *value_str; |
1878 | double value = 0; |
1879 | const char *position_str = NULL; |
1880 | GtkPositionType position = GTK_POS_BOTTOM; |
1881 | const char *msg_context = NULL; |
1882 | gboolean translatable = FALSE; |
1883 | MarkData *mark; |
1884 | |
1885 | if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "marks" , error)) |
1886 | return; |
1887 | |
1888 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
1889 | first_type: G_MARKUP_COLLECT_STRING, first_attr: "value" , &value_str, |
1890 | G_MARKUP_COLLECT_BOOLEAN|G_MARKUP_COLLECT_OPTIONAL, "translatable" , &translatable, |
1891 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "comments" , NULL, |
1892 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "context" , &msg_context, |
1893 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "position" , &position_str, |
1894 | G_MARKUP_COLLECT_INVALID)) |
1895 | { |
1896 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
1897 | return; |
1898 | } |
1899 | |
1900 | if (value_str != NULL) |
1901 | { |
1902 | GValue gvalue = G_VALUE_INIT; |
1903 | |
1904 | if (!gtk_builder_value_from_string_type (builder: data->builder, G_TYPE_DOUBLE, string: value_str, value: &gvalue, error)) |
1905 | { |
1906 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
1907 | return; |
1908 | } |
1909 | |
1910 | value = g_value_get_double (value: &gvalue); |
1911 | } |
1912 | |
1913 | if (position_str != NULL) |
1914 | { |
1915 | GValue gvalue = G_VALUE_INIT; |
1916 | |
1917 | if (!gtk_builder_value_from_string_type (builder: data->builder, type: GTK_TYPE_POSITION_TYPE, string: position_str, value: &gvalue, error)) |
1918 | { |
1919 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
1920 | return; |
1921 | } |
1922 | |
1923 | position = g_value_get_enum (value: &gvalue); |
1924 | } |
1925 | |
1926 | mark = g_slice_new (MarkData); |
1927 | mark->value = value; |
1928 | if (position == GTK_POS_LEFT || position == GTK_POS_TOP) |
1929 | mark->position = GTK_POS_TOP; |
1930 | else |
1931 | mark->position = GTK_POS_BOTTOM; |
1932 | mark->markup = g_string_new (init: "" ); |
1933 | mark->context = g_strdup (str: msg_context); |
1934 | mark->translatable = translatable; |
1935 | |
1936 | data->marks = g_slist_prepend (list: data->marks, data: mark); |
1937 | } |
1938 | else |
1939 | { |
1940 | _gtk_builder_error_unhandled_tag (builder: data->builder, context, |
1941 | object: "GtkScale" , element_name, |
1942 | error); |
1943 | } |
1944 | } |
1945 | |
1946 | static void |
1947 | marks_text (GtkBuildableParseContext *context, |
1948 | const char *text, |
1949 | gsize text_len, |
1950 | gpointer user_data, |
1951 | GError **error) |
1952 | { |
1953 | MarksSubparserData *data = (MarksSubparserData*)user_data; |
1954 | |
1955 | if (strcmp (s1: gtk_buildable_parse_context_get_element (context), s2: "mark" ) == 0) |
1956 | { |
1957 | MarkData *mark = data->marks->data; |
1958 | |
1959 | g_string_append_len (string: mark->markup, val: text, len: text_len); |
1960 | } |
1961 | } |
1962 | |
1963 | static const GtkBuildableParser marks_parser = |
1964 | { |
1965 | marks_start_element, |
1966 | NULL, |
1967 | marks_text, |
1968 | }; |
1969 | |
1970 | |
1971 | static gboolean |
1972 | gtk_scale_buildable_custom_tag_start (GtkBuildable *buildable, |
1973 | GtkBuilder *builder, |
1974 | GObject *child, |
1975 | const char *tagname, |
1976 | GtkBuildableParser *parser, |
1977 | gpointer *parser_data) |
1978 | { |
1979 | MarksSubparserData *data; |
1980 | |
1981 | if (child) |
1982 | return FALSE; |
1983 | |
1984 | if (strcmp (s1: tagname, s2: "marks" ) == 0) |
1985 | { |
1986 | data = g_slice_new0 (MarksSubparserData); |
1987 | data->scale = GTK_SCALE (buildable); |
1988 | data->builder = builder; |
1989 | data->marks = NULL; |
1990 | |
1991 | *parser = marks_parser; |
1992 | *parser_data = data; |
1993 | |
1994 | return TRUE; |
1995 | } |
1996 | |
1997 | return parent_buildable_iface->custom_tag_start (buildable, builder, child, |
1998 | tagname, parser, parser_data); |
1999 | } |
2000 | |
2001 | static void |
2002 | gtk_scale_buildable_custom_finished (GtkBuildable *buildable, |
2003 | GtkBuilder *builder, |
2004 | GObject *child, |
2005 | const char *tagname, |
2006 | gpointer user_data) |
2007 | { |
2008 | GtkScale *scale = GTK_SCALE (buildable); |
2009 | MarksSubparserData *marks_data; |
2010 | |
2011 | if (strcmp (s1: tagname, s2: "marks" ) == 0) |
2012 | { |
2013 | GSList *m; |
2014 | const char *markup; |
2015 | |
2016 | marks_data = (MarksSubparserData *)user_data; |
2017 | |
2018 | for (m = marks_data->marks; m; m = m->next) |
2019 | { |
2020 | MarkData *mdata = m->data; |
2021 | |
2022 | if (mdata->translatable && mdata->markup->len) |
2023 | markup = _gtk_builder_parser_translate (domain: gtk_builder_get_translation_domain (builder), |
2024 | context: mdata->context, |
2025 | text: mdata->markup->str); |
2026 | else |
2027 | markup = mdata->markup->str; |
2028 | |
2029 | gtk_scale_add_mark (scale, value: mdata->value, position: mdata->position, markup); |
2030 | |
2031 | mark_data_free (data: mdata); |
2032 | } |
2033 | |
2034 | g_slist_free (list: marks_data->marks); |
2035 | g_slice_free (MarksSubparserData, marks_data); |
2036 | } |
2037 | else |
2038 | { |
2039 | parent_buildable_iface->custom_finished (buildable, builder, child, |
2040 | tagname, user_data); |
2041 | } |
2042 | |
2043 | } |
2044 | |
2045 | /** |
2046 | * gtk_scale_set_format_value_func: |
2047 | * @scale: a `GtkScale` |
2048 | * @func: (nullable): function that formats the value |
2049 | * @user_data: (closure): user data to pass to @func |
2050 | * @destroy_notify: (nullable): destroy function for @user_data |
2051 | * |
2052 | * @func allows you to change how the scale value is displayed. |
2053 | * |
2054 | * The given function will return an allocated string representing |
2055 | * @value. That string will then be used to display the scale's value. |
2056 | * |
2057 | * If #NULL is passed as @func, the value will be displayed on |
2058 | * its own, rounded according to the value of the |
2059 | * [property@GtkScale:digits] property. |
2060 | */ |
2061 | void |
2062 | gtk_scale_set_format_value_func (GtkScale *scale, |
2063 | GtkScaleFormatValueFunc func, |
2064 | gpointer user_data, |
2065 | GDestroyNotify destroy_notify) |
2066 | { |
2067 | GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale); |
2068 | GtkAdjustment *adjustment; |
2069 | char *text; |
2070 | |
2071 | g_return_if_fail (GTK_IS_SCALE (scale)); |
2072 | |
2073 | if (priv->format_value_func_destroy_notify) |
2074 | priv->format_value_func_destroy_notify (priv->format_value_func_user_data); |
2075 | |
2076 | priv->format_value_func = func; |
2077 | priv->format_value_func_user_data = user_data; |
2078 | priv->format_value_func_destroy_notify = destroy_notify; |
2079 | |
2080 | if (!priv->value_widget) |
2081 | return; |
2082 | |
2083 | update_label_request (scale); |
2084 | |
2085 | adjustment = gtk_range_get_adjustment (GTK_RANGE (scale)); |
2086 | text = gtk_scale_format_value (scale, |
2087 | value: gtk_adjustment_get_value (adjustment)); |
2088 | gtk_label_set_label (GTK_LABEL (priv->value_widget), str: text); |
2089 | |
2090 | g_free (mem: text); |
2091 | } |
2092 | |