Evince
Evince is a document viewer capable of displaying multiple and single page document formats like PDF and Postscript.
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
gd-two-lines-renderer.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2011 Red Hat, Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or (at your
7  * option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
12  * License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17  *
18  * Author: Cosimo Cecchi <cosimoc@redhat.com>
19  *
20  */
21 
22 #include "gd-two-lines-renderer.h"
23 #include <string.h>
24 
25 G_DEFINE_TYPE (GdTwoLinesRenderer, gd_two_lines_renderer, GTK_TYPE_CELL_RENDERER_TEXT)
26 
28  gchar *line_two;
29  gint text_lines;
30 };
31 
32 enum {
36 };
37 
38 static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
39 
40 static PangoLayout *
41 create_layout_with_attrs (GtkWidget *widget,
42  const GdkRectangle *cell_area,
43  GdTwoLinesRenderer *self,
44  PangoEllipsizeMode ellipsize)
45 {
46  PangoLayout *layout;
47  gint wrap_width, xpad;
48  PangoWrapMode wrap_mode;
49  PangoAlignment alignment;
50 
51  g_object_get (self,
52  "wrap-width", &wrap_width,
53  "wrap-mode", &wrap_mode,
54  "alignment", &alignment,
55  "xpad", &xpad,
56  NULL);
57 
58  layout = pango_layout_new (gtk_widget_get_pango_context (widget));
59 
60  pango_layout_set_ellipsize (layout, ellipsize);
61  pango_layout_set_alignment (layout, alignment);
62 
63  if (wrap_width != -1)
64  {
65  pango_layout_set_width (layout, wrap_width * PANGO_SCALE);
66  pango_layout_set_wrap (layout, wrap_mode);
67  }
68  else
69  {
70  if (cell_area != NULL)
71  pango_layout_set_width (layout, (cell_area->width - 2 * xpad) * PANGO_SCALE);
72  else
73  pango_layout_set_width (layout, -1);
74 
75  pango_layout_set_wrap (layout, PANGO_WRAP_CHAR);
76  }
77 
78  return layout;
79 }
80 
81 static void
83  const GdkRectangle *cell_area,
84  GtkWidget *widget,
85  PangoLayout **layout_one,
86  PangoLayout **layout_two)
87 {
88  PangoLayout *line_one;
89  PangoLayout *line_two = NULL;
90  gchar *text = NULL;
91 
92  g_object_get (self,
93  "text", &text,
94  NULL);
95 
96  line_one = create_layout_with_attrs (widget, cell_area,
97  self, PANGO_ELLIPSIZE_MIDDLE);
98 
99  if (self->priv->line_two == NULL ||
100  g_strcmp0 (self->priv->line_two, "") == 0)
101  {
102  pango_layout_set_height (line_one, - (self->priv->text_lines));
103 
104  if (text != NULL)
105  pango_layout_set_text (line_one, text, -1);
106  }
107  else
108  {
109  line_two = create_layout_with_attrs (widget, cell_area,
110  self, PANGO_ELLIPSIZE_END);
111 
112  pango_layout_set_height (line_one, - (self->priv->text_lines - 1));
113  pango_layout_set_height (line_two, -1);
114  pango_layout_set_text (line_two, self->priv->line_two, -1);
115 
116  if (text != NULL)
117  pango_layout_set_text (line_one, text, -1);
118  }
119 
120  if (layout_one)
121  *layout_one = line_one;
122  if (layout_two)
123  *layout_two = line_two;
124 
125  g_free (text);
126 }
127 
128 static void
129 gd_two_lines_renderer_get_size (GtkCellRenderer *cell,
130  GtkWidget *widget,
131  PangoLayout *layout_1,
132  PangoLayout *layout_2,
133  gint *width,
134  gint *height,
135  const GdkRectangle *cell_area,
136  gint *x_offset_1,
137  gint *x_offset_2,
138  gint *y_offset)
139 {
141  gint xpad, ypad;
142  PangoLayout *layout_one, *layout_two;
143  GdkRectangle layout_one_rect, layout_two_rect, layout_union;
144 
145  if (layout_1 == NULL)
146  {
147  gd_two_lines_renderer_prepare_layouts (self, cell_area, widget, &layout_one, &layout_two);
148  }
149  else
150  {
151  layout_one = g_object_ref (layout_1);
152 
153  if (layout_2 != NULL)
154  layout_two = g_object_ref (layout_2);
155  else
156  layout_two = NULL;
157  }
158 
159  gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
160  pango_layout_get_pixel_extents (layout_one, NULL, (PangoRectangle *) &layout_one_rect);
161 
162  if (layout_two != NULL)
163  {
164  pango_layout_get_pixel_extents (layout_two, NULL, (PangoRectangle *) &layout_two_rect);
165 
166  layout_union.width = MAX (layout_one_rect.width, layout_two_rect.width);
167  layout_union.height = layout_one_rect.height + layout_two_rect.height;
168  }
169  else
170  {
171  layout_union = layout_one_rect;
172  }
173 
174  if (cell_area)
175  {
176  gfloat xalign, yalign;
177 
178  gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
179 
180  layout_union.width = MIN (layout_union.width, cell_area->width - 2 * xpad);
181  layout_union.height = MIN (layout_union.height, cell_area->height - 2 * ypad);
182 
183  if (x_offset_1)
184  {
185  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
186  *x_offset_1 = (1.0 - xalign) * (cell_area->width - (layout_one_rect.width + (2 * xpad)));
187  else
188  *x_offset_1 = xalign * (cell_area->width - (layout_one_rect.width + (2 * xpad)));
189 
190  *x_offset_1 = MAX (*x_offset_1, 0);
191  }
192  if (x_offset_2)
193  {
194  if (layout_two != NULL)
195  {
196  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
197  *x_offset_2 = (1.0 - xalign) * (cell_area->width - (layout_two_rect.width + (2 * xpad)));
198  else
199  *x_offset_2 = xalign * (cell_area->width - (layout_two_rect.width + (2 * xpad)));
200 
201  *x_offset_2 = MAX (*x_offset_2, 0);
202  }
203  else
204  {
205  *x_offset_2 = 0;
206  }
207  }
208 
209  if (y_offset)
210  {
211  *y_offset = yalign * (cell_area->height - (layout_union.height + (2 * ypad)));
212  *y_offset = MAX (*y_offset, 0);
213  }
214  }
215  else
216  {
217  if (x_offset_1) *x_offset_1 = 0;
218  if (x_offset_2) *x_offset_2 = 0;
219  if (y_offset) *y_offset = 0;
220  }
221 
222  g_clear_object (&layout_one);
223  g_clear_object (&layout_two);
224 
225  if (height)
226  *height = ypad * 2 + layout_union.height;
227 
228  if (width)
229  *width = xpad * 2 + layout_union.width;
230 }
231 
232 static void
233 gd_two_lines_renderer_render (GtkCellRenderer *cell,
234  cairo_t *cr,
235  GtkWidget *widget,
236  const GdkRectangle *background_area,
237  const GdkRectangle *cell_area,
238  GtkCellRendererState flags)
239 {
241  GtkStyleContext *context;
242  gint line_one_height;
243  GtkStateFlags state;
244  GdkRectangle area, render_area = *cell_area;
245  gint xpad, ypad, x_offset_1, x_offset_2, y_offset;
246  PangoLayout *layout_one, *layout_two;
247  PangoRectangle layout_rect;
248 
249  /* fetch common information */
250  context = gtk_widget_get_style_context (widget);
251  gd_two_lines_renderer_prepare_layouts (self, cell_area, widget, &layout_one, &layout_two);
252  gd_two_lines_renderer_get_size (cell, widget,
253  layout_one, layout_two,
254  NULL, NULL,
255  cell_area,
256  &x_offset_1, &x_offset_2, &y_offset);
257  gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
258 
259  area = *cell_area;
260  area.x += xpad;
261  area.y += ypad;
262 
263  /* now render the first layout */
264  pango_layout_get_pixel_extents (layout_one, NULL, &layout_rect);
265 
266  render_area = area;
267  render_area.x += x_offset_1 - layout_rect.x;
268 
269  gtk_render_layout (context, cr,
270  render_area.x,
271  render_area.y,
272  layout_one);
273 
274  /* render the second layout */
275  if (layout_two != NULL)
276  {
277  pango_layout_get_pixel_size (layout_one,
278  NULL, &line_one_height);
279 
280  gtk_style_context_save (context);
281  gtk_style_context_add_class (context, "dim-label");
282 
283  state = gtk_cell_renderer_get_state (cell, widget, flags);
284  gtk_style_context_set_state (context, state);
285 
286  pango_layout_get_pixel_extents (layout_two, NULL, &layout_rect);
287 
288  render_area = area;
289  render_area.x += x_offset_2 - layout_rect.x;
290  render_area.y += line_one_height;
291 
292  gtk_render_layout (context, cr,
293  render_area.x,
294  render_area.y,
295  layout_two);
296 
297  gtk_style_context_restore (context);
298  }
299 
300  g_clear_object (&layout_one);
301  g_clear_object (&layout_two);
302 }
303 
304 static void
306  GtkWidget *widget,
307  gint *minimum_size,
308  gint *natural_size)
309 {
310  PangoContext *context;
311  PangoFontMetrics *metrics;
312  PangoFontDescription *font_desc;
313  GtkStyleContext *style_context;
314  gint nat_width, min_width;
315  gint xpad, char_width, wrap_width, text_width;
316  gint width_chars, ellipsize_chars;
317 
318  g_object_get (cell,
319  "xpad", &xpad,
320  "width-chars", &width_chars,
321  "wrap-width", &wrap_width,
322  NULL);
323  style_context = gtk_widget_get_style_context (widget);
324  gtk_cell_renderer_get_padding (cell, &xpad, NULL);
325 
326  gd_two_lines_renderer_get_size (cell, widget,
327  NULL, NULL,
328  &text_width, NULL,
329  NULL,
330  NULL, NULL, NULL);
331 
332  /* Fetch the average size of a charachter */
333  context = gtk_widget_get_pango_context (widget);
334  gtk_style_context_save (style_context);
335  gtk_style_context_set_state (style_context, 0);
336  gtk_style_context_get (style_context, 0, "font", &font_desc, NULL);
337  gtk_style_context_restore (style_context);
338  metrics = pango_context_get_metrics (context, font_desc,
339  pango_context_get_language (context));
340 
341  char_width = pango_font_metrics_get_approximate_char_width (metrics);
342 
343  pango_font_metrics_unref (metrics);
344  pango_font_description_free (font_desc);
345 
346  /* enforce minimum width for ellipsized labels at ~3 chars */
347  ellipsize_chars = 3;
348 
349  /* If no width-chars set, minimum for wrapping text will be the wrap-width */
350  if (wrap_width > -1)
351  min_width = xpad * 2 + MIN (text_width, wrap_width);
352  else
353  min_width = xpad * 2 +
354  MIN (text_width,
355  (PANGO_PIXELS (char_width) * MAX (width_chars, ellipsize_chars)));
356 
357  if (width_chars > 0)
358  nat_width = xpad * 2 +
359  MAX ((PANGO_PIXELS (char_width) * width_chars), text_width);
360  else
361  nat_width = xpad * 2 + text_width;
362 
363  nat_width = MAX (nat_width, min_width);
364 
365  if (minimum_size)
366  *minimum_size = min_width;
367 
368  if (natural_size)
369  *natural_size = nat_width;
370 }
371 
372 static void
374  GtkWidget *widget,
375  gint width,
376  gint *minimum_size,
377  gint *natural_size)
378 {
380  PangoLayout *layout_one, *layout_two;
381  gint text_height, wrap_width;
382  gint xpad, ypad;
383 
384  gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
385  g_object_get (cell, "wrap-width", &wrap_width, NULL);
386  gd_two_lines_renderer_prepare_layouts (self, NULL, widget, &layout_one, &layout_two);
387 
388  if (wrap_width != -1)
389  wrap_width = MIN (width - 2 * xpad, wrap_width);
390  else
391  wrap_width = width - 2 * xpad;
392 
393  pango_layout_set_width (layout_one, wrap_width);
394  if (layout_two != NULL)
395  pango_layout_set_width (layout_two, wrap_width);
396 
397  gd_two_lines_renderer_get_size (cell, widget,
398  layout_one, layout_two,
399  NULL, &text_height,
400  NULL,
401  NULL, NULL, NULL);
402 
403  text_height += 2 * ypad;
404 
405  if (minimum_size != NULL)
406  *minimum_size = text_height;
407 
408  if (natural_size != NULL)
409  *natural_size = text_height;
410 
411  g_clear_object (&layout_one);
412  g_clear_object (&layout_two);
413 }
414 
415 static void
417  GtkWidget *widget,
418  gint *minimum_size,
419  gint *natural_size)
420 {
421  gint min_width;
422 
423  gtk_cell_renderer_get_preferred_width (cell, widget, &min_width, NULL);
425  minimum_size, natural_size);
426 }
427 
428 static void
430  GtkWidget *widget,
431  GtkCellRendererState flags,
432  const GdkRectangle *cell_area,
433  GdkRectangle *aligned_area)
434 {
436  gint x_offset, x_offset_1, x_offset_2, y_offset;
437  PangoLayout *layout_one, *layout_two;
438 
439  /* fetch common information */
440  gd_two_lines_renderer_prepare_layouts (self, cell_area, widget, &layout_one, &layout_two);
441  gd_two_lines_renderer_get_size (cell, widget,
442  layout_one, layout_two,
443  &aligned_area->width, &aligned_area->height,
444  cell_area,
445  &x_offset_1, &x_offset_2, &y_offset);
446 
447  x_offset = MIN (x_offset_1, x_offset_2);
448 
449  aligned_area->x = cell_area->x + x_offset;
450  aligned_area->y = cell_area->y;
451 
452  g_clear_object (&layout_one);
453  g_clear_object (&layout_two);
454 }
455 
456 static void
458  const gchar *line_two)
459 {
460  if (g_strcmp0 (self->priv->line_two, line_two) == 0)
461  return;
462 
463  g_free (self->priv->line_two);
464  self->priv->line_two = g_strdup (line_two);
465 
466  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LINE_TWO]);
467 }
468 
469 static void
471  gint text_lines)
472 {
473  if (self->priv->text_lines == text_lines)
474  return;
475 
476  self->priv->text_lines = text_lines;
477  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TEXT_LINES]);
478 }
479 
480 static void
482  guint property_id,
483  const GValue *value,
484  GParamSpec *pspec)
485 {
486  GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (object);
487 
488  switch (property_id)
489  {
490  case PROP_TEXT_LINES:
491  gd_two_lines_renderer_set_text_lines (self, g_value_get_int (value));
492  break;
493  case PROP_LINE_TWO:
494  gd_two_lines_renderer_set_line_two (self, g_value_get_string (value));
495  break;
496  default:
497  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
498  break;
499  }
500 }
501 
502 static void
504  guint property_id,
505  GValue *value,
506  GParamSpec *pspec)
507 {
508  GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (object);
509 
510  switch (property_id)
511  {
512  case PROP_TEXT_LINES:
513  g_value_set_int (value, self->priv->text_lines);
514  break;
515  case PROP_LINE_TWO:
516  g_value_set_string (value, self->priv->line_two);
517  break;
518  default:
519  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
520  break;
521  }
522 }
523 
524 static void
526 {
527  GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (object);
528 
529  g_free (self->priv->line_two);
530 
531  G_OBJECT_CLASS (gd_two_lines_renderer_parent_class)->finalize (object);
532 }
533 
534 static void
536 {
537  GtkCellRendererClass *cclass = GTK_CELL_RENDERER_CLASS (klass);
538  GObjectClass *oclass = G_OBJECT_CLASS (klass);
539 
540  cclass->render = gd_two_lines_renderer_render;
541  cclass->get_preferred_width = gd_two_lines_renderer_get_preferred_width;
542  cclass->get_preferred_height = gd_two_lines_renderer_get_preferred_height;
543  cclass->get_preferred_height_for_width = gd_two_lines_renderer_get_preferred_height_for_width;
544  cclass->get_aligned_area = gd_two_lines_renderer_get_aligned_area;
545 
546  oclass->set_property = gd_two_lines_renderer_set_property;
547  oclass->get_property = gd_two_lines_renderer_get_property;
548  oclass->finalize = gd_two_lines_renderer_finalize;
549 
551  g_param_spec_int ("text-lines",
552  "Lines of text",
553  "The total number of lines to be displayed",
554  2, G_MAXINT, 2,
555  G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
556 
558  g_param_spec_string ("line-two",
559  "Second line",
560  "Second line",
561  NULL,
562  G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
563 
564  g_type_class_add_private (klass, sizeof (GdTwoLinesRendererPrivate));
565  g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
566 }
567 
568 static void
570 {
571  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_TWO_LINES_RENDERER,
573 }
574 
575 GtkCellRenderer *
577 {
578  return g_object_new (GD_TYPE_TWO_LINES_RENDERER, NULL);
579 }