]> Pileus Git - ~andy/gtk/blob - gtk/gtkprogressbar.c
34e70007607c54040a2487f9093ec5f0bd191bdc
[~andy/gtk] / gtk / gtkprogressbar.c
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, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22  * file for a list of people on the GTK+ Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
25  */
26
27 #include "config.h"
28
29 #include <string.h>
30
31 #include "gtkprogressbar.h"
32 #include "gtkorientableprivate.h"
33 #include "gtkprivate.h"
34 #include "gtkintl.h"
35
36 #include "a11y/gtkprogressbaraccessible.h"
37
38 /**
39  * SECTION:gtkprogressbar
40  * @Short_description: A widget which indicates progress visually
41  * @Title: GtkProgressBar
42  *
43  * The #GtkProgressBar is typically used to display the progress of a long
44  * running operation.  It provides a visual clue that processing
45  * is underway.  The #GtkProgressBar can be used in two different
46  * modes: percentage mode and activity mode.
47  *
48  * When an application can determine how much work needs to take place
49  * (e.g. read a fixed number of bytes from a file) and can monitor its
50  * progress, it can use the #GtkProgressBar in percentage mode and the user
51  * sees a growing bar indicating the percentage of the work that has
52  * been completed.  In this mode, the application is required to call
53  * gtk_progress_bar_set_fraction() periodically to update the progress bar.
54  *
55  * When an application has no accurate way of knowing the amount of work
56  * to do, it can use the #GtkProgressBar in activity mode, which shows
57  * activity by a block moving back and forth within the progress area. In
58  * this mode, the application is required to call gtk_progress_bar_pulse()
59  * periodically to update the progress bar.
60  *
61  * There is quite a bit of flexibility provided to control the appearance
62  * of the #GtkProgressBar.  Functions are provided to control the
63  * orientation of the bar, optional text can be displayed along with
64  * the bar, and the step size used in activity mode can be set.
65  */
66
67 #define MIN_HORIZONTAL_BAR_WIDTH   150
68 #define MIN_HORIZONTAL_BAR_HEIGHT  20
69 #define MIN_VERTICAL_BAR_WIDTH     22
70 #define MIN_VERTICAL_BAR_HEIGHT    80
71
72
73 struct _GtkProgressBarPrivate
74 {
75   gchar         *text;
76
77   gdouble        fraction;
78   gdouble        pulse_fraction;
79
80   gint           activity_pos;
81   guint          activity_blocks;
82   guint          activity_step;
83
84   GtkOrientation orientation;
85
86   guint          activity_dir  : 1;
87   guint          activity_mode : 1;
88   guint          ellipsize     : 3;
89   guint          show_text     : 1;
90   guint          inverted      : 1;
91 };
92
93 enum {
94   PROP_0,
95   PROP_FRACTION,
96   PROP_PULSE_STEP,
97   PROP_ORIENTATION,
98   PROP_INVERTED,
99   PROP_TEXT,
100   PROP_SHOW_TEXT,
101   PROP_ELLIPSIZE
102 };
103
104 static void gtk_progress_bar_set_property         (GObject        *object,
105                                                    guint           prop_id,
106                                                    const GValue   *value,
107                                                    GParamSpec     *pspec);
108 static void gtk_progress_bar_get_property         (GObject        *object,
109                                                    guint           prop_id,
110                                                    GValue         *value,
111                                                    GParamSpec     *pspec);
112 static void gtk_progress_bar_get_preferred_width  (GtkWidget      *widget,
113                                                    gint           *minimum,
114                                                    gint           *natural);
115 static void gtk_progress_bar_get_preferred_height (GtkWidget      *widget,
116                                                    gint           *minimum,
117                                                    gint           *natural);
118
119 static void     gtk_progress_bar_real_update      (GtkProgressBar *progress);
120 static gboolean gtk_progress_bar_draw             (GtkWidget      *widget,
121                                                    cairo_t        *cr);
122 static void     gtk_progress_bar_act_mode_enter   (GtkProgressBar *progress);
123 static void     gtk_progress_bar_finalize         (GObject        *object);
124 static void     gtk_progress_bar_set_orientation  (GtkProgressBar *progress,
125                                                    GtkOrientation  orientation);
126
127 G_DEFINE_TYPE_WITH_CODE (GtkProgressBar, gtk_progress_bar, GTK_TYPE_WIDGET,
128                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
129
130 static void
131 gtk_progress_bar_class_init (GtkProgressBarClass *class)
132 {
133   GObjectClass *gobject_class;
134   GtkWidgetClass *widget_class;
135
136   gobject_class = G_OBJECT_CLASS (class);
137   widget_class = (GtkWidgetClass *) class;
138
139   gobject_class->set_property = gtk_progress_bar_set_property;
140   gobject_class->get_property = gtk_progress_bar_get_property;
141   gobject_class->finalize = gtk_progress_bar_finalize;
142
143   widget_class->draw = gtk_progress_bar_draw;
144   widget_class->get_preferred_width = gtk_progress_bar_get_preferred_width;
145   widget_class->get_preferred_height = gtk_progress_bar_get_preferred_height;
146
147   g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation");
148
149   g_object_class_install_property (gobject_class,
150                                    PROP_INVERTED,
151                                    g_param_spec_boolean ("inverted",
152                                                          P_("Inverted"),
153                                                          P_("Invert the direction in which the progress bar grows"),
154                                                          FALSE,
155                                                          GTK_PARAM_READWRITE));
156
157   g_object_class_install_property (gobject_class,
158                                    PROP_FRACTION,
159                                    g_param_spec_double ("fraction",
160                                                         P_("Fraction"),
161                                                         P_("The fraction of total work that has been completed"),
162                                                         0.0, 1.0, 0.0,
163                                                         GTK_PARAM_READWRITE));
164
165   g_object_class_install_property (gobject_class,
166                                    PROP_PULSE_STEP,
167                                    g_param_spec_double ("pulse-step",
168                                                         P_("Pulse Step"),
169                                                         P_("The fraction of total progress to move the bouncing block when pulsed"),
170                                                         0.0, 1.0, 0.1,
171                                                         GTK_PARAM_READWRITE));
172
173   g_object_class_install_property (gobject_class,
174                                    PROP_TEXT,
175                                    g_param_spec_string ("text",
176                                                         P_("Text"),
177                                                         P_("Text to be displayed in the progress bar"),
178                                                         NULL,
179                                                         GTK_PARAM_READWRITE));
180
181   /**
182    * GtkProgressBar:show-text:
183    *
184    * Sets whether the progress bar will show text superimposed
185    * over the bar. The shown text is either the value of
186    * the #GtkProgressBar:text property or, if that is %NULL,
187    * the #GtkProgressBar:fraction value, as a percentage.
188    *
189    * To make a progress bar that is styled and sized suitably for containing
190    * text (even if the actual text is blank), set #GtkProgressBar:show-text to
191    * %TRUE and #GtkProgressBar:text to the empty string (not %NULL).
192    *
193    * Since: 3.0
194    */
195   g_object_class_install_property (gobject_class,
196                                    PROP_SHOW_TEXT,
197                                    g_param_spec_boolean ("show-text",
198                                                          P_("Show text"),
199                                                          P_("Whether the progress is shown as text."),
200                                                          FALSE,
201                                                          GTK_PARAM_READWRITE));
202
203   /**
204    * GtkProgressBar:ellipsize:
205    *
206    * The preferred place to ellipsize the string, if the progress bar does
207    * not have enough room to display the entire string, specified as a
208    * #PangoEllisizeMode.
209    *
210    * Note that setting this property to a value other than
211    * %PANGO_ELLIPSIZE_NONE has the side-effect that the progress bar requests
212    * only enough space to display the ellipsis ("..."). Another means to set a
213    * progress bar's width is gtk_widget_set_size_request().
214    *
215    * Since: 2.6
216    */
217   g_object_class_install_property (gobject_class,
218                                    PROP_ELLIPSIZE,
219                                    g_param_spec_enum ("ellipsize",
220                                                       P_("Ellipsize"),
221                                                       P_("The preferred place to ellipsize the string, if the progress bar "
222                                                          "does not have enough room to display the entire string, if at all."),
223                                                       PANGO_TYPE_ELLIPSIZE_MODE,
224                                                       PANGO_ELLIPSIZE_NONE,
225                                                       GTK_PARAM_READWRITE));
226   gtk_widget_class_install_style_property (widget_class,
227                                            g_param_spec_int ("xspacing",
228                                                              P_("X spacing"),
229                                                              P_("Extra spacing applied to the width of a progress bar."),
230                                                              0, G_MAXINT, 7,
231                                                              G_PARAM_READWRITE));
232   gtk_widget_class_install_style_property (widget_class,
233                                            g_param_spec_int ("yspacing",
234                                                              P_("Y spacing"),
235                                                              P_("Extra spacing applied to the height of a progress bar."),
236                                                              0, G_MAXINT, 7,
237                                                              G_PARAM_READWRITE));
238
239   /**
240    * GtkProgressBar:min-horizontal-bar-width:
241    *
242    * The minimum horizontal width of the progress bar.
243    *
244    * Since: 2.14
245    */
246   gtk_widget_class_install_style_property (widget_class,
247                                            g_param_spec_int ("min-horizontal-bar-width",
248                                                              P_("Minimum horizontal bar width"),
249                                                              P_("The minimum horizontal width of the progress bar"),
250                                                              1, G_MAXINT, MIN_HORIZONTAL_BAR_WIDTH,
251                                                              G_PARAM_READWRITE));
252   /**
253    * GtkProgressBar:min-horizontal-bar-height:
254    *
255    * Minimum horizontal height of the progress bar.
256    *
257    * Since: 2.14
258    */
259   gtk_widget_class_install_style_property (widget_class,
260                                            g_param_spec_int ("min-horizontal-bar-height",
261                                                              P_("Minimum horizontal bar height"),
262                                                              P_("Minimum horizontal height of the progress bar"),
263                                                              1, G_MAXINT, MIN_HORIZONTAL_BAR_HEIGHT,
264                                                              G_PARAM_READWRITE));
265   /**
266    * GtkProgressBar:min-vertical-bar-width:
267    *
268    * The minimum vertical width of the progress bar.
269    *
270    * Since: 2.14
271    */
272   gtk_widget_class_install_style_property (widget_class,
273                                            g_param_spec_int ("min-vertical-bar-width",
274                                                              P_("Minimum vertical bar width"),
275                                                              P_("The minimum vertical width of the progress bar"),
276                                                              1, G_MAXINT, MIN_VERTICAL_BAR_WIDTH,
277                                                              G_PARAM_READWRITE));
278   /**
279    * GtkProgressBar:min-vertical-bar-height:
280    *
281    * The minimum vertical height of the progress bar.
282    *
283    * Since: 2.14
284    */
285   gtk_widget_class_install_style_property (widget_class,
286                                            g_param_spec_int ("min-vertical-bar-height",
287                                                              P_("Minimum vertical bar height"),
288                                                              P_("The minimum vertical height of the progress bar"),
289                                                              1, G_MAXINT, MIN_VERTICAL_BAR_HEIGHT,
290                                                              G_PARAM_READWRITE));
291
292   g_type_class_add_private (class, sizeof (GtkProgressBarPrivate));
293
294   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_PROGRESS_BAR_ACCESSIBLE);
295 }
296
297 static void
298 gtk_progress_bar_init (GtkProgressBar *pbar)
299 {
300   GtkProgressBarPrivate *priv;
301
302   pbar->priv = G_TYPE_INSTANCE_GET_PRIVATE (pbar,
303                                             GTK_TYPE_PROGRESS_BAR,
304                                             GtkProgressBarPrivate);
305   priv = pbar->priv;
306
307   priv->orientation = GTK_ORIENTATION_HORIZONTAL;
308   priv->inverted = FALSE;
309   priv->pulse_fraction = 0.1;
310   priv->activity_pos = 0;
311   priv->activity_dir = 1;
312   priv->activity_step = 3;
313   priv->activity_blocks = 5;
314   priv->ellipsize = PANGO_ELLIPSIZE_NONE;
315   priv->show_text = FALSE;
316
317   priv->text = NULL;
318   priv->fraction = 0.0;
319
320   gtk_widget_set_has_window (GTK_WIDGET (pbar), FALSE);
321 }
322
323 static void
324 gtk_progress_bar_set_property (GObject      *object,
325                                guint         prop_id,
326                                const GValue *value,
327                                GParamSpec   *pspec)
328 {
329   GtkProgressBar *pbar;
330
331   pbar = GTK_PROGRESS_BAR (object);
332
333   switch (prop_id)
334     {
335     case PROP_ORIENTATION:
336       gtk_progress_bar_set_orientation (pbar, g_value_get_enum (value));
337       break;
338     case PROP_INVERTED:
339       gtk_progress_bar_set_inverted (pbar, g_value_get_boolean (value));
340       break;
341     case PROP_FRACTION:
342       gtk_progress_bar_set_fraction (pbar, g_value_get_double (value));
343       break;
344     case PROP_PULSE_STEP:
345       gtk_progress_bar_set_pulse_step (pbar, g_value_get_double (value));
346       break;
347     case PROP_TEXT:
348       gtk_progress_bar_set_text (pbar, g_value_get_string (value));
349       break;
350     case PROP_SHOW_TEXT:
351       gtk_progress_bar_set_show_text (pbar, g_value_get_boolean (value));
352       break;
353     case PROP_ELLIPSIZE:
354       gtk_progress_bar_set_ellipsize (pbar, g_value_get_enum (value));
355       break;
356     default:
357       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
358       break;
359     }
360 }
361
362 static void
363 gtk_progress_bar_get_property (GObject      *object,
364                                guint         prop_id,
365                                GValue       *value,
366                                GParamSpec   *pspec)
367 {
368   GtkProgressBar *pbar = GTK_PROGRESS_BAR (object);
369   GtkProgressBarPrivate* priv = pbar->priv;
370
371   switch (prop_id)
372     {
373     case PROP_ORIENTATION:
374       g_value_set_enum (value, priv->orientation);
375       break;
376     case PROP_INVERTED:
377       g_value_set_boolean (value, priv->inverted);
378       break;
379     case PROP_FRACTION:
380       g_value_set_double (value, priv->fraction);
381       break;
382     case PROP_PULSE_STEP:
383       g_value_set_double (value, priv->pulse_fraction);
384       break;
385     case PROP_TEXT:
386       g_value_set_string (value, priv->text);
387       break;
388     case PROP_SHOW_TEXT:
389       g_value_set_boolean (value, priv->show_text);
390       break;
391     case PROP_ELLIPSIZE:
392       g_value_set_enum (value, priv->ellipsize);
393       break;
394     default:
395       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
396       break;
397     }
398 }
399
400 /**
401  * gtk_progress_bar_new:
402  *
403  * Creates a new #GtkProgressBar.
404  *
405  * Returns: a #GtkProgressBar.
406  */
407 GtkWidget*
408 gtk_progress_bar_new (void)
409 {
410   GtkWidget *pbar;
411
412   pbar = g_object_new (GTK_TYPE_PROGRESS_BAR, NULL);
413
414   return pbar;
415 }
416
417 static void
418 gtk_progress_bar_real_update (GtkProgressBar *pbar)
419 {
420   GtkProgressBarPrivate *priv;
421   GtkWidget *widget;
422
423   g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
424
425   priv = pbar->priv;
426   widget = GTK_WIDGET (pbar);
427
428   if (priv->activity_mode)
429     {
430       GtkAllocation allocation;
431       GtkStyleContext *context;
432       GtkStateFlags state;
433       GtkBorder padding;
434       gint size;
435
436       gtk_widget_get_allocation (widget, &allocation);
437       context = gtk_widget_get_style_context (widget);
438       state = gtk_widget_get_state_flags (widget);
439       gtk_style_context_get_padding (context, state, &padding);
440
441       /* advance the block */
442       if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
443         {
444           /* Update our activity step. */
445           priv->activity_step = allocation.width * priv->pulse_fraction;
446
447           size = MAX (2, allocation.width / priv->activity_blocks);
448
449           if (priv->activity_dir == 0)
450             {
451               priv->activity_pos += priv->activity_step;
452               if (priv->activity_pos + size >= allocation.width - padding.left)
453                 {
454                   priv->activity_pos = allocation.width - padding.left - size;
455                   priv->activity_dir = 1;
456                 }
457             }
458           else
459             {
460               priv->activity_pos -= priv->activity_step;
461               if (priv->activity_pos <= padding.left)
462                 {
463                   priv->activity_pos = padding.left;
464                   priv->activity_dir = 0;
465                 }
466             }
467         }
468       else
469         {
470           /* Update our activity step. */
471           priv->activity_step = allocation.height * priv->pulse_fraction;
472
473           size = MAX (2, allocation.height / priv->activity_blocks);
474
475           if (priv->activity_dir == 0)
476             {
477               priv->activity_pos += priv->activity_step;
478               if (priv->activity_pos + size >= allocation.height - padding.top)
479                 {
480                   priv->activity_pos = allocation.height - padding.top - size;
481                   priv->activity_dir = 1;
482                 }
483             }
484           else
485             {
486               priv->activity_pos -= priv->activity_step;
487               if (priv->activity_pos <= padding.top)
488                 {
489                   priv->activity_pos = padding.top;
490                   priv->activity_dir = 0;
491                 }
492             }
493         }
494     }
495   gtk_widget_queue_draw (widget);
496 }
497
498 static void
499 gtk_progress_bar_finalize (GObject *object)
500 {
501   GtkProgressBar *pbar = GTK_PROGRESS_BAR (object);
502   GtkProgressBarPrivate *priv = pbar->priv;
503
504   g_free (priv->text);
505
506   G_OBJECT_CLASS (gtk_progress_bar_parent_class)->finalize (object);
507 }
508
509 static gchar *
510 get_current_text (GtkProgressBar *pbar)
511 {
512   GtkProgressBarPrivate *priv = pbar->priv;
513
514   if (priv->text)
515     return g_strdup (priv->text);
516   else
517     return g_strdup_printf ("%.0f %%", priv->fraction * 100.0);
518 }
519
520 static void
521 gtk_progress_bar_get_preferred_width (GtkWidget *widget,
522                                       gint      *minimum,
523                                       gint      *natural)
524 {
525   GtkProgressBar *pbar;
526   GtkProgressBarPrivate *priv;
527   GtkStyleContext *style_context;
528   GtkStateFlags state;
529   GtkBorder padding;
530   gchar *buf;
531   PangoRectangle logical_rect;
532   PangoLayout *layout;
533   gint width;
534   gint xspacing;
535   gint min_width;
536
537   g_return_if_fail (GTK_IS_PROGRESS_BAR (widget));
538
539   style_context = gtk_widget_get_style_context (widget);
540   state = gtk_widget_get_state_flags (widget);
541   gtk_style_context_get_padding (style_context, state, &padding);
542
543   gtk_widget_style_get (widget,
544                         "xspacing", &xspacing,
545                         NULL);
546
547   pbar = GTK_PROGRESS_BAR (widget);
548   priv = pbar->priv;
549
550   width = padding.left + padding.right + xspacing;
551
552   if (priv->show_text)
553     {
554       buf = get_current_text (pbar);
555       layout = gtk_widget_create_pango_layout (widget, buf);
556
557       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
558
559       if (priv->ellipsize)
560         {
561           const PangoFontDescription *font_desc;
562           PangoContext *context;
563           PangoFontMetrics *metrics;
564           gint char_width;
565
566           /* The minimum size for ellipsized text is ~ 3 chars */
567           context = pango_layout_get_context (layout);
568           font_desc = gtk_style_context_get_font (style_context, state);
569           metrics = pango_context_get_metrics (context, font_desc, pango_context_get_language (context));
570
571           char_width = pango_font_metrics_get_approximate_char_width (metrics);
572           pango_font_metrics_unref (metrics);
573
574           width += PANGO_PIXELS (char_width) * 3;
575         }
576       else
577         width += logical_rect.width;
578
579       g_object_unref (layout);
580       g_free (buf);
581     }
582
583   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
584     gtk_widget_style_get (widget,
585                           "min-horizontal-bar-width", &min_width,
586                           NULL);
587   else
588     gtk_widget_style_get (widget,
589                           "min-vertical-bar-width", &min_width,
590                           NULL);
591
592   *minimum = *natural = MAX (min_width, width);
593 }
594
595 static void
596 gtk_progress_bar_get_preferred_height (GtkWidget *widget,
597                                        gint      *minimum,
598                                        gint      *natural)
599 {
600   GtkProgressBar *pbar;
601   GtkProgressBarPrivate *priv;
602   GtkStyleContext *context;
603   GtkStateFlags state;
604   GtkBorder padding;
605   gchar *buf;
606   PangoRectangle logical_rect;
607   PangoLayout *layout;
608   gint height;
609   gint yspacing;
610   gint min_height;
611
612   g_return_if_fail (GTK_IS_PROGRESS_BAR (widget));
613
614   context = gtk_widget_get_style_context (widget);
615   state = gtk_widget_get_state_flags (widget);
616   gtk_style_context_get_padding (context, state, &padding);
617
618   gtk_widget_style_get (widget,
619                         "yspacing", &yspacing,
620                         NULL);
621
622   pbar = GTK_PROGRESS_BAR (widget);
623   priv = pbar->priv;
624
625   height = padding.top + padding.bottom + yspacing;
626
627   if (priv->show_text)
628     {
629       buf = get_current_text (pbar);
630       layout = gtk_widget_create_pango_layout (widget, buf);
631
632       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
633
634       height += logical_rect.height;
635
636       g_object_unref (layout);
637       g_free (buf);
638     }
639
640   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
641     gtk_widget_style_get (widget,
642                           "min-horizontal-bar-height", &min_height,
643                           NULL);
644   else
645     gtk_widget_style_get (widget,
646                           "min-vertical-bar-height", &min_height,
647                           NULL);
648
649   *minimum = *natural = MAX (min_height, height);
650 }
651
652 static void
653 gtk_progress_bar_act_mode_enter (GtkProgressBar *pbar)
654 {
655   GtkProgressBarPrivate *priv = pbar->priv;
656   GtkAllocation allocation;
657   GtkStyleContext *context;
658   GtkStateFlags state;
659   GtkBorder padding;
660   GtkWidget *widget = GTK_WIDGET (pbar);
661   GtkOrientation orientation;
662   gboolean inverted;
663
664   context = gtk_widget_get_style_context (widget);
665   state = gtk_widget_get_state_flags (widget);
666   gtk_style_context_get_padding (context, state, &padding);
667
668   orientation = priv->orientation;
669   inverted = priv->inverted;
670   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
671     {
672       if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
673         inverted = !inverted;
674     }
675
676   /* calculate start pos */
677
678   if (orientation == GTK_ORIENTATION_HORIZONTAL)
679     {
680       if (!inverted)
681         {
682           priv->activity_pos = padding.left;
683           priv->activity_dir = 0;
684         }
685       else
686         {
687           gtk_widget_get_allocation (widget, &allocation);
688           priv->activity_pos = allocation.width - padding.left -
689                                (allocation.height - padding.top - padding.bottom);
690           priv->activity_dir = 1;
691         }
692     }
693   else
694     {
695       if (!inverted)
696         {
697           priv->activity_pos = padding.top;
698           priv->activity_dir = 0;
699         }
700       else
701         {
702           gtk_widget_get_allocation (widget, &allocation);
703           priv->activity_pos = allocation.height - padding.top -
704                                (allocation.width - padding.left - padding.right);
705           priv->activity_dir = 1;
706         }
707     }
708 }
709
710 static void
711 gtk_progress_bar_get_activity (GtkProgressBar *pbar,
712                                GtkOrientation  orientation,
713                                gint           *offset,
714                                gint           *amount)
715 {
716   GtkProgressBarPrivate *priv = pbar->priv;
717   GtkAllocation allocation;
718   GtkWidget *widget = GTK_WIDGET (pbar);
719
720   *offset = priv->activity_pos;
721
722   gtk_widget_get_allocation (widget, &allocation);
723
724   if (orientation == GTK_ORIENTATION_HORIZONTAL)
725     *amount = MAX (2, allocation.width / priv->activity_blocks);
726   else
727     *amount = MAX (2, allocation.height / priv->activity_blocks);
728 }
729
730 static void
731 gtk_progress_bar_paint_activity (GtkProgressBar *pbar,
732                                  cairo_t        *cr,
733                                  GtkOrientation  orientation,
734                                  gboolean        inverted,
735                                  int             width,
736                                  int             height)
737 {
738   GtkStyleContext *context;
739   GtkStateFlags state;
740   GtkBorder padding;
741   GtkWidget *widget = GTK_WIDGET (pbar);
742   GdkRectangle area;
743
744   context = gtk_widget_get_style_context (widget);
745   state = gtk_widget_get_state_flags (widget);
746   gtk_style_context_get_padding (context, state, &padding);
747
748   if (orientation == GTK_ORIENTATION_HORIZONTAL)
749     {
750       gtk_progress_bar_get_activity (pbar, orientation, &area.x, &area.width);
751       area.y = padding.top;
752       area.height = height - padding.top - padding.bottom;
753     }
754   else
755     {
756       gtk_progress_bar_get_activity (pbar, orientation, &area.y, &area.height);
757       area.x = padding.left;
758       area.width = width - padding.left - padding.right;
759     }
760
761   gtk_style_context_save (context);
762   gtk_style_context_add_class (context, GTK_STYLE_CLASS_PROGRESSBAR);
763   gtk_style_context_add_class (context, GTK_STYLE_CLASS_PULSE);
764
765   gtk_render_activity (context, cr, area.x, area.y, area.width, area.height);
766
767   gtk_style_context_restore (context);
768 }
769
770 static void
771 gtk_progress_bar_paint_continuous (GtkProgressBar *pbar,
772                                    cairo_t        *cr,
773                                    gint            amount,
774                                    GtkOrientation  orientation,
775                                    gboolean        inverted,
776                                    int             width,
777                                    int             height)
778 {
779   GtkStyleContext *context;
780   GtkStateFlags state;
781   GtkBorder padding;
782   GtkWidget *widget = GTK_WIDGET (pbar);
783   GdkRectangle area;
784
785   if (amount <= 0)
786     return;
787
788   context = gtk_widget_get_style_context (widget);
789   state = gtk_widget_get_state_flags (widget);
790   gtk_style_context_get_padding (context, state, &padding);
791
792   if (orientation == GTK_ORIENTATION_HORIZONTAL)
793     {
794       area.width = amount;
795       area.height = height - padding.top - padding.bottom;
796       area.y = padding.top;
797
798       if (!inverted)
799         area.x = padding.left;
800       else
801         area.x = width - amount - padding.right;
802     }
803   else
804     {
805       area.width = width - padding.left - padding.right;
806       area.height = amount;
807       area.x = padding.left;
808
809       if (!inverted)
810         area.y = padding.top;
811       else
812         area.y = height - amount - padding.bottom;
813     }
814
815   gtk_style_context_save (context);
816   gtk_style_context_add_class (context, GTK_STYLE_CLASS_PROGRESSBAR);
817
818   gtk_render_activity (context, cr, area.x, area.y, area.width, area.height);
819
820   gtk_style_context_restore (context);
821 }
822
823 static void
824 gtk_progress_bar_paint_text (GtkProgressBar *pbar,
825                              cairo_t        *cr,
826                              gint            offset,
827                              gint            amount,
828                              GtkOrientation  orientation,
829                              gboolean        inverted,
830                              int             width,
831                              int             height)
832 {
833   GtkProgressBarPrivate *priv = pbar->priv;
834   GtkStyleContext *context;
835   GtkStateFlags state;
836   GtkBorder padding;
837   GtkWidget *widget = GTK_WIDGET (pbar);
838   gint x;
839   gint y;
840   gchar *buf;
841   GdkRectangle rect;
842   PangoLayout *layout;
843   PangoRectangle logical_rect;
844   GdkRectangle prelight_clip, start_clip, end_clip;
845   gfloat text_xalign = 0.5;
846   gfloat text_yalign = 0.5;
847
848   context = gtk_widget_get_style_context (widget);
849   state = gtk_widget_get_state_flags (widget);
850   gtk_style_context_get_padding (context, state, &padding);
851
852   if (gtk_widget_get_direction (widget) != GTK_TEXT_DIR_LTR)
853     text_xalign = 1.0 - text_xalign;
854
855   buf = get_current_text (pbar);
856
857   layout = gtk_widget_create_pango_layout (widget, buf);
858   pango_layout_set_ellipsize (layout, priv->ellipsize);
859   if (priv->ellipsize)
860     pango_layout_set_width (layout, width * PANGO_SCALE);
861
862   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
863
864   x = padding.left + 1 + text_xalign * (width - padding.left - padding.right - 2 - logical_rect.width);
865   y = padding.top + 1 + text_yalign * (height - padding.top - padding.bottom - 2 - logical_rect.height);
866
867   rect.x = padding.left;
868   rect.y = padding.top;
869   rect.width = width - padding.left - padding.right;
870   rect.height = height - padding.top - padding.bottom;
871
872   prelight_clip = start_clip = end_clip = rect;
873
874   if (orientation == GTK_ORIENTATION_HORIZONTAL)
875     {
876       if (!inverted)
877         {
878           if (offset != -1)
879             prelight_clip.x = offset;
880           prelight_clip.width = amount;
881           start_clip.width = prelight_clip.x - start_clip.x;
882           end_clip.x = start_clip.x + start_clip.width + prelight_clip.width;
883           end_clip.width -= prelight_clip.width + start_clip.width;
884         }
885       else
886         {
887           if (offset != -1)
888             prelight_clip.x = offset;
889           else
890             prelight_clip.x = rect.x + rect.width - amount;
891           prelight_clip.width = amount;
892           start_clip.width = prelight_clip.x - start_clip.x;
893           end_clip.x = start_clip.x + start_clip.width + prelight_clip.width;
894           end_clip.width -= prelight_clip.width + start_clip.width;
895         }
896     }
897   else
898     {
899       if (!inverted)
900         {
901           if (offset != -1)
902             prelight_clip.y = offset;
903           prelight_clip.height = amount;
904           start_clip.height = prelight_clip.y - start_clip.y;
905           end_clip.y = start_clip.y + start_clip.height + prelight_clip.height;
906           end_clip.height -= prelight_clip.height + start_clip.height;
907         }
908       else
909         {
910           if (offset != -1)
911             prelight_clip.y = offset;
912           else
913             prelight_clip.y = rect.y + rect.height - amount;
914           prelight_clip.height = amount;
915           start_clip.height = prelight_clip.y - start_clip.y;
916           end_clip.y = start_clip.y + start_clip.height + prelight_clip.height;
917           end_clip.height -= prelight_clip.height + start_clip.height;
918         }
919     }
920
921   gtk_style_context_save (context);
922   gtk_style_context_add_class (context, GTK_STYLE_CLASS_TROUGH);
923
924   if (start_clip.width > 0 && start_clip.height > 0)
925     {
926       cairo_save (cr);
927       gdk_cairo_rectangle (cr, &start_clip);
928       cairo_clip (cr);
929
930       gtk_render_layout (context, cr, x, y, layout);
931       cairo_restore (cr);
932     }
933
934   if (end_clip.width > 0 && end_clip.height > 0)
935     {
936       cairo_save (cr);
937       gdk_cairo_rectangle (cr, &end_clip);
938       cairo_clip (cr);
939
940       gtk_render_layout (context, cr, x, y, layout);
941       cairo_restore (cr);
942     }
943
944   gtk_style_context_restore (context);
945
946   cairo_save (cr);
947   gdk_cairo_rectangle (cr, &prelight_clip);
948   cairo_clip (cr);
949
950   gtk_style_context_save (context);
951   gtk_style_context_add_class (context, GTK_STYLE_CLASS_PROGRESSBAR);
952
953   gtk_render_layout (context, cr, x, y, layout);
954
955   gtk_style_context_restore (context);
956   cairo_restore (cr);
957
958   g_object_unref (layout);
959   g_free (buf);
960 }
961
962 static gboolean
963 gtk_progress_bar_draw (GtkWidget      *widget,
964                        cairo_t        *cr)
965 {
966   GtkProgressBar *pbar = GTK_PROGRESS_BAR (widget);
967   GtkProgressBarPrivate *priv = pbar->priv;
968   GtkOrientation orientation;
969   gboolean inverted;
970   GtkStyleContext *context;
971   GtkStateFlags state;
972   GtkBorder padding;
973   int width, height;
974
975   context = gtk_widget_get_style_context (widget);
976   state = gtk_widget_get_state_flags (widget);
977   gtk_style_context_get_padding (context, state, &padding);
978
979   orientation = priv->orientation;
980   inverted = priv->inverted;
981   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
982     {
983       if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
984         inverted = !inverted;
985     }
986   width = gtk_widget_get_allocated_width (widget);
987   height = gtk_widget_get_allocated_height (widget);
988
989   gtk_style_context_save (context);
990   gtk_style_context_add_class (context, GTK_STYLE_CLASS_TROUGH);
991
992   gtk_render_background (context, cr, 0, 0, width, height);
993   gtk_render_frame (context, cr, 0, 0, width, height);
994
995   gtk_style_context_restore (context);
996
997   if (priv->activity_mode)
998     {
999       gtk_progress_bar_paint_activity (pbar, cr,
1000                                        orientation, inverted,
1001                                        width, height);
1002
1003       if (priv->show_text)
1004         {
1005           gint offset;
1006           gint amount;
1007
1008           gtk_progress_bar_get_activity (pbar, orientation, &offset, &amount);
1009           gtk_progress_bar_paint_text (pbar, cr,
1010                                        offset, amount,
1011                                        orientation, inverted,
1012                                        width, height);
1013         }
1014     }
1015   else
1016     {
1017       gint amount;
1018       gint space;
1019
1020       if (orientation == GTK_ORIENTATION_HORIZONTAL)
1021         space = width - padding.left - padding.right;
1022       else
1023         space = height - padding.top - padding.bottom;
1024
1025       amount = space * gtk_progress_bar_get_fraction (pbar);
1026
1027       gtk_progress_bar_paint_continuous (pbar, cr, amount, orientation, inverted, width, height);
1028
1029       if (priv->show_text)
1030         gtk_progress_bar_paint_text (pbar, cr, -1, amount, orientation, inverted, width, height);
1031     }
1032
1033   return FALSE;
1034 }
1035
1036 static void
1037 gtk_progress_bar_set_activity_mode (GtkProgressBar *pbar,
1038                                     gboolean        activity_mode)
1039 {
1040   GtkProgressBarPrivate *priv = pbar->priv;
1041
1042   activity_mode = !!activity_mode;
1043
1044   if (priv->activity_mode != activity_mode)
1045     {
1046       priv->activity_mode = activity_mode;
1047
1048       if (priv->activity_mode)
1049         gtk_progress_bar_act_mode_enter (pbar);
1050
1051       gtk_widget_queue_resize (GTK_WIDGET (pbar));
1052     }
1053 }
1054
1055 /**
1056  * gtk_progress_bar_set_fraction:
1057  * @pbar: a #GtkProgressBar
1058  * @fraction: fraction of the task that's been completed
1059  *
1060  * Causes the progress bar to "fill in" the given fraction
1061  * of the bar. The fraction should be between 0.0 and 1.0,
1062  * inclusive.
1063  */
1064 void
1065 gtk_progress_bar_set_fraction (GtkProgressBar *pbar,
1066                                gdouble         fraction)
1067 {
1068   GtkProgressBarPrivate* priv;
1069
1070   g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1071
1072   priv = pbar->priv;
1073
1074   priv->fraction = CLAMP(fraction, 0.0, 1.0);
1075   gtk_progress_bar_set_activity_mode (pbar, FALSE);
1076   gtk_progress_bar_real_update (pbar);
1077
1078   g_object_notify (G_OBJECT (pbar), "fraction");
1079 }
1080
1081 /**
1082  * gtk_progress_bar_pulse:
1083  * @pbar: a #GtkProgressBar
1084  *
1085  * Indicates that some progress has been made, but you don't know how much.
1086  * Causes the progress bar to enter "activity mode," where a block
1087  * bounces back and forth. Each call to gtk_progress_bar_pulse()
1088  * causes the block to move by a little bit (the amount of movement
1089  * per pulse is determined by gtk_progress_bar_set_pulse_step()).
1090  */
1091 void
1092 gtk_progress_bar_pulse (GtkProgressBar *pbar)
1093 {
1094   g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1095
1096   gtk_progress_bar_set_activity_mode (pbar, TRUE);
1097   gtk_progress_bar_real_update (pbar);
1098 }
1099
1100 /**
1101  * gtk_progress_bar_set_text:
1102  * @pbar: a #GtkProgressBar
1103  * @text: (allow-none): a UTF-8 string, or %NULL
1104  *
1105  * Causes the given @text to appear superimposed on the progress bar.
1106  *
1107  * If @text is %NULL and #GtkProgressBar:show-text is %TRUE, the current
1108  * value of #GtkProgressBar:fraction will be displayed as a percentage.
1109  *
1110  * If @text is non-%NULL and #GtkProgressBar:show-text is %TRUE, the text will
1111  * be displayed. In this case, it will not display the progress percentage.
1112  * If @text is the empty string, the progress bar will still be styled and sized
1113  * suitably for containing text, as long as #GtkProgressBar:show-text is %TRUE.
1114  */
1115 void
1116 gtk_progress_bar_set_text (GtkProgressBar *pbar,
1117                            const gchar    *text)
1118 {
1119   GtkProgressBarPrivate *priv;
1120
1121   g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1122
1123   priv = pbar->priv;
1124
1125   /* Don't notify again if nothing's changed. */
1126   if (g_strcmp0 (priv->text, text) == 0)
1127     return;
1128
1129   g_free (priv->text);
1130   priv->text = g_strdup (text);
1131
1132   gtk_widget_queue_resize (GTK_WIDGET (pbar));
1133
1134   g_object_notify (G_OBJECT (pbar), "text");
1135 }
1136
1137 /**
1138  * gtk_progress_bar_set_show_text:
1139  * @pbar: a #GtkProgressBar
1140  * @show_text: whether to show superimposed text
1141  *
1142  * Sets whether the progress bar will show text superimposed
1143  * over the bar. The shown text is either the value of
1144  * the #GtkProgressBar:text property or, if that is %NULL,
1145  * the #GtkProgressBar:fraction value, as a percentage.
1146  *
1147  * To make a progress bar that is styled and sized suitably for containing
1148  * text (even if the actual text is blank), set #GtkProgressBar:show-text to
1149  * %TRUE and #GtkProgressBar:text to the empty string (not %NULL).
1150  *
1151  * Since: 3.0
1152  */
1153 void
1154 gtk_progress_bar_set_show_text (GtkProgressBar *pbar,
1155                                 gboolean        show_text)
1156 {
1157   GtkProgressBarPrivate *priv;
1158
1159   g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1160
1161   priv = pbar->priv;
1162
1163   show_text = !!show_text;
1164
1165   if (priv->show_text != show_text)
1166     {
1167       priv->show_text = show_text;
1168
1169       gtk_widget_queue_resize (GTK_WIDGET (pbar));
1170
1171       g_object_notify (G_OBJECT (pbar), "show-text");
1172     }
1173 }
1174
1175 /**
1176  * gtk_progress_bar_get_show_text:
1177  * @pbar: a #GtkProgressBar
1178  *
1179  * Gets the value of the #GtkProgressBar:show-text property.
1180  * See gtk_progress_bar_set_show_text().
1181  *
1182  * Returns: %TRUE if text is shown in the progress bar
1183  *
1184  * Since: 3.0
1185  */
1186 gboolean
1187 gtk_progress_bar_get_show_text (GtkProgressBar *pbar)
1188 {
1189   g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), FALSE);
1190
1191   return pbar->priv->show_text;
1192 }
1193
1194 /**
1195  * gtk_progress_bar_set_pulse_step:
1196  * @pbar: a #GtkProgressBar
1197  * @fraction: fraction between 0.0 and 1.0
1198  *
1199  * Sets the fraction of total progress bar length to move the
1200  * bouncing block for each call to gtk_progress_bar_pulse().
1201  */
1202 void
1203 gtk_progress_bar_set_pulse_step (GtkProgressBar *pbar,
1204                                  gdouble         fraction)
1205 {
1206   GtkProgressBarPrivate *priv;
1207
1208   g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1209
1210   priv = pbar->priv;
1211
1212   priv->pulse_fraction = fraction;
1213
1214   g_object_notify (G_OBJECT (pbar), "pulse-step");
1215 }
1216
1217 static void
1218 gtk_progress_bar_set_orientation (GtkProgressBar *pbar,
1219                                   GtkOrientation  orientation)
1220 {
1221   GtkProgressBarPrivate *priv = pbar->priv;
1222
1223   if (priv->orientation != orientation)
1224     {
1225       priv->orientation = orientation;
1226       _gtk_orientable_set_style_classes (GTK_ORIENTABLE (pbar));
1227
1228       gtk_widget_queue_resize (GTK_WIDGET (pbar));
1229     }
1230 }
1231
1232 /**
1233  * gtk_progress_bar_set_inverted:
1234  * @pbar: a #GtkProgressBar
1235  * @inverted: %TRUE to invert the progress bar
1236  *
1237  * Progress bars normally grow from top to bottom or left to right.
1238  * Inverted progress bars grow in the opposite direction.
1239  */
1240 void
1241 gtk_progress_bar_set_inverted (GtkProgressBar *pbar,
1242                                gboolean        inverted)
1243 {
1244   GtkProgressBarPrivate *priv;
1245
1246   g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1247
1248   priv = pbar->priv;
1249
1250   if (priv->inverted != inverted)
1251     {
1252       priv->inverted = inverted;
1253
1254       gtk_widget_queue_resize (GTK_WIDGET (pbar));
1255
1256       g_object_notify (G_OBJECT (pbar), "inverted");
1257     }
1258 }
1259
1260 /**
1261  * gtk_progress_bar_get_text:
1262  * @pbar: a #GtkProgressBar
1263  *
1264  * Retrieves the text displayed superimposed on the progress bar,
1265  * if any, otherwise %NULL. The return value is a reference
1266  * to the text, not a copy of it, so will become invalid
1267  * if you change the text in the progress bar.
1268  *
1269  * Return value: text, or %NULL; this string is owned by the widget
1270  * and should not be modified or freed.
1271  */
1272 const gchar*
1273 gtk_progress_bar_get_text (GtkProgressBar *pbar)
1274 {
1275   g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), NULL);
1276
1277   return pbar->priv->text;
1278 }
1279
1280 /**
1281  * gtk_progress_bar_get_fraction:
1282  * @pbar: a #GtkProgressBar
1283  *
1284  * Returns the current fraction of the task that's been completed.
1285  *
1286  * Return value: a fraction from 0.0 to 1.0
1287  */
1288 gdouble
1289 gtk_progress_bar_get_fraction (GtkProgressBar *pbar)
1290 {
1291   g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), 0);
1292
1293   return pbar->priv->fraction;
1294 }
1295
1296 /**
1297  * gtk_progress_bar_get_pulse_step:
1298  * @pbar: a #GtkProgressBar
1299  *
1300  * Retrieves the pulse step set with gtk_progress_bar_set_pulse_step().
1301  *
1302  * Return value: a fraction from 0.0 to 1.0
1303  */
1304 gdouble
1305 gtk_progress_bar_get_pulse_step (GtkProgressBar *pbar)
1306 {
1307   g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), 0);
1308
1309   return pbar->priv->pulse_fraction;
1310 }
1311
1312 /**
1313  * gtk_progress_bar_get_inverted:
1314  * @pbar: a #GtkProgressBar
1315  *
1316  * Gets the value set by gtk_progress_bar_set_inverted().
1317  *
1318  * Return value: %TRUE if the progress bar is inverted
1319  */
1320 gboolean
1321 gtk_progress_bar_get_inverted (GtkProgressBar *pbar)
1322 {
1323   g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), FALSE);
1324
1325   return pbar->priv->inverted;
1326 }
1327
1328 /**
1329  * gtk_progress_bar_set_ellipsize:
1330  * @pbar: a #GtkProgressBar
1331  * @mode: a #PangoEllipsizeMode
1332  *
1333  * Sets the mode used to ellipsize (add an ellipsis: "...") the text
1334  * if there is not enough space to render the entire string.
1335  *
1336  * Since: 2.6
1337  */
1338 void
1339 gtk_progress_bar_set_ellipsize (GtkProgressBar     *pbar,
1340                                 PangoEllipsizeMode  mode)
1341 {
1342   GtkProgressBarPrivate *priv;
1343
1344   g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1345   g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE &&
1346                     mode <= PANGO_ELLIPSIZE_END);
1347
1348   priv = pbar->priv;
1349
1350   if ((PangoEllipsizeMode)priv->ellipsize != mode)
1351     {
1352       priv->ellipsize = mode;
1353
1354       g_object_notify (G_OBJECT (pbar), "ellipsize");
1355       gtk_widget_queue_resize (GTK_WIDGET (pbar));
1356     }
1357 }
1358
1359 /**
1360  * gtk_progress_bar_get_ellipsize:
1361  * @pbar: a #GtkProgressBar
1362  *
1363  * Returns the ellipsizing position of the progress bar.
1364  * See gtk_progress_bar_set_ellipsize().
1365  *
1366  * Return value: #PangoEllipsizeMode
1367  *
1368  * Since: 2.6
1369  */
1370 PangoEllipsizeMode
1371 gtk_progress_bar_get_ellipsize (GtkProgressBar *pbar)
1372 {
1373   g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), PANGO_ELLIPSIZE_NONE);
1374
1375   return pbar->priv->ellipsize;
1376 }