1/* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18/*
19 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
20 * file for a list of people on the GTK+ Team. See the ChangeLog
21 * files for a list of changes. These files are distributed with
22 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23 */
24
25#include "config.h"
26
27#include "gtkprogressbar.h"
28
29#include "gtkboxlayout.h"
30#include "gtkgizmoprivate.h"
31#include "gtkintl.h"
32#include "gtklabel.h"
33#include "gtkorientable.h"
34#include "gtkprogresstrackerprivate.h"
35#include "gtkprivate.h"
36#include "gtkwidgetprivate.h"
37
38#include <string.h>
39
40/**
41 * GtkProgressBar:
42 *
43 * `GtkProgressBar` is typically used to display the progress of a long
44 * running operation.
45 *
46 * It provides a visual clue that processing is underway. `GtkProgressBar`
47 * can be used in two different modes: percentage mode and activity mode.
48 *
49 * ![An example GtkProgressBar](progressbar.png)
50 *
51 * When an application can determine how much work needs to take place
52 * (e.g. read a fixed number of bytes from a file) and can monitor its
53 * progress, it can use the `GtkProgressBar` in percentage mode and the
54 * user sees a growing bar indicating the percentage of the work that
55 * has been completed. In this mode, the application is required to call
56 * [method@Gtk.ProgressBar.set_fraction] periodically to update the progress bar.
57 *
58 * When an application has no accurate way of knowing the amount of work
59 * to do, it can use the `GtkProgressBar` in activity mode, which shows
60 * activity by a block moving back and forth within the progress area. In
61 * this mode, the application is required to call [method@Gtk.ProgressBar.pulse]
62 * periodically to update the progress bar.
63 *
64 * There is quite a bit of flexibility provided to control the appearance
65 * of the `GtkProgressBar`. Functions are provided to control the orientation
66 * of the bar, optional text can be displayed along with the bar, and the
67 * step size used in activity mode can be set.
68 *
69 * # CSS nodes
70 *
71 * ```
72 * progressbar[.osd]
73 * ├── [text]
74 * ╰── trough[.empty][.full]
75 * ╰── progress[.pulse]
76 * ```
77 *
78 * `GtkProgressBar` has a main CSS node with name progressbar and subnodes with
79 * names text and trough, of which the latter has a subnode named progress. The
80 * text subnode is only present if text is shown. The progress subnode has the
81 * style class .pulse when in activity mode. It gets the style classes .left,
82 * .right, .top or .bottom added when the progress 'touches' the corresponding
83 * end of the GtkProgressBar. The .osd class on the progressbar node is for use
84 * in overlays like the one Epiphany has for page loading progress.
85 *
86 * # Accessibility
87 *
88 * `GtkProgressBar` uses the %GTK_ACCESSIBLE_ROLE_PROGRESS_BAR role.
89 */
90
91typedef struct _GtkProgressBarClass GtkProgressBarClass;
92
93struct _GtkProgressBar
94{
95 GtkWidget parent_instance;
96
97 char *text;
98
99 GtkWidget *label;
100 GtkWidget *trough_widget;
101 GtkWidget *progress_widget;
102
103 double fraction;
104 double pulse_fraction;
105
106 double activity_pos;
107 guint activity_blocks;
108
109 GtkOrientation orientation;
110
111 guint tick_id;
112 GtkProgressTracker tracker;
113 gint64 pulse1;
114 gint64 pulse2;
115 double last_iteration;
116
117 guint activity_dir : 1;
118 guint activity_mode : 1;
119 guint ellipsize : 3;
120 guint show_text : 1;
121 guint inverted : 1;
122};
123
124struct _GtkProgressBarClass
125{
126 GtkWidgetClass parent_class;
127};
128
129enum {
130 PROP_0,
131 PROP_FRACTION,
132 PROP_PULSE_STEP,
133 PROP_INVERTED,
134 PROP_TEXT,
135 PROP_SHOW_TEXT,
136 PROP_ELLIPSIZE,
137 PROP_ORIENTATION,
138 NUM_PROPERTIES = PROP_ORIENTATION
139};
140
141static GParamSpec *progress_props[NUM_PROPERTIES] = { NULL, };
142
143static void gtk_progress_bar_set_property (GObject *object,
144 guint prop_id,
145 const GValue *value,
146 GParamSpec *pspec);
147static void gtk_progress_bar_get_property (GObject *object,
148 guint prop_id,
149 GValue *value,
150 GParamSpec *pspec);
151static void gtk_progress_bar_act_mode_enter (GtkProgressBar *progress);
152static void gtk_progress_bar_act_mode_leave (GtkProgressBar *progress);
153static void gtk_progress_bar_finalize (GObject *object);
154static void gtk_progress_bar_dispose (GObject *object);
155static void gtk_progress_bar_set_orientation (GtkProgressBar *progress,
156 GtkOrientation orientation);
157static void gtk_progress_bar_direction_changed (GtkWidget *widget,
158 GtkTextDirection previous_dir);
159
160G_DEFINE_TYPE_WITH_CODE (GtkProgressBar, gtk_progress_bar, GTK_TYPE_WIDGET,
161 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
162
163static void
164gtk_progress_bar_class_init (GtkProgressBarClass *class)
165{
166 GObjectClass *gobject_class;
167 GtkWidgetClass *widget_class;
168
169 gobject_class = G_OBJECT_CLASS (class);
170 widget_class = (GtkWidgetClass *) class;
171
172 gobject_class->set_property = gtk_progress_bar_set_property;
173 gobject_class->get_property = gtk_progress_bar_get_property;
174 gobject_class->dispose = gtk_progress_bar_dispose;
175 gobject_class->finalize = gtk_progress_bar_finalize;
176
177 widget_class->direction_changed = gtk_progress_bar_direction_changed;
178
179 g_object_class_override_property (oclass: gobject_class, property_id: PROP_ORIENTATION, name: "orientation");
180
181 /**
182 * GtkProgressBar:inverted: (attributes org.gtk.Property.get=gtk_progress_bar_get_inverted org.gtk.Property.set=gtk_progress_bar_set_inverted)
183 *
184 * Invert the direction in which the progress bar grows.
185 */
186 progress_props[PROP_INVERTED] =
187 g_param_spec_boolean (name: "inverted",
188 P_("Inverted"),
189 P_("Invert the direction in which the progress bar grows"),
190 FALSE,
191 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
192
193 /**
194 * GtkProgressBar:fraction: (attributes org.gtk.Property.get=gtk_progress_bar_get_fraction org.gtk.Property.set=gtk_progress_bar_set_fraction)
195 *
196 * The fraction of total work that has been completed.
197 */
198 progress_props[PROP_FRACTION] =
199 g_param_spec_double (name: "fraction",
200 P_("Fraction"),
201 P_("The fraction of total work that has been completed"),
202 minimum: 0.0, maximum: 1.0,
203 default_value: 0.0,
204 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
205
206 /**
207 * GtkProgressBar:pulse-step: (attributes org.gtk.Property.get=gtk_progress_bar_get_pulse_step org.gtk.Property.set=gtk_progress_bar_set_pulse_step)
208 *
209 * The fraction of total progress to move the bounding block when pulsed.
210 */
211 progress_props[PROP_PULSE_STEP] =
212 g_param_spec_double (name: "pulse-step",
213 P_("Pulse Step"),
214 P_("The fraction of total progress to move the bouncing block when pulsed"),
215 minimum: 0.0, maximum: 1.0,
216 default_value: 0.1,
217 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
218
219 /**
220 * GtkProgressBar:text: (attributes org.gtk.Property.get=gtk_progress_bar_get_text org.gtk.Property.set=gtk_progress_bar_set_text)
221 *
222 * Text to be displayed in the progress bar.
223 */
224 progress_props[PROP_TEXT] =
225 g_param_spec_string (name: "text",
226 P_("Text"),
227 P_("Text to be displayed in the progress bar"),
228 NULL,
229 GTK_PARAM_READWRITE);
230
231 /**
232 * GtkProgressBar:show-text: (attributes org.gtk.Property.get=gtk_progress_bar_get_show_text org.gtk.Property.set=gtk_progress_bar_set_show_text)
233 *
234 * Sets whether the progress bar will show a text in addition
235 * to the bar itself.
236 *
237 * The shown text is either the value of the [property@Gtk.ProgressBar:text]
238 * property or, if that is %NULL, the [property@Gtk.ProgressBar:fraction]
239 * value, as a percentage.
240 *
241 * To make a progress bar that is styled and sized suitably for showing text
242 * (even if the actual text is blank), set [property@Gtk.ProgressBar:show-text]
243 * to %TRUE and [property@Gtk.ProgressBar:text] to the empty string (not %NULL).
244 */
245 progress_props[PROP_SHOW_TEXT] =
246 g_param_spec_boolean (name: "show-text",
247 P_("Show text"),
248 P_("Whether the progress is shown as text."),
249 FALSE,
250 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
251
252 /**
253 * GtkProgressBar:ellipsize: (attributes org.gtk.Property.get=gtk_progress_bar_get_ellipsize org.gtk.Property.set=gtk_progress_bar_set_ellipsize)
254 *
255 * The preferred place to ellipsize the string.
256 *
257 * The text will be ellipsized if the progress bar does not have enough room
258 * to display the entire string, specified as a `PangoEllipsizeMode`.
259 *
260 * Note that setting this property to a value other than
261 * %PANGO_ELLIPSIZE_NONE has the side-effect that the progress bar requests
262 * only enough space to display the ellipsis ("..."). Another means to set a
263 * progress bar's width is [method@Gtk.Widget.set_size_request].
264 */
265 progress_props[PROP_ELLIPSIZE] =
266 g_param_spec_enum (name: "ellipsize",
267 P_("Ellipsize"),
268 P_("The preferred place to ellipsize the string, if the progress bar "
269 "does not have enough room to display the entire string, if at all."),
270 enum_type: PANGO_TYPE_ELLIPSIZE_MODE,
271 default_value: PANGO_ELLIPSIZE_NONE,
272 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
273
274 g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_PROPERTIES, pspecs: progress_props);
275
276 gtk_widget_class_set_css_name (widget_class, I_("progressbar"));
277 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
278 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_PROGRESS_BAR);
279}
280
281static void
282update_fraction_classes (GtkProgressBar *pbar)
283{
284 gboolean empty = FALSE;
285 gboolean full = FALSE;
286
287 /* Here we set classes based on fill-level unless we're in activity-mode.
288 */
289
290 if (!pbar->activity_mode)
291 {
292 if (pbar->fraction <= 0.0)
293 empty = TRUE;
294 else if (pbar->fraction >= 1.0)
295 full = TRUE;
296 }
297
298 if (empty)
299 gtk_widget_add_css_class (widget: pbar->trough_widget, css_class: "empty");
300 else
301 gtk_widget_remove_css_class (widget: pbar->trough_widget, css_class: "empty");
302
303 if (full)
304 gtk_widget_add_css_class (widget: pbar->trough_widget, css_class: "full");
305 else
306 gtk_widget_remove_css_class (widget: pbar->trough_widget, css_class: "full");
307}
308
309static void
310update_node_classes (GtkProgressBar *pbar)
311{
312 gboolean left = FALSE;
313 gboolean right = FALSE;
314 gboolean top = FALSE;
315 gboolean bottom = FALSE;
316
317 /* Here we set positional classes, depending on which end of the
318 * progressbar the progress touches.
319 */
320
321 if (pbar->activity_mode)
322 {
323 if (pbar->orientation == GTK_ORIENTATION_HORIZONTAL)
324 {
325 left = pbar->activity_pos <= 0.0;
326 right = pbar->activity_pos >= 1.0;
327 }
328 else
329 {
330 top = pbar->activity_pos <= 0.0;
331 bottom = pbar->activity_pos >= 1.0;
332 }
333 }
334 else /* continuous */
335 {
336 gboolean inverted;
337
338 inverted = pbar->inverted;
339 if (gtk_widget_get_direction (GTK_WIDGET (pbar)) == GTK_TEXT_DIR_RTL)
340 {
341 if (pbar->orientation == GTK_ORIENTATION_HORIZONTAL)
342 inverted = !inverted;
343 }
344
345 if (pbar->orientation == GTK_ORIENTATION_HORIZONTAL)
346 {
347 left = !inverted || pbar->fraction >= 1.0;
348 right = inverted || pbar->fraction >= 1.0;
349 }
350 else
351 {
352 top = !inverted || pbar->fraction >= 1.0;
353 bottom = inverted || pbar->fraction >= 1.0;
354 }
355 }
356
357 if (left)
358 gtk_widget_add_css_class (widget: pbar->progress_widget, css_class: "left");
359 else
360 gtk_widget_remove_css_class (widget: pbar->progress_widget, css_class: "left");
361
362 if (right)
363 gtk_widget_add_css_class (widget: pbar->progress_widget, css_class: "right");
364 else
365 gtk_widget_remove_css_class (widget: pbar->progress_widget, css_class: "right");
366
367 if (top)
368 gtk_widget_add_css_class (widget: pbar->progress_widget, css_class: "top");
369 else
370 gtk_widget_remove_css_class (widget: pbar->progress_widget, css_class: "top");
371
372 if (bottom)
373 gtk_widget_add_css_class (widget: pbar->progress_widget, css_class: "bottom");
374 else
375 gtk_widget_remove_css_class (widget: pbar->progress_widget, css_class: "bottom");
376
377 update_fraction_classes (pbar);
378}
379
380static void
381allocate_trough (GtkGizmo *gizmo,
382 int width,
383 int height,
384 int baseline)
385
386{
387 GtkProgressBar *pbar = GTK_PROGRESS_BAR (gtk_widget_get_parent (GTK_WIDGET (gizmo)));
388 GtkAllocation alloc;
389 int progress_width, progress_height;
390 gboolean inverted;
391
392 inverted = pbar->inverted;
393 if (gtk_widget_get_direction (GTK_WIDGET (pbar)) == GTK_TEXT_DIR_RTL)
394 {
395 if (pbar->orientation == GTK_ORIENTATION_HORIZONTAL)
396 inverted = !inverted;
397 }
398
399 gtk_widget_measure (widget: pbar->progress_widget, orientation: GTK_ORIENTATION_VERTICAL, for_size: -1,
400 minimum: &progress_height, NULL,
401 NULL, NULL);
402
403 gtk_widget_measure (widget: pbar->progress_widget, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
404 minimum: &progress_width, NULL,
405 NULL, NULL);
406
407 if (pbar->activity_mode)
408 {
409 if (pbar->orientation == GTK_ORIENTATION_HORIZONTAL)
410 {
411 alloc.width = progress_width + (width - progress_width) / pbar->activity_blocks;
412 alloc.x = pbar->activity_pos * (width - alloc.width);
413 alloc.y = (height - progress_height) / 2;
414 alloc.height = progress_height;
415 }
416 else
417 {
418
419 alloc.height = progress_height + (height - progress_height) / pbar->activity_blocks;
420 alloc.y = pbar->activity_pos * (height - alloc.height);
421 alloc.x = (width - progress_width) / 2;
422 alloc.width = progress_width;
423 }
424 }
425 else
426 {
427 if (pbar->orientation == GTK_ORIENTATION_HORIZONTAL)
428 {
429 alloc.width = progress_width + (width - progress_width) * pbar->fraction;
430 alloc.height = progress_height;
431 alloc.y = (height - progress_height) / 2;
432
433 if (!inverted)
434 alloc.x = 0;
435 else
436 alloc.x = width - alloc.width;
437 }
438 else
439 {
440 alloc.width = progress_width;
441 alloc.height = progress_height + (height - progress_height) * pbar->fraction;
442 alloc.x = (width - progress_width) / 2;
443
444 if (!inverted)
445 alloc.y = 0;
446 else
447 alloc.y = height - alloc.height;
448 }
449 }
450
451 gtk_widget_size_allocate (widget: pbar->progress_widget, allocation: &alloc, baseline: -1);
452
453}
454
455static void
456gtk_progress_bar_init (GtkProgressBar *pbar)
457{
458 pbar->inverted = FALSE;
459 pbar->pulse_fraction = 0.1;
460 pbar->activity_pos = 0;
461 pbar->activity_dir = 1;
462 pbar->activity_blocks = 5;
463 pbar->ellipsize = PANGO_ELLIPSIZE_NONE;
464 pbar->show_text = FALSE;
465
466 pbar->text = NULL;
467 pbar->fraction = 0.0;
468
469 pbar->trough_widget = gtk_gizmo_new_with_role (css_name: "trough",
470 role: GTK_ACCESSIBLE_ROLE_NONE,
471 NULL,
472 allocate_func: allocate_trough,
473 NULL,
474 NULL,
475 NULL, NULL);
476 gtk_widget_set_parent (widget: pbar->trough_widget, GTK_WIDGET (pbar));
477
478 pbar->progress_widget = gtk_gizmo_new_with_role (css_name: "progress",
479 role: GTK_ACCESSIBLE_ROLE_NONE,
480 NULL, NULL, NULL, NULL, NULL, NULL);
481 gtk_widget_set_parent (widget: pbar->progress_widget, parent: pbar->trough_widget);
482
483 /* horizontal is default */
484 pbar->orientation = GTK_ORIENTATION_VERTICAL; /* Just to force an update... */
485 gtk_progress_bar_set_orientation (progress: pbar, orientation: GTK_ORIENTATION_HORIZONTAL);
486 gtk_widget_update_orientation (GTK_WIDGET (pbar), orientation: pbar->orientation);
487
488 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: pbar),
489 first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, 1.0,
490 GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, 0.0,
491 GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, 0.0,
492 -1);
493}
494
495static void
496gtk_progress_bar_set_property (GObject *object,
497 guint prop_id,
498 const GValue *value,
499 GParamSpec *pspec)
500{
501 GtkProgressBar *pbar;
502
503 pbar = GTK_PROGRESS_BAR (object);
504
505 switch (prop_id)
506 {
507 case PROP_ORIENTATION:
508 gtk_progress_bar_set_orientation (progress: pbar, orientation: g_value_get_enum (value));
509 break;
510 case PROP_INVERTED:
511 gtk_progress_bar_set_inverted (pbar, inverted: g_value_get_boolean (value));
512 break;
513 case PROP_FRACTION:
514 gtk_progress_bar_set_fraction (pbar, fraction: g_value_get_double (value));
515 break;
516 case PROP_PULSE_STEP:
517 gtk_progress_bar_set_pulse_step (pbar, fraction: g_value_get_double (value));
518 break;
519 case PROP_TEXT:
520 gtk_progress_bar_set_text (pbar, text: g_value_get_string (value));
521 break;
522 case PROP_SHOW_TEXT:
523 gtk_progress_bar_set_show_text (pbar, show_text: g_value_get_boolean (value));
524 break;
525 case PROP_ELLIPSIZE:
526 gtk_progress_bar_set_ellipsize (pbar, mode: g_value_get_enum (value));
527 break;
528 default:
529 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
530 break;
531 }
532}
533
534static void
535gtk_progress_bar_get_property (GObject *object,
536 guint prop_id,
537 GValue *value,
538 GParamSpec *pspec)
539{
540 GtkProgressBar *pbar = GTK_PROGRESS_BAR (object);
541
542 switch (prop_id)
543 {
544 case PROP_ORIENTATION:
545 g_value_set_enum (value, v_enum: pbar->orientation);
546 break;
547 case PROP_INVERTED:
548 g_value_set_boolean (value, v_boolean: pbar->inverted);
549 break;
550 case PROP_FRACTION:
551 g_value_set_double (value, v_double: pbar->fraction);
552 break;
553 case PROP_PULSE_STEP:
554 g_value_set_double (value, v_double: pbar->pulse_fraction);
555 break;
556 case PROP_TEXT:
557 g_value_set_string (value, v_string: pbar->text);
558 break;
559 case PROP_SHOW_TEXT:
560 g_value_set_boolean (value, v_boolean: pbar->show_text);
561 break;
562 case PROP_ELLIPSIZE:
563 g_value_set_enum (value, v_enum: pbar->ellipsize);
564 break;
565 default:
566 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
567 break;
568 }
569}
570
571/**
572 * gtk_progress_bar_new:
573 *
574 * Creates a new `GtkProgressBar`.
575 *
576 * Returns: a `GtkProgressBar`.
577 */
578GtkWidget*
579gtk_progress_bar_new (void)
580{
581 GtkWidget *pbar;
582
583 pbar = g_object_new (GTK_TYPE_PROGRESS_BAR, NULL);
584
585 return pbar;
586}
587
588static void
589gtk_progress_bar_dispose (GObject *object)
590{
591 GtkProgressBar *pbar = GTK_PROGRESS_BAR (object);
592
593 if (pbar->activity_mode)
594 gtk_progress_bar_act_mode_leave (progress: pbar);
595
596 g_clear_pointer (&pbar->label, gtk_widget_unparent);
597 g_clear_pointer (&pbar->progress_widget, gtk_widget_unparent);
598 g_clear_pointer (&pbar->trough_widget, gtk_widget_unparent);
599
600 G_OBJECT_CLASS (gtk_progress_bar_parent_class)->dispose (object);
601}
602
603static void
604gtk_progress_bar_finalize (GObject *object)
605{
606 GtkProgressBar *pbar = GTK_PROGRESS_BAR (object);
607
608 g_free (mem: pbar->text);
609
610 G_OBJECT_CLASS (gtk_progress_bar_parent_class)->finalize (object);
611}
612
613static char *
614get_current_text (GtkProgressBar *pbar)
615{
616 if (pbar->text)
617 return g_strdup (str: pbar->text);
618 else
619 return g_strdup_printf (C_("progress bar label", "%.0f %%"), pbar->fraction * 100.0);
620}
621
622static gboolean
623tick_cb (GtkWidget *widget,
624 GdkFrameClock *frame_clock,
625 gpointer user_data)
626{
627 GtkProgressBar *pbar = GTK_PROGRESS_BAR (widget);
628 gint64 frame_time;
629 double iteration, pulse_iterations, current_iterations, fraction;
630
631 if (pbar->pulse2 == 0 && pbar->pulse1 == 0)
632 return G_SOURCE_CONTINUE;
633
634 frame_time = gdk_frame_clock_get_frame_time (frame_clock);
635 gtk_progress_tracker_advance_frame (tracker: &pbar->tracker, frame_time);
636
637 g_assert (pbar->pulse2 > pbar->pulse1);
638
639 pulse_iterations = (pbar->pulse2 - pbar->pulse1) / (double) G_USEC_PER_SEC;
640 current_iterations = (frame_time - pbar->pulse1) / (double) G_USEC_PER_SEC;
641
642 iteration = gtk_progress_tracker_get_iteration (tracker: &pbar->tracker);
643 /* Determine the fraction to move the block from one frame
644 * to the next when pulse_fraction is how far the block should
645 * move between two calls to gtk_progress_bar_pulse().
646 */
647 fraction = pbar->pulse_fraction * (iteration - pbar->last_iteration) / MAX (pulse_iterations, current_iterations);
648 pbar->last_iteration = iteration;
649
650 if (current_iterations > 3 * pulse_iterations)
651 return G_SOURCE_CONTINUE;
652
653 /* advance the block */
654 if (pbar->activity_dir == 0)
655 {
656 pbar->activity_pos += fraction;
657 if (pbar->activity_pos > 1.0)
658 {
659 pbar->activity_pos = 1.0;
660 pbar->activity_dir = 1;
661 }
662 }
663 else
664 {
665 pbar->activity_pos -= fraction;
666 if (pbar->activity_pos <= 0)
667 {
668 pbar->activity_pos = 0;
669 pbar->activity_dir = 0;
670 }
671 }
672
673 update_node_classes (pbar);
674
675 gtk_widget_queue_allocate (widget: pbar->trough_widget);
676
677 return G_SOURCE_CONTINUE;
678}
679
680static void
681gtk_progress_bar_act_mode_enter (GtkProgressBar *pbar)
682{
683 GtkWidget *widget = GTK_WIDGET (pbar);
684 gboolean inverted;
685
686 gtk_widget_add_css_class (widget: pbar->progress_widget, css_class: "pulse");
687 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: pbar),
688 first_state: GTK_ACCESSIBLE_STATE_BUSY, TRUE,
689 -1);
690
691 inverted = pbar->inverted;
692 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
693 {
694 if (pbar->orientation == GTK_ORIENTATION_HORIZONTAL)
695 inverted = !inverted;
696 }
697
698 /* calculate start pos */
699 if (!inverted)
700 {
701 pbar->activity_pos = 0.0;
702 pbar->activity_dir = 0;
703 }
704 else
705 {
706 pbar->activity_pos = 1.0;
707 pbar->activity_dir = 1;
708 }
709
710 update_node_classes (pbar);
711 /* No fixed schedule for pulses, will adapt after calls to update_pulse. Just
712 * start the tracker to repeat forever with iterations every second.*/
713 gtk_progress_tracker_start (tracker: &pbar->tracker, G_USEC_PER_SEC, delay: 0, INFINITY);
714 pbar->tick_id = gtk_widget_add_tick_callback (widget, callback: tick_cb, NULL, NULL);
715 pbar->pulse2 = 0;
716 pbar->pulse1 = 0;
717 pbar->last_iteration = 0;
718}
719
720static void
721gtk_progress_bar_act_mode_leave (GtkProgressBar *pbar)
722{
723 if (pbar->tick_id)
724 gtk_widget_remove_tick_callback (GTK_WIDGET (pbar), id: pbar->tick_id);
725 pbar->tick_id = 0;
726
727 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: pbar),
728 first_state: GTK_ACCESSIBLE_STATE_BUSY, FALSE,
729 -1);
730 gtk_widget_remove_css_class (widget: pbar->progress_widget, css_class: "pulse");
731 update_node_classes (pbar);
732}
733
734static void
735gtk_progress_bar_set_activity_mode (GtkProgressBar *pbar,
736 gboolean activity_mode)
737{
738 if (pbar->activity_mode == activity_mode)
739 return;
740
741 pbar->activity_mode = activity_mode;
742
743 if (pbar->activity_mode)
744 gtk_progress_bar_act_mode_enter (pbar);
745 else
746 gtk_progress_bar_act_mode_leave (pbar);
747
748 gtk_widget_queue_resize (GTK_WIDGET (pbar));
749}
750
751/**
752 * gtk_progress_bar_set_fraction: (attributes org.gtk.Method.set_property=fraction)
753 * @pbar: a `GtkProgressBar`
754 * @fraction: fraction of the task that’s been completed
755 *
756 * Causes the progress bar to “fill in” the given fraction
757 * of the bar.
758 *
759 * The fraction should be between 0.0 and 1.0, inclusive.
760 */
761void
762gtk_progress_bar_set_fraction (GtkProgressBar *pbar,
763 double fraction)
764{
765 char *text = NULL;
766
767 g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
768
769 pbar->fraction = CLAMP (fraction, 0.0, 1.0);
770
771 if (pbar->label)
772 {
773 text = get_current_text (pbar);
774 gtk_label_set_label (GTK_LABEL (pbar->label), str: text);
775 }
776
777 gtk_progress_bar_set_activity_mode (pbar, FALSE);
778 gtk_widget_queue_allocate (widget: pbar->trough_widget);
779 update_fraction_classes (pbar);
780
781 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: pbar),
782 first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, 1.0,
783 GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, 0.0,
784 GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, fraction,
785 -1);
786
787 if (text != NULL)
788 {
789 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: pbar),
790 first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT, text,
791 -1);
792 }
793 else
794 {
795 gtk_accessible_reset_property (self: GTK_ACCESSIBLE (ptr: pbar), property: GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT);
796 }
797
798 g_free (mem: text);
799
800 g_object_notify_by_pspec (G_OBJECT (pbar), pspec: progress_props[PROP_FRACTION]);
801}
802
803static void
804gtk_progress_bar_update_pulse (GtkProgressBar *pbar)
805{
806 gint64 pulse_time = g_get_monotonic_time ();
807
808 if (pbar->pulse2 == pulse_time)
809 return;
810
811 pbar->pulse1 = pbar->pulse2;
812 pbar->pulse2 = pulse_time;
813}
814
815/**
816 * gtk_progress_bar_pulse:
817 * @pbar: a `GtkProgressBar`
818 *
819 * Indicates that some progress has been made, but you don’t know how much.
820 *
821 * Causes the progress bar to enter “activity mode,” where a block
822 * bounces back and forth. Each call to [method@Gtk.ProgressBar.pulse]
823 * causes the block to move by a little bit (the amount of movement
824 * per pulse is determined by [method@Gtk.ProgressBar.set_pulse_step]).
825 */
826void
827gtk_progress_bar_pulse (GtkProgressBar *pbar)
828{
829 g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
830
831 gtk_progress_bar_set_activity_mode (pbar, TRUE);
832 gtk_progress_bar_update_pulse (pbar);
833}
834
835/**
836 * gtk_progress_bar_set_text: (attributes org.gtk.Method.set_property=text)
837 * @pbar: a `GtkProgressBar`
838 * @text: (nullable): a UTF-8 string
839 *
840 * Causes the given @text to appear next to the progress bar.
841 *
842 * If @text is %NULL and [property@Gtk.ProgressBar:show-text] is %TRUE,
843 * the current value of [property@Gtk.ProgressBar:fraction] will be displayed
844 * as a percentage.
845 *
846 * If @text is non-%NULL and [property@Gtk.ProgressBar:show-text] is %TRUE,
847 * the text will be displayed. In this case, it will not display the progress
848 * percentage. If @text is the empty string, the progress bar will still
849 * be styled and sized suitably for containing text, as long as
850 * [property@Gtk.ProgressBar:show-text] is %TRUE.
851 */
852void
853gtk_progress_bar_set_text (GtkProgressBar *pbar,
854 const char *text)
855{
856 g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
857
858 /* Don't notify again if nothing's changed. */
859 if (g_strcmp0 (str1: pbar->text, str2: text) == 0)
860 return;
861
862 g_free (mem: pbar->text);
863 pbar->text = g_strdup (str: text);
864
865 if (pbar->label)
866 gtk_label_set_label (GTK_LABEL (pbar->label), str: text);
867
868 g_object_notify_by_pspec (G_OBJECT (pbar), pspec: progress_props[PROP_TEXT]);
869}
870
871/**
872 * gtk_progress_bar_set_show_text: (attributes org.gtk.Method.set_property=show-text)
873 * @pbar: a `GtkProgressBar`
874 * @show_text: whether to show text
875 *
876 * Sets whether the progress bar will show text next to the bar.
877 *
878 * The shown text is either the value of the [property@Gtk.ProgressBar:text]
879 * property or, if that is %NULL, the [property@Gtk.ProgressBar:fraction] value,
880 * as a percentage.
881 *
882 * To make a progress bar that is styled and sized suitably for containing
883 * text (even if the actual text is blank), set [property@Gtk.ProgressBar:show-text]
884 * to %TRUE and [property@Gtk.ProgressBar:text] to the empty string (not %NULL).
885 */
886void
887gtk_progress_bar_set_show_text (GtkProgressBar *pbar,
888 gboolean show_text)
889{
890 g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
891
892 show_text = !!show_text;
893
894 if (pbar->show_text == show_text)
895 return;
896
897 pbar->show_text = show_text;
898
899 if (show_text)
900 {
901 char *text = get_current_text (pbar);
902
903 pbar->label = g_object_new (GTK_TYPE_LABEL,
904 first_property_name: "accessible-role", GTK_ACCESSIBLE_ROLE_NONE,
905 "css-name", "text",
906 "label", text,
907 "ellipsize", pbar->ellipsize,
908 NULL);
909 gtk_widget_insert_after (widget: pbar->label, GTK_WIDGET (pbar), NULL);
910
911 g_free (mem: text);
912 }
913 else
914 {
915 g_clear_pointer (&pbar->label, gtk_widget_unparent);
916 }
917
918 g_object_notify_by_pspec (G_OBJECT (pbar), pspec: progress_props[PROP_SHOW_TEXT]);
919}
920
921/**
922 * gtk_progress_bar_get_show_text: (attributes org.gtk.Method.get_property=show-text)
923 * @pbar: a `GtkProgressBar`
924 *
925 * Returns whether the `GtkProgressBar` shows text.
926 *
927 * See [method@Gtk.ProgressBar.set_show_text].
928 *
929 * Returns: %TRUE if text is shown in the progress bar
930 */
931gboolean
932gtk_progress_bar_get_show_text (GtkProgressBar *pbar)
933{
934 g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), FALSE);
935
936 return pbar->show_text;
937}
938
939/**
940 * gtk_progress_bar_set_pulse_step: (attributes org.gtk.Method.set_property=pulse-step)
941 * @pbar: a `GtkProgressBar`
942 * @fraction: fraction between 0.0 and 1.0
943 *
944 * Sets the fraction of total progress bar length to move the
945 * bouncing block.
946 *
947 * The bouncing block is moved when [method@Gtk.ProgressBar.pulse]
948 * is called.
949 */
950void
951gtk_progress_bar_set_pulse_step (GtkProgressBar *pbar,
952 double fraction)
953{
954 g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
955
956 pbar->pulse_fraction = fraction;
957
958 g_object_notify_by_pspec (G_OBJECT (pbar), pspec: progress_props[PROP_PULSE_STEP]);
959}
960
961static void
962gtk_progress_bar_direction_changed (GtkWidget *widget,
963 GtkTextDirection previous_dir)
964{
965 GtkProgressBar *pbar = GTK_PROGRESS_BAR (widget);
966
967 update_node_classes (pbar);
968
969 GTK_WIDGET_CLASS (gtk_progress_bar_parent_class)->direction_changed (widget, previous_dir);
970}
971
972static void
973gtk_progress_bar_set_orientation (GtkProgressBar *pbar,
974 GtkOrientation orientation)
975{
976 GtkBoxLayout *layout;
977
978 if (pbar->orientation == orientation)
979 return;
980
981 pbar->orientation = orientation;
982
983 if (orientation == GTK_ORIENTATION_HORIZONTAL)
984 {
985 gtk_widget_set_vexpand (widget: pbar->trough_widget, FALSE);
986 gtk_widget_set_hexpand (widget: pbar->trough_widget, TRUE);
987 gtk_widget_set_halign (widget: pbar->trough_widget, align: GTK_ALIGN_FILL);
988 gtk_widget_set_valign (widget: pbar->trough_widget, align: GTK_ALIGN_CENTER);
989 }
990 else
991 {
992 gtk_widget_set_vexpand (widget: pbar->trough_widget, TRUE);
993 gtk_widget_set_hexpand (widget: pbar->trough_widget, FALSE);
994 gtk_widget_set_halign (widget: pbar->trough_widget, align: GTK_ALIGN_CENTER);
995 gtk_widget_set_valign (widget: pbar->trough_widget, align: GTK_ALIGN_FILL);
996 }
997
998 gtk_widget_update_orientation (GTK_WIDGET (pbar), orientation: pbar->orientation);
999 update_node_classes (pbar);
1000
1001 layout = GTK_BOX_LAYOUT (ptr: gtk_widget_get_layout_manager (GTK_WIDGET (pbar)));
1002 gtk_orientable_set_orientation (GTK_ORIENTABLE (layout), orientation: GTK_ORIENTATION_VERTICAL);
1003
1004 g_object_notify (G_OBJECT (pbar), property_name: "orientation");
1005}
1006
1007/**
1008 * gtk_progress_bar_set_inverted: (attributes org.gtk.Method.set_property=inverted)
1009 * @pbar: a `GtkProgressBar`
1010 * @inverted: %TRUE to invert the progress bar
1011 *
1012 * Sets whether the progress bar is inverted.
1013 *
1014 * Progress bars normally grow from top to bottom or left to right.
1015 * Inverted progress bars grow in the opposite direction.
1016 */
1017void
1018gtk_progress_bar_set_inverted (GtkProgressBar *pbar,
1019 gboolean inverted)
1020{
1021 g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1022
1023 if (pbar->inverted == inverted)
1024 return;
1025
1026 pbar->inverted = inverted;
1027
1028 gtk_widget_queue_allocate (widget: pbar->trough_widget);
1029 update_node_classes (pbar);
1030
1031 g_object_notify_by_pspec (G_OBJECT (pbar), pspec: progress_props[PROP_INVERTED]);
1032}
1033
1034/**
1035 * gtk_progress_bar_get_text: (attributes org.gtk.Method.get_property=text)
1036 * @pbar: a `GtkProgressBar`
1037 *
1038 * Retrieves the text that is displayed with the progress bar.
1039 *
1040 * The return value is a reference to the text, not a copy of it,
1041 * so will become invalid if you change the text in the progress bar.
1042 *
1043 * Returns: (nullable): the text
1044 */
1045const char *
1046gtk_progress_bar_get_text (GtkProgressBar *pbar)
1047{
1048 g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), NULL);
1049
1050 return pbar->text;
1051}
1052
1053/**
1054 * gtk_progress_bar_get_fraction: (attributes org.gtk.Method.get_property=fraction)
1055 * @pbar: a `GtkProgressBar`
1056 *
1057 * Returns the current fraction of the task that’s been completed.
1058 *
1059 * Returns: a fraction from 0.0 to 1.0
1060 */
1061double
1062gtk_progress_bar_get_fraction (GtkProgressBar *pbar)
1063{
1064 g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), 0);
1065
1066 return pbar->fraction;
1067}
1068
1069/**
1070 * gtk_progress_bar_get_pulse_step: (attributes org.gtk.Method.get_property=pulse-step)
1071 * @pbar: a `GtkProgressBar`
1072 *
1073 * Retrieves the pulse step.
1074 *
1075 * See [method@Gtk.ProgressBar.set_pulse_step].
1076 *
1077 * Returns: a fraction from 0.0 to 1.0
1078 */
1079double
1080gtk_progress_bar_get_pulse_step (GtkProgressBar *pbar)
1081{
1082 g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), 0);
1083
1084 return pbar->pulse_fraction;
1085}
1086
1087/**
1088 * gtk_progress_bar_get_inverted: (attributes org.gtk.Method.get_property=inverted)
1089 * @pbar: a `GtkProgressBar`
1090 *
1091 * Returns whether the progress bar is inverted.
1092 *
1093 * Returns: %TRUE if the progress bar is inverted
1094 */
1095gboolean
1096gtk_progress_bar_get_inverted (GtkProgressBar *pbar)
1097{
1098 g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), FALSE);
1099
1100 return pbar->inverted;
1101}
1102
1103/**
1104 * gtk_progress_bar_set_ellipsize: (attributes org.gtk.Method.set_property=ellipsize)
1105 * @pbar: a `GtkProgressBar`
1106 * @mode: a `PangoEllipsizeMode`
1107 *
1108 * Sets the mode used to ellipsize the text.
1109 *
1110 * The text is ellipsized if there is not enough space
1111 * to render the entire string.
1112 */
1113void
1114gtk_progress_bar_set_ellipsize (GtkProgressBar *pbar,
1115 PangoEllipsizeMode mode)
1116{
1117 g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1118 g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE &&
1119 mode <= PANGO_ELLIPSIZE_END);
1120
1121 if ((PangoEllipsizeMode)pbar->ellipsize == mode)
1122 return;
1123
1124 pbar->ellipsize = mode;
1125
1126 if (pbar->label)
1127 gtk_label_set_ellipsize (GTK_LABEL (pbar->label), mode);
1128
1129 g_object_notify_by_pspec (G_OBJECT (pbar), pspec: progress_props[PROP_ELLIPSIZE]);
1130}
1131
1132/**
1133 * gtk_progress_bar_get_ellipsize: (attributes org.gtk.Method.get_property=ellipsize)
1134 * @pbar: a `GtkProgressBar`
1135 *
1136 * Returns the ellipsizing position of the progress bar.
1137 *
1138 * See [method@Gtk.ProgressBar.set_ellipsize].
1139 *
1140 * Returns: `PangoEllipsizeMode`
1141 */
1142PangoEllipsizeMode
1143gtk_progress_bar_get_ellipsize (GtkProgressBar *pbar)
1144{
1145 g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), PANGO_ELLIPSIZE_NONE);
1146
1147 return pbar->ellipsize;
1148}
1149

source code of gtk/gtk/gtkprogressbar.c