]> Pileus Git - ~andy/gtk/blob - gtk/gtkprogressbar.c
Revert "progressbar: Set state flags on draw"
[~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   gtk_style_context_add_class (context, GTK_STYLE_CLASS_PULSE);
764
765   gtk_render_activity (context, cr, area.x, area.y, area.width, area.height);
766
767   gtk_style_context_restore (context);
768 }
769
770 static void
771 gtk_progress_bar_paint_continuous (GtkProgressBar *pbar,
772                                    cairo_t        *cr,
773                                    gint            amount,
774                                    GtkOrientation  orientation,
775                                    gboolean        inverted,
776                                    int             width,
777                                    int             height)
778 {
779   GtkStyleContext *context;
780   GtkStateFlags state;
781   GtkBorder padding;
782   GtkWidget *widget = GTK_WIDGET (pbar);
783   GdkRectangle area;
784
785   if (amount <= 0)
786     return;
787
788   context = gtk_widget_get_style_context (widget);
789   state = gtk_widget_get_state_flags (widget);
790   gtk_style_context_get_padding (context, state, &padding);
791
792   if (orientation == GTK_ORIENTATION_HORIZONTAL)
793     {
794       area.width = amount;
795       area.height = height - padding.top - padding.bottom;
796       area.y = padding.top;
797
798       if (!inverted)
799         area.x = padding.left;
800       else
801         area.x = width - amount - padding.right;
802     }
803   else
804     {
805       area.width = width - padding.left - padding.right;
806       area.height = amount;
807       area.x = padding.left;
808
809       if (!inverted)
810         area.y = padding.top;
811       else
812         area.y = height - amount - padding.bottom;
813     }
814
815   gtk_style_context_save (context);
816   gtk_style_context_add_class (context, GTK_STYLE_CLASS_PROGRESSBAR);
817
818   gtk_render_activity (context, cr, area.x, area.y, area.width, area.height);
819
820   gtk_style_context_restore (context);
821 }
822
823 static void
824 gtk_progress_bar_paint_text (GtkProgressBar *pbar,
825                              cairo_t        *cr,
826                              gint            offset,
827                              gint            amount,
828                              GtkOrientation  orientation,
829                              gboolean        inverted,
830                              int             width,
831                              int             height)
832 {
833   GtkProgressBarPrivate *priv = pbar->priv;
834   GtkStyleContext *context;
835   GtkStateFlags state;
836   GtkBorder padding;
837   GtkWidget *widget = GTK_WIDGET (pbar);
838   gint x;
839   gint y;
840   gchar *buf;
841   GdkRectangle rect;
842   PangoLayout *layout;
843   PangoRectangle logical_rect;
844   GdkRectangle prelight_clip, start_clip, end_clip;
845   gfloat text_xalign = 0.5;
846   gfloat text_yalign = 0.5;
847
848   context = gtk_widget_get_style_context (widget);
849   state = gtk_widget_get_state_flags (widget);
850   gtk_style_context_get_padding (context, state, &padding);
851
852   if (gtk_widget_get_direction (widget) != GTK_TEXT_DIR_LTR)
853     text_xalign = 1.0 - text_xalign;
854
855   buf = get_current_text (pbar);
856
857   layout = gtk_widget_create_pango_layout (widget, buf);
858   pango_layout_set_ellipsize (layout, priv->ellipsize);
859   if (priv->ellipsize)
860     pango_layout_set_width (layout, width * PANGO_SCALE);
861
862   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
863
864   x = padding.left + 1 + text_xalign * (width - padding.left - padding.right - 2 - logical_rect.width);
865   y = padding.top + 1 + text_yalign * (height - padding.top - padding.bottom - 2 - logical_rect.height);
866
867   rect.x = padding.left;
868   rect.y = padding.top;
869   rect.width = width - padding.left - padding.right;
870   rect.height = height - padding.top - padding.bottom;
871
872   prelight_clip = start_clip = end_clip = rect;
873
874   if (orientation == GTK_ORIENTATION_HORIZONTAL)
875     {
876       if (!inverted)
877         {
878           if (offset != -1)
879             prelight_clip.x = offset;
880           prelight_clip.width = amount;
881           start_clip.width = prelight_clip.x - start_clip.x;
882           end_clip.x = start_clip.x + start_clip.width + prelight_clip.width;
883           end_clip.width -= prelight_clip.width + start_clip.width;
884         }
885       else
886         {
887           if (offset != -1)
888             prelight_clip.x = offset;
889           else
890             prelight_clip.x = rect.x + rect.width - amount;
891           prelight_clip.width = amount;
892           start_clip.width = prelight_clip.x - start_clip.x;
893           end_clip.x = start_clip.x + start_clip.width + prelight_clip.width;
894           end_clip.width -= prelight_clip.width + start_clip.width;
895         }
896     }
897   else
898     {
899       if (!inverted)
900         {
901           if (offset != -1)
902             prelight_clip.y = offset;
903           prelight_clip.height = amount;
904           start_clip.height = prelight_clip.y - start_clip.y;
905           end_clip.y = start_clip.y + start_clip.height + prelight_clip.height;
906           end_clip.height -= prelight_clip.height + start_clip.height;
907         }
908       else
909         {
910           if (offset != -1)
911             prelight_clip.y = offset;
912           else
913             prelight_clip.y = rect.y + rect.height - amount;
914           prelight_clip.height = amount;
915           start_clip.height = prelight_clip.y - start_clip.y;
916           end_clip.y = start_clip.y + start_clip.height + prelight_clip.height;
917           end_clip.height -= prelight_clip.height + start_clip.height;
918         }
919     }
920
921   gtk_style_context_save (context);
922   gtk_style_context_add_class (context, GTK_STYLE_CLASS_TROUGH);
923
924   if (start_clip.width > 0 && start_clip.height > 0)
925     {
926       cairo_save (cr);
927       gdk_cairo_rectangle (cr, &start_clip);
928       cairo_clip (cr);
929
930       gtk_render_layout (context, cr, x, y, layout);
931       cairo_restore (cr);
932     }
933
934   if (end_clip.width > 0 && end_clip.height > 0)
935     {
936       cairo_save (cr);
937       gdk_cairo_rectangle (cr, &end_clip);
938       cairo_clip (cr);
939
940       gtk_render_layout (context, cr, x, y, layout);
941       cairo_restore (cr);
942     }
943
944   gtk_style_context_restore (context);
945
946   cairo_save (cr);
947   gdk_cairo_rectangle (cr, &prelight_clip);
948   cairo_clip (cr);
949
950   gtk_style_context_save (context);
951   gtk_style_context_add_class (context, GTK_STYLE_CLASS_PROGRESSBAR);
952
953   gtk_render_layout (context, cr, x, y, layout);
954
955   gtk_style_context_restore (context);
956   cairo_restore (cr);
957
958   g_object_unref (layout);
959   g_free (buf);
960 }
961
962 static gboolean
963 gtk_progress_bar_draw (GtkWidget      *widget,
964                        cairo_t        *cr)
965 {
966   GtkProgressBar *pbar = GTK_PROGRESS_BAR (widget);
967   GtkProgressBarPrivate *priv = pbar->priv;
968   GtkOrientation orientation;
969   gboolean inverted;
970   GtkStyleContext *context;
971   GtkStateFlags state;
972   GtkBorder padding;
973   int width, height;
974
975   context = gtk_widget_get_style_context (widget);
976   state = gtk_widget_get_state_flags (widget);
977   gtk_style_context_get_padding (context, state, &padding);
978
979   orientation = priv->orientation;
980   inverted = priv->inverted;
981   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
982     {
983       if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
984         inverted = !inverted;
985     }
986   width = gtk_widget_get_allocated_width (widget);
987   height = gtk_widget_get_allocated_height (widget);
988
989   gtk_style_context_save (context);
990   gtk_style_context_add_class (context, GTK_STYLE_CLASS_TROUGH);
991
992   gtk_render_background (context, cr, 0, 0, width, height);
993   gtk_render_frame (context, cr, 0, 0, width, height);
994
995   gtk_style_context_restore (context);
996
997   if (priv->activity_mode)
998     {
999       gtk_progress_bar_paint_activity (pbar, cr,
1000                                        orientation, inverted,
1001                                        width, height);
1002
1003       if (priv->show_text)
1004         {
1005           gint offset;
1006           gint amount;
1007
1008           gtk_progress_bar_get_activity (pbar, orientation, &offset, &amount);
1009           gtk_progress_bar_paint_text (pbar, cr,
1010                                        offset, amount,
1011                                        orientation, inverted,
1012                                        width, height);
1013         }
1014     }
1015   else
1016     {
1017       gint amount;
1018       gint space;
1019
1020       if (orientation == GTK_ORIENTATION_HORIZONTAL)
1021         space = width - padding.left - padding.right;
1022       else
1023         space = height - padding.top - padding.bottom;
1024
1025       amount = space * gtk_progress_bar_get_fraction (pbar);
1026
1027       gtk_progress_bar_paint_continuous (pbar, cr, amount, orientation, inverted, width, height);
1028
1029       if (priv->show_text)
1030         gtk_progress_bar_paint_text (pbar, cr, -1, amount, orientation, inverted, width, height);
1031     }
1032
1033   return FALSE;
1034 }
1035
1036 static void
1037 gtk_progress_bar_set_activity_mode (GtkProgressBar *pbar,
1038                                     gboolean        activity_mode)
1039 {
1040   GtkProgressBarPrivate *priv = pbar->priv;
1041
1042   activity_mode = !!activity_mode;
1043
1044   if (priv->activity_mode != activity_mode)
1045     {
1046       priv->activity_mode = activity_mode;
1047
1048       if (priv->activity_mode)
1049         gtk_progress_bar_act_mode_enter (pbar);
1050
1051       gtk_widget_queue_resize (GTK_WIDGET (pbar));
1052     }
1053 }
1054
1055 /**
1056  * gtk_progress_bar_set_fraction:
1057  * @pbar: a #GtkProgressBar
1058  * @fraction: fraction of the task that's been completed
1059  *
1060  * Causes the progress bar to "fill in" the given fraction
1061  * of the bar. The fraction should be between 0.0 and 1.0,
1062  * inclusive.
1063  *
1064  */
1065 void
1066 gtk_progress_bar_set_fraction (GtkProgressBar *pbar,
1067                                gdouble         fraction)
1068 {
1069   GtkProgressBarPrivate* priv;
1070
1071   g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1072
1073   priv = pbar->priv;
1074
1075   priv->fraction = CLAMP(fraction, 0.0, 1.0);
1076   gtk_progress_bar_set_activity_mode (pbar, FALSE);
1077   gtk_progress_bar_real_update (pbar);
1078
1079   g_object_notify (G_OBJECT (pbar), "fraction");
1080 }
1081
1082 /**
1083  * gtk_progress_bar_pulse:
1084  * @pbar: a #GtkProgressBar
1085  *
1086  * Indicates that some progress is made, but you don't know how much.
1087  * Causes the progress bar to enter "activity mode," where a block
1088  * bounces back and forth. Each call to gtk_progress_bar_pulse()
1089  * causes the block to move by a little bit (the amount of movement
1090  * per pulse is determined by gtk_progress_bar_set_pulse_step()).
1091  **/
1092 void
1093 gtk_progress_bar_pulse (GtkProgressBar *pbar)
1094 {
1095   g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1096
1097   gtk_progress_bar_set_activity_mode (pbar, TRUE);
1098   gtk_progress_bar_real_update (pbar);
1099 }
1100
1101 /**
1102  * gtk_progress_bar_set_text:
1103  * @pbar: a #GtkProgressBar
1104  * @text: (allow-none): a UTF-8 string, or %NULL
1105  *
1106  * Causes the given @text to appear superimposed on the progress bar.
1107  *
1108  * If @text is %NULL and #GtkProgressBar:show-text is %TRUE, the current
1109  * value of #GtkProgressBar:fraction will be displayed as a percentage.
1110  *
1111  * If @text is non-%NULL and #GtkProgressBar:show-text is %TRUE, the text will
1112  * be displayed. In this case, it will not display the progress percentage.
1113  * If @text is the empty string, the progress bar will still be styled and sized
1114  * suitably for containing text, as long as #GtkProgressBar:show-text is %TRUE.
1115  **/
1116 void
1117 gtk_progress_bar_set_text (GtkProgressBar *pbar,
1118                            const gchar    *text)
1119 {
1120   GtkProgressBarPrivate *priv;
1121
1122   g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1123
1124   priv = pbar->priv;
1125
1126   /* Don't notify again if nothing's changed. */
1127   if (g_strcmp0 (priv->text, text) == 0)
1128     return;
1129
1130   g_free (priv->text);
1131   priv->text = g_strdup (text);
1132
1133   gtk_widget_queue_resize (GTK_WIDGET (pbar));
1134
1135   g_object_notify (G_OBJECT (pbar), "text");
1136 }
1137
1138 /**
1139  * gtk_progress_bar_set_show_text:
1140  * @pbar: a #GtkProgressBar
1141  * @show_text: whether to show superimposed text
1142  *
1143  * Sets whether the progressbar will show text superimposed
1144  * over the bar. The shown text is either the value of
1145  * the #GtkProgressBar::text property or, if that is %NULL,
1146  * the #GtkProgressBar::fraction value, as a percentage.
1147  *
1148  * To make a progress bar that is styled and sized suitably for containing
1149  * text (even if the actual text is blank), set #GtkProgressBar:show-text to
1150  * %TRUE and #GtkProgressBar:text to the empty string (not %NULL).
1151  *
1152  * Since: 3.0
1153  */
1154 void
1155 gtk_progress_bar_set_show_text (GtkProgressBar *pbar,
1156                                 gboolean        show_text)
1157 {
1158   GtkProgressBarPrivate *priv;
1159
1160   g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1161
1162   priv = pbar->priv;
1163
1164   show_text = !!show_text;
1165
1166   if (priv->show_text != show_text)
1167     {
1168       priv->show_text = show_text;
1169
1170       gtk_widget_queue_resize (GTK_WIDGET (pbar));
1171
1172       g_object_notify (G_OBJECT (pbar), "show-text");
1173     }
1174 }
1175
1176 /**
1177  * gtk_progress_bar_get_show_text:
1178  * @pbar: a #GtkProgressBar
1179  *
1180  * Gets the value of the #GtkProgressBar::show-text property.
1181  * See gtk_progress_bar_set_show_text().
1182  *
1183  * Returns: %TRUE if text is shown in the progress bar
1184  *
1185  * Since: 3.0
1186  */
1187 gboolean
1188 gtk_progress_bar_get_show_text (GtkProgressBar *pbar)
1189 {
1190   g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), FALSE);
1191
1192   return pbar->priv->show_text;
1193 }
1194
1195 /**
1196  * gtk_progress_bar_set_pulse_step:
1197  * @pbar: a #GtkProgressBar
1198  * @fraction: fraction between 0.0 and 1.0
1199  *
1200  * Sets the fraction of total progress bar length to move the
1201  * bouncing block for each call to gtk_progress_bar_pulse().
1202  **/
1203 void
1204 gtk_progress_bar_set_pulse_step (GtkProgressBar *pbar,
1205                                  gdouble         fraction)
1206 {
1207   GtkProgressBarPrivate *priv;
1208
1209   g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1210
1211   priv = pbar->priv;
1212
1213   priv->pulse_fraction = fraction;
1214
1215   g_object_notify (G_OBJECT (pbar), "pulse-step");
1216 }
1217
1218 static void
1219 gtk_progress_bar_set_orientation (GtkProgressBar *pbar,
1220                                   GtkOrientation  orientation)
1221 {
1222   GtkProgressBarPrivate *priv = pbar->priv;
1223
1224   if (priv->orientation != orientation)
1225     {
1226       priv->orientation = orientation;
1227       _gtk_orientable_set_style_classes (GTK_ORIENTABLE (pbar));
1228
1229       gtk_widget_queue_resize (GTK_WIDGET (pbar));
1230     }
1231 }
1232
1233 /**
1234  * gtk_progress_bar_set_inverted:
1235  * @pbar: a #GtkProgressBar
1236  * @inverted: %TRUE to invert the progress bar
1237  *
1238  * Progress bars normally grow from top to bottom or left to right.
1239  * Inverted progress bars grow in the opposite direction.
1240  */
1241 void
1242 gtk_progress_bar_set_inverted (GtkProgressBar *pbar,
1243                                gboolean        inverted)
1244 {
1245   GtkProgressBarPrivate *priv;
1246
1247   g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1248
1249   priv = pbar->priv;
1250
1251   if (priv->inverted != inverted)
1252     {
1253       priv->inverted = inverted;
1254
1255       gtk_widget_queue_resize (GTK_WIDGET (pbar));
1256
1257       g_object_notify (G_OBJECT (pbar), "inverted");
1258     }
1259 }
1260
1261 /**
1262  * gtk_progress_bar_get_text:
1263  * @pbar: a #GtkProgressBar
1264  *
1265  * Retrieves the text displayed superimposed on the progress bar,
1266  * if any, otherwise %NULL. The return value is a reference
1267  * to the text, not a copy of it, so will become invalid
1268  * if you change the text in the progress bar.
1269  *
1270  * Return value: text, or %NULL; this string is owned by the widget
1271  * and should not be modified or freed.
1272  **/
1273 const gchar*
1274 gtk_progress_bar_get_text (GtkProgressBar *pbar)
1275 {
1276   g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), NULL);
1277
1278   return pbar->priv->text;
1279 }
1280
1281 /**
1282  * gtk_progress_bar_get_fraction:
1283  * @pbar: a #GtkProgressBar
1284  *
1285  * Returns the current fraction of the task that's been completed.
1286  *
1287  * Return value: a fraction from 0.0 to 1.0
1288  **/
1289 gdouble
1290 gtk_progress_bar_get_fraction (GtkProgressBar *pbar)
1291 {
1292   g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), 0);
1293
1294   return pbar->priv->fraction;
1295 }
1296
1297 /**
1298  * gtk_progress_bar_get_pulse_step:
1299  * @pbar: a #GtkProgressBar
1300  *
1301  * Retrieves the pulse step set with gtk_progress_bar_set_pulse_step()
1302  *
1303  * Return value: a fraction from 0.0 to 1.0
1304  **/
1305 gdouble
1306 gtk_progress_bar_get_pulse_step (GtkProgressBar *pbar)
1307 {
1308   g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), 0);
1309
1310   return pbar->priv->pulse_fraction;
1311 }
1312
1313 /**
1314  * gtk_progress_bar_get_inverted:
1315  * @pbar: a #GtkProgressBar
1316  *
1317  * Gets the value set by gtk_progress_bar_set_inverted()
1318  *
1319  * Return value: %TRUE if the progress bar is inverted
1320  */
1321 gboolean
1322 gtk_progress_bar_get_inverted (GtkProgressBar *pbar)
1323 {
1324   g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), FALSE);
1325
1326   return pbar->priv->inverted;
1327 }
1328
1329 /**
1330  * gtk_progress_bar_set_ellipsize:
1331  * @pbar: a #GtkProgressBar
1332  * @mode: a #PangoEllipsizeMode
1333  *
1334  * Sets the mode used to ellipsize (add an ellipsis: "...") the text
1335  * if there is not enough space to render the entire string.
1336  *
1337  * Since: 2.6
1338  **/
1339 void
1340 gtk_progress_bar_set_ellipsize (GtkProgressBar     *pbar,
1341                                 PangoEllipsizeMode  mode)
1342 {
1343   GtkProgressBarPrivate *priv;
1344
1345   g_return_if_fail (GTK_IS_PROGRESS_BAR (pbar));
1346   g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE &&
1347                     mode <= PANGO_ELLIPSIZE_END);
1348
1349   priv = pbar->priv;
1350
1351   if ((PangoEllipsizeMode)priv->ellipsize != mode)
1352     {
1353       priv->ellipsize = mode;
1354
1355       g_object_notify (G_OBJECT (pbar), "ellipsize");
1356       gtk_widget_queue_resize (GTK_WIDGET (pbar));
1357     }
1358 }
1359
1360 /**
1361  * gtk_progress_bar_get_ellipsize:
1362  * @pbar: a #GtkProgressBar
1363  *
1364  * Returns the ellipsizing position of the progressbar.
1365  * See gtk_progress_bar_set_ellipsize().
1366  *
1367  * Return value: #PangoEllipsizeMode
1368  *
1369  * Since: 2.6
1370  **/
1371 PangoEllipsizeMode
1372 gtk_progress_bar_get_ellipsize (GtkProgressBar *pbar)
1373 {
1374   g_return_val_if_fail (GTK_IS_PROGRESS_BAR (pbar), PANGO_ELLIPSIZE_NONE);
1375
1376   return pbar->priv->ellipsize;
1377 }