]> Pileus Git - ~andy/gtk/blob - gtk/gtkcellrendererprogress.c
Move documentation to inline comments: GtkCellRendererProgress
[~andy/gtk] / gtk / gtkcellrendererprogress.c
1 /* gtkcellrendererprogress.c
2  * Copyright (C) 2002 Naba Kumar <kh_naba@users.sourceforge.net>
3  * heavily modified by Jörgen Scheibengruber <mfcn@gmx.de>
4  * heavily modified by Marco Pesenti Gritti <marco@gnome.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21 /*
22  * Modified by the GTK+ Team and others 1997-2007.  See the AUTHORS
23  * file for a list of people on the GTK+ Team.  See the ChangeLog
24  * files for a list of changes.  These files are distributed with
25  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
26  */
27
28 #include "config.h"
29 #include <stdlib.h>
30
31 #include "gtkcellrendererprogress.h"
32 #include "gtkorientable.h"
33 #include "gtkprivate.h"
34 #include "gtkintl.h"
35
36
37 /**
38  * SECTION:gtkcellrendererprogress
39  * @Short_description: Renders numbers as progress bars
40  * @Title: GtkCellRendererProgress
41  *
42  * #GtkCellRendererProgress renders a numeric value as a progress par in a cell.
43  * Additionally, it can display a text on top of the progress bar.
44  *
45  * The #GtkCellRendererProgress cell renderer was added in GTK+ 2.6.
46  */
47
48
49 enum
50 {
51   PROP_0,
52   PROP_VALUE,
53   PROP_TEXT,
54   PROP_PULSE,
55   PROP_TEXT_XALIGN,
56   PROP_TEXT_YALIGN,
57   PROP_ORIENTATION,
58   PROP_INVERTED
59 };
60
61 struct _GtkCellRendererProgressPrivate
62 {
63   gint value;
64   gchar *text;
65   gchar *label;
66   gint min_h;
67   gint min_w;
68   gint pulse;
69   gint offset;
70   gfloat text_xalign;
71   gfloat text_yalign;
72   GtkOrientation orientation;
73   gboolean inverted;
74 };
75
76 static void gtk_cell_renderer_progress_finalize     (GObject                 *object);
77 static void gtk_cell_renderer_progress_get_property (GObject                 *object,
78                                                      guint                    param_id,
79                                                      GValue                  *value,
80                                                      GParamSpec              *pspec);
81 static void gtk_cell_renderer_progress_set_property (GObject                 *object,
82                                                      guint                    param_id,
83                                                      const GValue            *value,
84                                                      GParamSpec              *pspec);
85 static void gtk_cell_renderer_progress_set_value    (GtkCellRendererProgress *cellprogress,
86                                                      gint                     value);
87 static void gtk_cell_renderer_progress_set_text     (GtkCellRendererProgress *cellprogress,
88                                                      const gchar             *text);
89 static void gtk_cell_renderer_progress_set_pulse    (GtkCellRendererProgress *cellprogress,
90                                                      gint                     pulse);
91 static void compute_dimensions                      (GtkCellRenderer         *cell,
92                                                      GtkWidget               *widget,
93                                                      const gchar             *text,
94                                                      gint                    *width,
95                                                      gint                    *height);
96 static void gtk_cell_renderer_progress_get_size     (GtkCellRenderer         *cell,
97                                                      GtkWidget               *widget,
98                                                      const GdkRectangle      *cell_area,
99                                                      gint                    *x_offset,
100                                                      gint                    *y_offset,
101                                                      gint                    *width,
102                                                      gint                    *height);
103 static void gtk_cell_renderer_progress_render       (GtkCellRenderer         *cell,
104                                                      cairo_t                 *cr,
105                                                      GtkWidget               *widget,
106                                                      const GdkRectangle      *background_area,
107                                                      const GdkRectangle      *cell_area,
108                                                      GtkCellRendererState    flags);
109
110      
111 G_DEFINE_TYPE_WITH_CODE (GtkCellRendererProgress, gtk_cell_renderer_progress, GTK_TYPE_CELL_RENDERER,
112                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
113
114 static void
115 gtk_cell_renderer_progress_class_init (GtkCellRendererProgressClass *klass)
116 {
117   GObjectClass *object_class = G_OBJECT_CLASS (klass);
118   GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass);
119   
120   object_class->finalize = gtk_cell_renderer_progress_finalize;
121   object_class->get_property = gtk_cell_renderer_progress_get_property;
122   object_class->set_property = gtk_cell_renderer_progress_set_property;
123   
124   cell_class->get_size = gtk_cell_renderer_progress_get_size;
125   cell_class->render = gtk_cell_renderer_progress_render;
126   
127   /**
128    * GtkCellRendererProgress:value:
129    *
130    * The "value" property determines the percentage to which the
131    * progress bar will be "filled in".
132    *
133    * Since: 2.6
134    **/
135   g_object_class_install_property (object_class,
136                                    PROP_VALUE,
137                                    g_param_spec_int ("value",
138                                                      P_("Value"),
139                                                      P_("Value of the progress bar"),
140                                                      0, 100, 0,
141                                                      GTK_PARAM_READWRITE));
142
143   /**
144    * GtkCellRendererProgress:text:
145    * 
146    * The "text" property determines the label which will be drawn
147    * over the progress bar. Setting this property to %NULL causes the default 
148    * label to be displayed. Setting this property to an empty string causes 
149    * no label to be displayed.
150    *
151    * Since: 2.6
152    **/
153   g_object_class_install_property (object_class,
154                                    PROP_TEXT,
155                                    g_param_spec_string ("text",
156                                                         P_("Text"),
157                                                         P_("Text on the progress bar"),
158                                                         NULL,
159                                                         GTK_PARAM_READWRITE));
160
161   /**
162    * GtkCellRendererProgress:pulse:
163    * 
164    * Setting this to a non-negative value causes the cell renderer to
165    * enter "activity mode", where a block bounces back and forth to 
166    * indicate that some progress is made, without specifying exactly how
167    * much.
168    *
169    * Each increment of the property causes the block to move by a little 
170    * bit.
171    *
172    * To indicate that the activity has not started yet, set the property
173    * to zero. To indicate completion, set the property to %G_MAXINT.
174    *
175    * Since: 2.12
176    */
177   g_object_class_install_property (object_class,
178                                    PROP_PULSE,
179                                    g_param_spec_int ("pulse",
180                                                      P_("Pulse"),
181                                                      P_("Set this to positive values to indicate that some progress is made, but you don't know how much."),
182                                                      -1, G_MAXINT, -1,
183                                                      GTK_PARAM_READWRITE));
184
185   /**
186    * GtkCellRendererProgress:text-xalign:
187    *
188    * The "text-xalign" property controls the horizontal alignment of the
189    * text in the progress bar.  Valid values range from 0 (left) to 1
190    * (right).  Reserved for RTL layouts.
191    *
192    * Since: 2.12
193    */
194   g_object_class_install_property (object_class,
195                                    PROP_TEXT_XALIGN,
196                                    g_param_spec_float ("text-xalign",
197                                                        P_("Text x alignment"),
198                                                        P_("The horizontal text alignment, from 0 (left) to 1 (right). Reversed for RTL layouts."),
199                                                        0.0, 1.0, 0.5,
200                                                        GTK_PARAM_READWRITE));
201
202   /**
203    * GtkCellRendererProgress:text-yalign:
204    *
205    * The "text-yalign" property controls the vertical alignment of the
206    * text in the progress bar.  Valid values range from 0 (top) to 1
207    * (bottom).
208    *
209    * Since: 2.12
210    */
211   g_object_class_install_property (object_class,
212                                    PROP_TEXT_YALIGN,
213                                    g_param_spec_float ("text-yalign",
214                                                        P_("Text y alignment"),
215                                                        P_("The vertical text alignment, from 0 (top) to 1 (bottom)."),
216                                                        0.0, 1.0, 0.5,
217                                                        GTK_PARAM_READWRITE));
218
219   g_object_class_override_property (object_class,
220                                     PROP_ORIENTATION,
221                                     "orientation");
222
223   g_object_class_install_property (object_class,
224                                    PROP_INVERTED,
225                                    g_param_spec_boolean ("inverted",
226                                                          P_("Inverted"),
227                                                          P_("Invert the direction in which the progress bar grows"),
228                                                          FALSE,
229                                                          GTK_PARAM_READWRITE));
230
231   g_type_class_add_private (object_class, 
232                             sizeof (GtkCellRendererProgressPrivate));
233 }
234
235 static void
236 gtk_cell_renderer_progress_init (GtkCellRendererProgress *cellprogress)
237 {
238   GtkCellRendererProgressPrivate *priv;
239
240   cellprogress->priv = G_TYPE_INSTANCE_GET_PRIVATE (cellprogress,
241                                                     GTK_TYPE_CELL_RENDERER_PROGRESS,
242                                                     GtkCellRendererProgressPrivate);
243   priv = cellprogress->priv;
244
245   priv->value = 0;
246   priv->text = NULL;
247   priv->label = NULL;
248   priv->min_w = -1;
249   priv->min_h = -1;
250   priv->pulse = -1;
251   priv->offset = 0;
252
253   priv->text_xalign = 0.5;
254   priv->text_yalign = 0.5;
255
256   priv->orientation = GTK_ORIENTATION_HORIZONTAL,
257   priv->inverted = FALSE;
258 }
259
260
261 /**
262  * gtk_cell_renderer_progress_new:
263  * 
264  * Creates a new #GtkCellRendererProgress. 
265  *
266  * Return value: the new cell renderer
267  *
268  * Since: 2.6
269  **/
270 GtkCellRenderer*
271 gtk_cell_renderer_progress_new (void)
272 {
273   return g_object_new (GTK_TYPE_CELL_RENDERER_PROGRESS, NULL);
274 }
275
276 static void
277 gtk_cell_renderer_progress_finalize (GObject *object)
278 {
279   GtkCellRendererProgress *cellprogress = GTK_CELL_RENDERER_PROGRESS (object);
280   GtkCellRendererProgressPrivate *priv = cellprogress->priv;
281   
282   g_free (priv->text);
283   g_free (priv->label);
284   
285   G_OBJECT_CLASS (gtk_cell_renderer_progress_parent_class)->finalize (object);
286 }
287
288 static void
289 gtk_cell_renderer_progress_get_property (GObject *object,
290                                          guint param_id,
291                                          GValue *value,
292                                          GParamSpec *pspec)
293 {
294   GtkCellRendererProgress *cellprogress = GTK_CELL_RENDERER_PROGRESS (object);
295   GtkCellRendererProgressPrivate *priv = cellprogress->priv;
296   
297   switch (param_id)
298     {
299     case PROP_VALUE:
300       g_value_set_int (value, priv->value);
301       break;
302     case PROP_TEXT:
303       g_value_set_string (value, priv->text);
304       break;
305     case PROP_PULSE:
306       g_value_set_int (value, priv->pulse);
307       break;
308     case PROP_TEXT_XALIGN:
309       g_value_set_float (value, priv->text_xalign);
310       break;
311     case PROP_TEXT_YALIGN:
312       g_value_set_float (value, priv->text_yalign);
313       break;
314     case PROP_ORIENTATION:
315       g_value_set_enum (value, priv->orientation);
316       break;
317     case PROP_INVERTED:
318       g_value_set_boolean (value, priv->inverted);
319       break;
320     default:
321       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
322     }
323 }
324
325 static void
326 gtk_cell_renderer_progress_set_property (GObject *object,
327                                          guint param_id,
328                                          const GValue *value,
329                                          GParamSpec   *pspec)
330 {
331   GtkCellRendererProgress *cellprogress = GTK_CELL_RENDERER_PROGRESS (object);
332   GtkCellRendererProgressPrivate *priv = cellprogress->priv;
333   
334   switch (param_id)
335     {
336     case PROP_VALUE:
337       gtk_cell_renderer_progress_set_value (cellprogress, 
338                                             g_value_get_int (value));
339       break;
340     case PROP_TEXT:
341       gtk_cell_renderer_progress_set_text (cellprogress,
342                                            g_value_get_string (value));
343       break;
344     case PROP_PULSE:
345       gtk_cell_renderer_progress_set_pulse (cellprogress, 
346                                             g_value_get_int (value));
347       break;
348     case PROP_TEXT_XALIGN:
349       priv->text_xalign = g_value_get_float (value);
350       break;
351     case PROP_TEXT_YALIGN:
352       priv->text_yalign = g_value_get_float (value);
353       break;
354     case PROP_ORIENTATION:
355       priv->orientation = g_value_get_enum (value);
356       break;
357     case PROP_INVERTED:
358       priv->orientation = g_value_get_boolean (value);
359       break;
360     default:
361       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
362     }
363 }
364
365 static void
366 recompute_label (GtkCellRendererProgress *cellprogress)
367 {
368   GtkCellRendererProgressPrivate *priv = cellprogress->priv;
369   gchar *label;
370
371   if (priv->text)
372     label = g_strdup (priv->text);
373   else if (priv->pulse < 0)
374     label = g_strdup_printf (C_("progress bar label", "%d %%"), priv->value);
375   else
376     label = NULL;
377  
378   g_free (priv->label);
379   priv->label = label;
380 }
381
382 static void
383 gtk_cell_renderer_progress_set_value (GtkCellRendererProgress *cellprogress, 
384                                       gint                     value)
385 {
386   cellprogress->priv->value = value;
387
388   recompute_label (cellprogress);
389 }
390
391 static void
392 gtk_cell_renderer_progress_set_text (GtkCellRendererProgress *cellprogress, 
393                                      const gchar             *text)
394 {
395   gchar *new_text;
396
397   new_text = g_strdup (text);
398   g_free (cellprogress->priv->text);
399   cellprogress->priv->text = new_text;
400
401   recompute_label (cellprogress);
402 }
403
404 static void
405 gtk_cell_renderer_progress_set_pulse (GtkCellRendererProgress *cellprogress, 
406                                       gint                     pulse)
407 {
408    GtkCellRendererProgressPrivate *priv = cellprogress->priv;
409
410    if (pulse != priv->pulse)
411      {
412        if (pulse <= 0)
413          priv->offset = 0;
414        else
415          priv->offset = pulse;
416      }
417
418    priv->pulse = pulse;
419
420    recompute_label (cellprogress);
421 }
422
423 static void
424 compute_dimensions (GtkCellRenderer *cell,
425                     GtkWidget       *widget, 
426                     const gchar     *text, 
427                     gint            *width, 
428                     gint            *height)
429 {
430   PangoRectangle logical_rect;
431   PangoLayout *layout;
432   gint xpad, ypad;
433   
434   layout = gtk_widget_create_pango_layout (widget, text);
435   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
436
437   gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
438   
439   if (width)
440     *width = logical_rect.width + xpad * 2;
441   
442   if (height)
443     *height = logical_rect.height + ypad * 2;
444
445   g_object_unref (layout);
446 }
447
448 static void
449 gtk_cell_renderer_progress_get_size (GtkCellRenderer    *cell,
450                                      GtkWidget          *widget,
451                                      const GdkRectangle *cell_area,
452                                      gint               *x_offset,
453                                      gint               *y_offset,
454                                      gint               *width,
455                                      gint               *height)
456 {
457   GtkCellRendererProgress *cellprogress = GTK_CELL_RENDERER_PROGRESS (cell);
458   GtkCellRendererProgressPrivate *priv = cellprogress->priv;
459   gint w, h;
460   gchar *text;
461
462   if (priv->min_w < 0)
463     {
464       text = g_strdup_printf (C_("progress bar label", "%d %%"), 100);
465       compute_dimensions (cell, widget, text,
466                           &priv->min_w,
467                           &priv->min_h);
468       g_free (text);
469     }
470   
471   compute_dimensions (cell, widget, priv->label, &w, &h);
472   
473   if (width)
474     *width = MAX (priv->min_w, w);
475   
476   if (height)
477     *height = MIN (priv->min_h, h);
478
479   /* FIXME: at the moment cell_area is only set when we are requesting
480    * the size for drawing the focus rectangle. We now just return
481    * the last size we used for drawing the progress bar, which will
482    * work for now. Not a really nice solution though.
483    */
484   if (cell_area)
485     {
486       if (width)
487         *width = cell_area->width;
488       if (height)
489         *height = cell_area->height;
490     }
491
492   if (x_offset) *x_offset = 0;
493   if (y_offset) *y_offset = 0;
494 }
495
496 static inline gint
497 get_bar_size (gint pulse,
498               gint value,
499               gint full_size)
500 {
501   gint bar_size;
502
503   if (pulse < 0)
504     bar_size = full_size * MAX (0, value) / 100;
505   else if (pulse == 0)
506     bar_size = 0;
507   else if (pulse == G_MAXINT)
508     bar_size = full_size;
509   else
510     bar_size = MAX (2, full_size / 5);
511
512   return bar_size;
513 }
514
515 static inline gint
516 get_bar_position (gint     start,
517                   gint     full_size,
518                   gint     bar_size,
519                   gint     pulse,
520                   gint     offset,
521                   gboolean is_rtl)
522 {
523   gint position;
524
525   if (pulse < 0 || pulse == 0 || pulse == G_MAXINT)
526     {
527       position = is_rtl ? (start + full_size - bar_size) : start;
528     }
529   else
530     {
531       position = (is_rtl ? offset + 12 : offset) % 24;
532       if (position > 12)
533         position = 24 - position;
534       position = start + full_size * position / 15;
535     }
536
537   return position;
538 }
539
540 static void
541 gtk_cell_renderer_progress_render (GtkCellRenderer      *cell,
542                                    cairo_t              *cr,
543                                    GtkWidget            *widget,
544                                    const GdkRectangle   *background_area,
545                                    const GdkRectangle   *cell_area,
546                                    GtkCellRendererState  flags)
547 {
548   GtkCellRendererProgress *cellprogress = GTK_CELL_RENDERER_PROGRESS (cell);
549   GtkCellRendererProgressPrivate *priv= cellprogress->priv;
550   GtkStyleContext *context;
551   GtkBorder padding, border;
552   PangoLayout *layout;
553   PangoRectangle logical_rect;
554   gint x, y, w, h, x_pos, y_pos, bar_position, bar_size, start, full_size;
555   gint xpad, ypad;
556   GdkRectangle clip;
557   gboolean is_rtl;
558
559   context = gtk_widget_get_style_context (widget);
560   is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
561
562   gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
563   x = cell_area->x + xpad;
564   y = cell_area->y + ypad;
565   w = cell_area->width - xpad * 2;
566   h = cell_area->height - ypad * 2;
567
568   gtk_style_context_save (context);
569   gtk_style_context_add_class (context, GTK_STYLE_CLASS_TROUGH);
570
571   gtk_render_background (context, cr, x, y, w, h);
572   gtk_render_frame (context, cr, x, y, w, h);
573
574   gtk_style_context_get_border (context, GTK_STATE_FLAG_NORMAL, &border);
575   gtk_style_context_get_padding (context, GTK_STATE_FLAG_NORMAL, &padding);
576
577   x += border.left + padding.left;
578   y += border.top + padding.top;
579   w -= border.left + border.right + padding.left + padding.right;
580   h -= border.top + border.bottom + padding.top + padding.bottom;
581
582   gtk_style_context_restore (context);
583
584   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
585     {
586       clip.y = y;
587       clip.height = h;
588
589       start = x;
590       full_size = w;
591
592       bar_size = get_bar_size (priv->pulse, priv->value, full_size);
593
594       if (!priv->inverted)
595         bar_position = get_bar_position (start, full_size, bar_size,
596                                          priv->pulse, priv->offset, is_rtl);
597       else
598         bar_position = get_bar_position (start, full_size, bar_size,
599                                          priv->pulse, priv->offset, !is_rtl);
600
601       clip.width = bar_size;
602       clip.x = bar_position;
603     }
604   else
605     {
606       clip.x = x;
607       clip.width = w;
608
609       start = y;
610       full_size = h;
611
612       bar_size = get_bar_size (priv->pulse, priv->value, full_size);
613
614       if (priv->inverted)
615         bar_position = get_bar_position (start, full_size, bar_size,
616                                          priv->pulse, priv->offset, TRUE);
617       else
618         bar_position = get_bar_position (start, full_size, bar_size,
619                                          priv->pulse, priv->offset, FALSE);
620
621       clip.height = bar_size;
622       clip.y = bar_position;
623     }
624
625   gtk_style_context_save (context);
626   gtk_style_context_add_class (context, GTK_STYLE_CLASS_PROGRESSBAR);
627
628   if (bar_size > 0)
629     gtk_render_activity (context, cr,
630                          clip.x, clip.y,
631                          clip.width, clip.height);
632
633   gtk_style_context_restore (context);
634
635   if (priv->label)
636     {
637       gfloat text_xalign;
638
639       layout = gtk_widget_create_pango_layout (widget, priv->label);
640       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
641
642       if (gtk_widget_get_direction (widget) != GTK_TEXT_DIR_LTR)
643         text_xalign = 1.0 - priv->text_xalign;
644       else
645         text_xalign = priv->text_xalign;
646
647       x_pos = x + padding.left + text_xalign *
648         (w - padding.left - padding.right - logical_rect.width);
649
650       y_pos = y + padding.top + priv->text_yalign *
651         (h - padding.top - padding.bottom - logical_rect.height);
652
653       cairo_save (cr);
654       gdk_cairo_rectangle (cr, &clip);
655       cairo_clip (cr);
656
657       gtk_style_context_save (context);
658       gtk_style_context_add_class (context, GTK_STYLE_CLASS_PROGRESSBAR);
659
660       gtk_render_layout (context, cr,
661                          x_pos, y_pos,
662                          layout);
663
664       gtk_style_context_restore (context);
665       cairo_restore (cr);
666
667       gtk_style_context_save (context);
668       gtk_style_context_add_class (context, GTK_STYLE_CLASS_TROUGH);
669
670       if (bar_position > start)
671         {
672           if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
673             {
674               clip.x = x;
675               clip.width = bar_position - x;
676             }
677           else
678             {
679               clip.y = y;
680               clip.height = bar_position - y;
681             }
682
683           cairo_save (cr);
684           gdk_cairo_rectangle (cr, &clip);
685           cairo_clip (cr);
686
687           gtk_render_layout (context, cr,
688                              x_pos, y_pos,
689                              layout);
690
691           cairo_restore (cr);
692         }
693
694       if (bar_position + bar_size < start + full_size)
695         {
696           if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
697             {
698               clip.x = bar_position + bar_size;
699               clip.width = x + w - (bar_position + bar_size);
700             }
701           else
702             {
703               clip.y = bar_position + bar_size;
704               clip.height = y + h - (bar_position + bar_size);
705             }
706
707           cairo_save (cr);
708           gdk_cairo_rectangle (cr, &clip);
709           cairo_clip (cr);
710
711           gtk_render_layout (context, cr,
712                              x_pos, y_pos,
713                              layout);
714
715           cairo_restore (cr);
716         }
717
718       gtk_style_context_restore (context);
719       g_object_unref (layout);
720     }
721 }