]> Pileus Git - ~andy/gtk/blob - gtk/gtkprogressbar.c
GtkProgressbar: respect the range of 'fraction'
[~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 progressbar 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 progressbar 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 progressbar requests
212    * only enough space to display the ellipsis "...". Another means to set a
213    * progressbar'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
764   gtk_render_activity (context, cr, area.x, area.y, area.width, area.height);
765
766   gtk_style_context_restore (context);
767 }
768
769 static void
770 gtk_progress_bar_paint_continuous (GtkProgressBar *pbar,
771                                    cairo_t        *cr,
772                                    gint            amount,
773                                    GtkOrientation  orientation,
774                                    gboolean        inverted,
775                                    int             width,
776                                    int             height)
777 {
778   GtkStyleContext *context;
779   GtkStateFlags state;
780   GtkBorder padding;
781   GtkWidget *widget = GTK_WIDGET (pbar);
782   GdkRectangle area;
783
784   if (amount <= 0)
785     return;
786
787   context = gtk_widget_get_style_context (widget);
788   state = gtk_widget_get_state_flags (widget);
789   gtk_style_context_get_padding (context, state, &padding);
790
791   if (orientation == GTK_ORIENTATION_HORIZONTAL)
792     {
793       area.width = amount;
794       area.height = height - padding.top - padding.bottom;
795       area.y = padding.top;
796
797       if (!inverted)
798         area.x = padding.left;
799       else
800         area.x = width - amount - padding.right;
801     }
802   else
803     {
804       area.width = width - padding.left - padding.right;
805       area.height = amount;
806       area.x = padding.left;
807
808       if (!inverted)
809         area.y = padding.top;
810       else
811         area.y = height - amount - padding.bottom;
812     }
813
814   gtk_style_context_save (context);
815   gtk_style_context_add_class (context, GTK_STYLE_CLASS_PROGRESSBAR);
816
817   gtk_render_activity (context, cr, area.x, area.y, area.width, area.height);
818
819   gtk_style_context_restore (context);
820 }
821
822 static void
823 gtk_progress_bar_paint_text (GtkProgressBar *pbar,
824                              cairo_t        *cr,
825                              gint            offset,
826                              gint            amount,
827                              GtkOrientation  orientation,
828                              gboolean        inverted,
829                              int             width,
830                              int             height)
831 {
832   GtkProgressBarPrivate *priv = pbar->priv;
833   GtkStyleContext *context;
834   GtkStateFlags state;
835   GtkBorder padding;
836   GtkWidget *widget = GTK_WIDGET (pbar);
837   gint x;
838   gint y;
839   gchar *buf;
840   GdkRectangle rect;
841   PangoLayout *layout;
842   PangoRectangle logical_rect;
843   GdkRectangle prelight_clip, start_clip, end_clip;
844   gfloat text_xalign = 0.5;
845   gfloat text_yalign = 0.5;
846
847   context = gtk_widget_get_style_context (widget);
848   state = gtk_widget_get_state_flags (widget);
849   gtk_style_context_get_padding (context, state, &padding);
850
851   if (gtk_widget_get_direction (widget) != GTK_TEXT_DIR_LTR)
852     text_xalign = 1.0 - text_xalign;
853
854   buf = get_current_text (pbar);
855
856   layout = gtk_widget_create_pango_layout (widget, buf);
857   pango_layout_set_ellipsize (layout, priv->ellipsize);
858   if (priv->ellipsize)
859     pango_layout_set_width (layout, width * PANGO_SCALE);
860
861   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
862
863   x = padding.left + 1 + text_xalign * (width - padding.left - padding.right - 2 - logical_rect.width);
864   y = padding.top + 1 + text_yalign * (height - padding.top - padding.bottom - 2 - logical_rect.height);
865
866   rect.x = padding.left;
867   rect.y = padding.top;
868   rect.width = width - padding.left - padding.right;
869   rect.height = height - padding.top - padding.bottom;
870
871   prelight_clip = start_clip = end_clip = rect;
872
873   if (orientation == GTK_ORIENTATION_HORIZONTAL)
874     {
875       if (!inverted)
876         {
877           if (offset != -1)
878             prelight_clip.x = offset;
879           prelight_clip.width = amount;
880           start_clip.width = prelight_clip.x - start_clip.x;
881           end_clip.x = start_clip.x + start_clip.width + prelight_clip.width;
882           end_clip.width -= prelight_clip.width + start_clip.width;
883         }
884       else
885         {
886           if (offset != -1)
887             prelight_clip.x = offset;
888           else
889             prelight_clip.x = rect.x + rect.width - amount;
890           prelight_clip.width = amount;
891           start_clip.width = prelight_clip.x - start_clip.x;
892           end_clip.x = start_clip.x + start_clip.width + prelight_clip.width;
893           end_clip.width -= prelight_clip.width + start_clip.width;
894         }
895     }
896   else
897     {
898       if (!inverted)
899         {
900           if (offset != -1)
901             prelight_clip.y = offset;
902           prelight_clip.height = amount;
903           start_clip.height = prelight_clip.y - start_clip.y;
904           end_clip.y = start_clip.y + start_clip.height + prelight_clip.height;
905           end_clip.height -= prelight_clip.height + start_clip.height;
906         }
907       else
908         {
909           if (offset != -1)
910             prelight_clip.y = offset;
911           else
912             prelight_clip.y = rect.y + rect.height - amount;
913           prelight_clip.height = amount;
914           start_clip.height = prelight_clip.y - start_clip.y;
915           end_clip.y = start_clip.y + start_clip.height + prelight_clip.height;
916           end_clip.height -= prelight_clip.height + start_clip.height;
917         }
918     }
919
920   gtk_style_context_save (context);
921   gtk_style_context_add_class (context, GTK_STYLE_CLASS_TROUGH);
922
923   if (start_clip.width > 0 && start_clip.height > 0)
924     {
925       cairo_save (cr);
926       gdk_cairo_rectangle (cr, &start_clip);
927       cairo_clip (cr);
928
929       gtk_render_layout (context, cr, x, y, layout);
930       cairo_restore (cr);
931     }
932
933   if (end_clip.width > 0 && end_clip.height > 0)
934     {
935       cairo_save (cr);
936       gdk_cairo_rectangle (cr, &end_clip);
937       cairo_clip (cr);
938
939       gtk_render_layout (context, cr, x, y, layout);
940       cairo_restore (cr);
941     }
942
943   gtk_style_context_restore (context);
944
945   cairo_save (cr);
946   gdk_cairo_rectangle (cr, &prelight_clip);
947   cairo_clip (cr);
948
949   gtk_style_context_save (context);
950   gtk_style_context_add_class (context, GTK_STYLE_CLASS_PROGRESSBAR);
951
952   gtk_render_layout (context, cr, x, y, layout);
953
954   gtk_style_context_restore (context);
955   cairo_restore (cr);
956
957   g_object_unref (layout);
958   g_free (buf);
959 }
960
961 static gboolean
962 gtk_progress_bar_draw (GtkWidget      *widget,
963                        cairo_t        *cr)
964 {
965   GtkProgressBar *pbar = GTK_PROGRESS_BAR (widget);
966   GtkProgressBarPrivate *priv = pbar->priv;
967   GtkOrientation orientation;
968   gboolean inverted;
969   GtkStyleContext *context;
970   GtkStateFlags state;
971   GtkBorder padding;
972   int width, height;
973
974   context = gtk_widget_get_style_context (widget);
975   state = gtk_widget_get_state_flags (widget);
976   gtk_style_context_get_padding (context, state, &padding);
977
978   orientation = priv->orientation;
979   inverted = priv->inverted;
980   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
981     {
982       if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
983         inverted = !inverted;
984     }
985   width = gtk_widget_get_allocated_width (widget);
986   height = gtk_widget_get_allocated_height (widget);
987
988   gtk_style_context_save (context);
989   gtk_style_context_add_class (context, GTK_STYLE_CLASS_TROUGH);
990
991   gtk_render_background (context, cr, 0, 0, width, height);
992   gtk_render_frame (context, cr, 0, 0, width, height);
993
994   gtk_style_context_restore (context);
995
996   if (priv->activity_mode)
997     {
998       gtk_progress_bar_paint_activity (pbar, cr,
999                                        orientation, inverted,
1000                                        width, height);
1001
1002       if (priv->show_text)
1003         {
1004           gint offset;
1005           gint amount;
1006
1007           gtk_progress_bar_get_activity (pbar, orientation, &offset, &amount);
1008           gtk_progress_bar_paint_text (pbar, cr,
1009                                        offset, amount,
1010                                        orientation, inverted,
1011                                        width, height);
1012         }
1013     }
1014   else
1015     {
1016       gint amount;
1017       gint space;
1018
1019       if (orientation == GTK_ORIENTATION_HORIZONTAL)
1020         space = width - padding.left - padding.right;
1021       else
1022         space = height - padding.top - padding.bottom;
1023
1024       amount = space * gtk_progress_bar_get_fraction (pbar);
1025
1026       gtk_progress_bar_paint_continuous (pbar, cr, amount, orientation, inverted, width, height);
1027
1028       if (priv->show_text)
1029         gtk_progress_bar_paint_text (pbar, cr, -1, amount, orientation, inverted, width, height);
1030     }
1031
1032   return FALSE;
1033 }
1034
1035 static void
1036 gtk_progress_bar_set_activity_mode (GtkProgressBar *pbar,
1037                                     gboolean        activity_mode)
1038 {
1039   GtkProgressBarPrivate *priv = pbar->priv;
1040
1041   activity_mode = !!activity_mode;
1042
1043   if (priv->activity_mode != activity_mode)
1044     {
1045       priv->activity_mode = activity_mode;
1046
1047       if (priv->activity_mode)
1048         gtk_progress_bar_act_mode_enter (pbar);
1049
1050       gtk_widget_queue_resize (GTK_WIDGET (pbar));
1051     }
1052 }
1053
1054 /**
1055  * gtk_progress_bar_set_fraction:
1056  * @pbar: a #GtkProgressBar
1057  * @fraction: fraction of the task that's been completed
1058  *
1059  * Causes the progress bar to "fill in" the given fraction
1060  * of the bar. The fraction should be between 0.0 and 1.0,
1061  * inclusive.
1062  *
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 is 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 progressbar 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 progressbar.
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 }