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