1 /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 #include "hippo-canvas-type-builtins.h"
3 #include "hippo-canvas-internal.h"
4 #include "hippo-canvas-style.h"
5 #include "hippo-canvas-text.h"
6 #include "hippo-canvas-box.h"
8 #include <pango/pangocairo.h>
12 static void hippo_canvas_text_init (HippoCanvasText
*text
);
13 static void hippo_canvas_text_class_init (HippoCanvasTextClass
*klass
);
14 static void hippo_canvas_text_iface_init (HippoCanvasItemIface
*item_class
);
15 static void hippo_canvas_text_finalize (GObject
*object
);
17 static void hippo_canvas_text_set_property (GObject
*object
,
21 static void hippo_canvas_text_get_property (GObject
*object
,
27 /* Canvas item methods */
28 static gboolean
hippo_canvas_text_button_press_event (HippoCanvasItem
*item
,
30 static void hippo_canvas_text_set_context (HippoCanvasItem
*item
,
31 HippoCanvasContext
*context
);
32 static char* hippo_canvas_text_get_tooltip (HippoCanvasItem
*item
,
35 HippoRectangle
*for_area
);
38 static void hippo_canvas_text_paint_below_children (HippoCanvasBox
*box
,
40 GdkRegion
*damaged_region
);
41 static void hippo_canvas_text_get_content_width_request (HippoCanvasBox
*box
,
43 int *natural_width_p
);
44 static void hippo_canvas_text_get_content_height_request (HippoCanvasBox
*box
,
47 int *natural_height_p
);
54 /* static int signals[LAST_SIGNAL]; */
65 G_DEFINE_TYPE_WITH_CODE(HippoCanvasText
, hippo_canvas_text
, HIPPO_TYPE_CANVAS_BOX
,
66 G_IMPLEMENT_INTERFACE(HIPPO_TYPE_CANVAS_ITEM
, hippo_canvas_text_iface_init
));
69 hippo_canvas_text_init(HippoCanvasText
*text
)
71 text
->font_scale
= 1.0;
72 text
->size_mode
= HIPPO_CANVAS_SIZE_FULL_WIDTH
;
75 static HippoCanvasItemIface
*item_parent_class
;
78 hippo_canvas_text_iface_init(HippoCanvasItemIface
*item_class
)
80 item_parent_class
= g_type_interface_peek_parent(item_class
);
82 item_class
->button_press_event
= hippo_canvas_text_button_press_event
;
84 item_class
->set_context
= hippo_canvas_text_set_context
;
85 item_class
->get_tooltip
= hippo_canvas_text_get_tooltip
;
89 hippo_canvas_text_class_init(HippoCanvasTextClass
*klass
)
91 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
92 HippoCanvasBoxClass
*box_class
= HIPPO_CANVAS_BOX_CLASS(klass
);
94 object_class
->set_property
= hippo_canvas_text_set_property
;
95 object_class
->get_property
= hippo_canvas_text_get_property
;
97 object_class
->finalize
= hippo_canvas_text_finalize
;
99 box_class
->paint_below_children
= hippo_canvas_text_paint_below_children
;
100 box_class
->get_content_width_request
= hippo_canvas_text_get_content_width_request
;
101 box_class
->get_content_height_request
= hippo_canvas_text_get_content_height_request
;
104 * HippoCanvasText:text
106 * The text to display.
108 g_object_class_install_property(object_class
,
110 g_param_spec_string("text",
112 _("Text to display"),
114 G_PARAM_READABLE
| G_PARAM_WRITABLE
));
117 * HippoCanvasText:markup
119 * The text to display in Pango markup format, allowing you to do bold and other text styles.
120 * This is a shortcut way to set the 'text' and 'attributes' properties.
122 g_object_class_install_property(object_class
,
124 g_param_spec_string("markup",
126 _("Marked-up text to display"),
132 * HippoCanvasText:attributes
134 * The Pango attributes of the text; usually it is more convenient to use the 'markup' property
135 * than to do the attributes by hand.
137 g_object_class_install_property(object_class
,
139 g_param_spec_boxed ("attributes",
141 _("A list of style attributes to apply to the text"),
142 PANGO_TYPE_ATTR_LIST
,
143 G_PARAM_READABLE
| G_PARAM_WRITABLE
));
146 * HippoCanvasText:font-scale
148 * A scale factor for the font; Pango exports constants for common
149 * factors, such as #PANGO_SCALE_LARGE, #PANGO_SCALE_X_SMALL and
150 * so forth. If you want to set an absolute font size, use the
151 * 'font' or 'font-desc' properties which are introduced in the
152 * HippoCanvasBox base class.
154 g_object_class_install_property(object_class
,
156 g_param_spec_double("font-scale",
158 _("Scale factor for fonts"),
162 G_PARAM_READABLE
| G_PARAM_WRITABLE
));
165 * HippoCanvasText:size-mode
167 * Three "size modes" are available. In #HIPPO_CANVAS_SIZE_FULL_WIDTH mode, the
168 * text item will give the width of its text as its minimum size. In #HIPPO_CANVAS_SIZE_WRAP_WORD,
169 * the text item will wrap to fit available space - its minimum width will be pretty small, but as
170 * the width decreases its height will increase. In #HIPPO_CANVAS_SIZE_ELLIPSIZE_END mode, the
171 * width of the text will be the natural width of the item, but the minimum width will be small.
172 * If the item gets less than its natural width, it will ellipsize the text in order to fit
173 * in available space.
175 * If none of that made sense, just decide whether you want to
176 * always display all the text without wrapping, want to be able
177 * to wrap as the available space shrinks, or want to ellipsize
178 * the text as the available space shrinks.
180 g_object_class_install_property(object_class
,
182 g_param_spec_enum("size-mode",
184 _("Mode for size request and allocation"),
185 HIPPO_TYPE_CANVAS_SIZE_MODE
,
186 HIPPO_CANVAS_SIZE_FULL_WIDTH
,
187 G_PARAM_READABLE
| G_PARAM_WRITABLE
));
191 hippo_canvas_text_finalize(GObject
*object
)
193 HippoCanvasText
*text
= HIPPO_CANVAS_TEXT(object
);
198 if (text
->attributes
) {
199 pango_attr_list_unref(text
->attributes
);
200 text
->attributes
= NULL
;
203 G_OBJECT_CLASS(hippo_canvas_text_parent_class
)->finalize(object
);
207 hippo_canvas_text_new(void)
209 HippoCanvasText
*text
= g_object_new(HIPPO_TYPE_CANVAS_TEXT
, NULL
);
213 return HIPPO_CANVAS_ITEM(text
);
218 hippo_canvas_text_set_property(GObject
*object
,
223 HippoCanvasText
*text
;
225 text
= HIPPO_CANVAS_TEXT(object
);
230 const char *new_text
;
231 new_text
= g_value_get_string(value
);
232 if (!(new_text
== text
->text
||
233 (new_text
&& text
->text
&& strcmp(new_text
, text
->text
) == 0))) {
235 text
->text
= g_strdup(new_text
);
236 hippo_canvas_item_emit_request_changed(HIPPO_CANVAS_ITEM(text
));
237 hippo_canvas_item_emit_paint_needed(HIPPO_CANVAS_ITEM(text
), 0, 0, -1, -1);
241 case PROP_ATTRIBUTES
:
243 PangoAttrList
*attrs
= g_value_get_boxed(value
);
245 pango_attr_list_ref(attrs
);
246 if (text
->attributes
)
247 pango_attr_list_unref(text
->attributes
);
248 text
->attributes
= attrs
;
249 hippo_canvas_item_emit_request_changed(HIPPO_CANVAS_ITEM(text
));
250 hippo_canvas_item_emit_paint_needed(HIPPO_CANVAS_ITEM(text
), 0, 0, -1, -1);
256 PangoAttrList
*attrs
;
257 GError
*error
= NULL
;
259 if (!pango_parse_markup(g_value_get_string(value
),
266 g_error("Failed to set markup: %s", error
->message
);
269 g_object_set(object
, "text", text
, "attributes", attrs
, NULL
);
270 pango_attr_list_unref(attrs
);
274 case PROP_FONT_SCALE
:
275 text
->font_scale
= g_value_get_double(value
);
276 hippo_canvas_item_emit_request_changed(HIPPO_CANVAS_ITEM(text
));
277 hippo_canvas_item_emit_paint_needed(HIPPO_CANVAS_ITEM(text
), 0, 0, -1, -1);
280 text
->size_mode
= g_value_get_enum(value
);
281 hippo_canvas_item_emit_request_changed(HIPPO_CANVAS_ITEM(text
));
282 hippo_canvas_item_emit_paint_needed(HIPPO_CANVAS_ITEM(text
), 0, 0, -1, -1);
285 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
291 hippo_canvas_text_get_property(GObject
*object
,
296 HippoCanvasText
*text
;
298 text
= HIPPO_CANVAS_TEXT (object
);
302 g_value_set_string(value
, text
->text
);
304 case PROP_ATTRIBUTES
:
305 g_value_set_boxed(value
, text
->attributes
);
307 case PROP_FONT_SCALE
:
308 g_value_set_double(value
, text
->font_scale
);
311 g_value_set_enum(value
, text
->size_mode
);
314 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
320 hippo_canvas_text_set_context(HippoCanvasItem
*item
,
321 HippoCanvasContext
*context
)
323 HippoCanvasBox
*box
= HIPPO_CANVAS_BOX(item
);
326 changed
= context
!= box
->context
;
328 item_parent_class
->set_context(item
, context
);
330 /* we can't create a layout until we have a context,
331 * so we have to queue a size change when the context
335 hippo_canvas_item_emit_request_changed(HIPPO_CANVAS_ITEM(item
));
336 hippo_canvas_item_emit_paint_needed(HIPPO_CANVAS_ITEM(item
), 0, 0, -1, -1);
342 hippo_canvas_text_get_tooltip (HippoCanvasItem
*item
,
345 HippoRectangle
*for_area
)
347 HippoCanvasText
*text
= HIPPO_CANVAS_TEXT(item
);
348 HippoCanvasBox
*box
= HIPPO_CANVAS_BOX(item
);
351 if (text
->is_ellipsized
&& text
->text
) {
354 for_area
->width
= box
->allocated_width
;
355 for_area
->height
= box
->allocated_height
;
357 return g_strdup(text
->text
);
359 return item_parent_class
->get_tooltip(item
, x
, y
, for_area
);
363 remove_newlines(const char *text
)
370 for (p
= s
; *p
!= '\0'; ++p
) {
371 if (*p
== '\n' || *p
== '\r')
379 create_layout(HippoCanvasText
*text
,
380 int allocation_width
)
382 HippoCanvasBox
*box
= HIPPO_CANVAS_BOX(text
);
384 HippoCanvasStyle
*style
= hippo_canvas_context_get_style(HIPPO_CANVAS_CONTEXT(text
));
386 g_return_val_if_fail(box
->context
!= NULL
, NULL
);
388 layout
= hippo_canvas_context_create_layout(box
->context
);
390 if (box
->font_desc
) {
391 PangoFontDescription
*merged
= pango_font_description_copy(hippo_canvas_style_get_font(style
));
392 pango_font_description_merge(merged
, box
->font_desc
, TRUE
);
393 pango_layout_set_font_description(layout
, merged
);
394 pango_font_description_free(merged
);
396 pango_layout_set_font_description(layout
, hippo_canvas_style_get_font(style
));
400 PangoAttrList
*attrs
;
401 HippoTextDecoration decoration
= hippo_canvas_style_get_text_decoration(style
);
403 if (text
->attributes
)
404 attrs
= pango_attr_list_copy(text
->attributes
);
406 attrs
= pango_attr_list_new();
409 if (ABS(1.0 - text
->font_scale
) > .000001) {
410 PangoAttribute
*attr
= pango_attr_scale_new(text
->font_scale
);
411 attr
->start_index
= 0;
412 attr
->end_index
= G_MAXUINT
;
413 pango_attr_list_insert(attrs
, attr
);
416 if ((decoration
& HIPPO_TEXT_DECORATION_UNDERLINE
) != 0) {
417 PangoAttribute
*attr
= pango_attr_underline_new(TRUE
);
418 attr
->start_index
= 0;
419 attr
->end_index
= G_MAXUINT
;
420 pango_attr_list_insert(attrs
, attr
);
423 if ((decoration
& HIPPO_TEXT_DECORATION_LINE_THROUGH
) != 0) {
424 PangoAttribute
*attr
= pango_attr_strikethrough_new(TRUE
);
425 attr
->start_index
= 0;
426 attr
->end_index
= G_MAXUINT
;
427 pango_attr_list_insert(attrs
, attr
);
430 pango_layout_set_attributes(layout
, attrs
);
431 pango_attr_list_unref(attrs
);
434 if (text
->text
!= NULL
) {
435 pango_layout_set_text(layout
, text
->text
, -1);
438 if (allocation_width
>= 0) {
439 int layout_width
, layout_height
;
440 pango_layout_get_size(layout
, &layout_width
, &layout_height
);
441 layout_width
/= PANGO_SCALE
;
442 layout_height
/= PANGO_SCALE
;
444 /* Force layout smaller if required, but we don't want to make
445 * the layout _wider_ because it breaks alignment, so only do
448 if (layout_width
> allocation_width
) {
449 pango_layout_set_width(layout
, allocation_width
* PANGO_SCALE
);
451 /* If we set ellipsize, then it overrides wrapping. If we get
452 * too-small allocation for HIPPO_CANVAS_SIZE_FULL_WIDTH, then
453 * we want to ellipsize instead of wrapping.
455 if (text
->size_mode
== HIPPO_CANVAS_SIZE_WRAP_WORD
) {
456 pango_layout_set_ellipsize(layout
, PANGO_ELLIPSIZE_NONE
);
458 pango_layout_set_ellipsize(layout
, PANGO_ELLIPSIZE_END
);
461 /* For now if we say ellipsize end, we always just want one line.
462 * Maybe this should be an orthogonal property?
464 if (text
->size_mode
== HIPPO_CANVAS_SIZE_ELLIPSIZE_END
) {
465 pango_layout_set_single_paragraph_mode(layout
, TRUE
);
467 /* Pango's line separator character in this case is ugly, so we
468 * fix it. Not a very efficient approach, but oh well.
470 if (text
->text
!= NULL
) {
471 char *new_text
= remove_newlines(text
->text
);
472 /* avoid making the layout recompute everything
473 * if we didn't have newlines anyhow
475 if (strcmp(text
->text
, new_text
) != 0) {
476 pango_layout_set_text(layout
, new_text
, -1);
488 layout_is_ellipsized(PangoLayout
*layout
)
490 /* pango_layout_is_ellipsized() is a new function in Pango-1.16; we
491 * emulate it here by trying to look for the ellipsis run
493 PangoLogAttr
*log_attrs
;
495 PangoLayoutIter
*iter
;
496 gboolean result
= FALSE
;
498 /* Short circuit when we aren't ellipsizing at all */
499 if (pango_layout_get_ellipsize(layout
) == PANGO_ELLIPSIZE_NONE
)
502 pango_layout_get_log_attrs(layout
, &log_attrs
, &n_attrs
);
504 iter
= pango_layout_get_iter(layout
);
512 run
= pango_layout_iter_get_run(iter
);
516 n_glyphs
= run
->glyphs
->num_glyphs
;
517 start_index
= pango_layout_iter_get_index(iter
);
519 /* Check the number of clusters in the run ... if it is greater
520 * than 1, then it isn't an ellipsis
522 if (run
->glyphs
->log_clusters
[0] != run
->glyphs
->log_clusters
[n_glyphs
- 1])
525 /* Now check the number of graphemes in the run ... if it is less
526 * than 3, it's probably an isolated 'fi' ligature or something
527 * like that rather than an ellipsis.
530 for (i
= 0; i
< run
->item
->num_chars
&& i
+ start_index
< n_attrs
; i
++)
531 if (log_attrs
[i
+ start_index
].is_cursor_position
)
537 /* OK, at this point it is probably an ellipsis; it's possible that
538 * the text consists of just the letters 'ffi' and the font has a ligature
539 * for that or something, but it's not too likely.
544 } while (pango_layout_iter_next_run(iter
));
546 pango_layout_iter_free(iter
);
554 hippo_canvas_text_paint_below_children(HippoCanvasBox
*box
,
556 GdkRegion
*damaged_region
)
558 HippoCanvasText
*text
= HIPPO_CANVAS_TEXT(box
);
561 if (box
->color_set
) {
562 color_rgba
= box
->color_rgba
;
564 HippoCanvasStyle
*style
= hippo_canvas_context_get_style(HIPPO_CANVAS_CONTEXT(text
));
565 color_rgba
= hippo_canvas_style_get_foreground_color(style
);
568 /* It would seem more natural to compute whether we are ellipsized or
569 * not when we are allocated to some width, but we don't have a layout
570 * at that point. We could do it in get_content_height_request(), but
571 * the parent could theoretically give us more width than it asked us
572 * about (and there are also some quirks in HippoCanvasBox where it
573 * will call get_content_height_request() with a width if 0 at odd times),
574 * so doing it here is more reliable. We use is_ellipsized only for
575 * computing whether to show a tooltip, and we make the assumption that
576 * if the user hasn't seen the text item drawn, they won't need a
579 text
->is_ellipsized
= FALSE
;
581 if ((color_rgba
& 0xff) != 0 && text
->text
!= NULL
) {
583 int layout_width
, layout_height
;
585 int allocation_width
, allocation_height
;
587 int space_left
= box
->border_left
+ box
->padding_left
;
588 int space_right
= box
->border_right
+ box
->padding_right
;
591 hippo_canvas_item_get_allocation(HIPPO_CANVAS_ITEM(box
),
592 &allocation_width
, &allocation_height
);
594 layout
= create_layout(text
, allocation_width
- space_left
- space_right
);
595 pango_layout_get_size(layout
, &layout_width
, &layout_height
);
596 layout_width
/= PANGO_SCALE
;
597 layout_height
/= PANGO_SCALE
;
599 text
->is_ellipsized
= layout_is_ellipsized(layout
);
601 hippo_canvas_box_align(box
,
602 layout_width
, layout_height
,
605 /* we can't really "fill" so we fall back to center if we seem to be
608 if (w
> layout_width
) {
609 x
+= (w
- layout_width
) / 2;
611 if (h
> layout_height
) {
612 y
+= (h
- layout_height
) / 2;
615 /* Clipping is needed since the layout size could exceed our
616 * allocation if we got a too-small allocation.
617 * FIXME It would be better to ellipsize or something instead, though.
620 cairo_rectangle(cr
, 0, 0, allocation_width
, allocation_height
);
623 cairo_move_to (cr
, x
, y
);
624 hippo_cairo_set_source_rgba32(cr
, color_rgba
);
625 pango_cairo_show_layout(cr
, layout
);
628 g_object_unref(layout
);
633 hippo_canvas_text_get_content_width_request(HippoCanvasBox
*box
,
635 int *natural_width_p
)
637 HippoCanvasText
*text
= HIPPO_CANVAS_TEXT(box
);
638 int children_min_width
, children_natural_width
;
641 HIPPO_CANVAS_BOX_CLASS(hippo_canvas_text_parent_class
)->get_content_width_request(box
,
643 &children_natural_width
);
644 if (box
->context
!= NULL
) {
645 PangoLayout
*layout
= create_layout(text
, -1);
646 pango_layout_get_size(layout
, &layout_width
, NULL
);
647 layout_width
/= PANGO_SCALE
;
648 g_object_unref(layout
);
654 if (text
->size_mode
== HIPPO_CANVAS_SIZE_FULL_WIDTH
)
655 *min_width_p
= MAX(children_min_width
, layout_width
);
657 *min_width_p
= children_min_width
;
660 if (natural_width_p
) {
661 *natural_width_p
= MAX(children_natural_width
, layout_width
);
666 hippo_canvas_text_get_content_height_request(HippoCanvasBox
*box
,
669 int *natural_height_p
)
671 HippoCanvasText
*text
= HIPPO_CANVAS_TEXT(box
);
672 int children_min_height
, children_natural_height
;
676 HIPPO_CANVAS_BOX_CLASS(hippo_canvas_text_parent_class
)->get_content_height_request(box
,
678 &children_min_height
,
679 &children_natural_height
);
682 if (box
->context
!= NULL
) {
683 layout
= create_layout(text
, for_width
);
684 pango_layout_get_size(layout
, NULL
, &layout_height
);
686 layout_height
/= PANGO_SCALE
;
687 g_object_unref(layout
);
696 *min_height_p
= MAX(layout_height
, children_min_height
);
697 if (natural_height_p
)
698 *natural_height_p
= MAX(layout_height
, children_natural_height
);
702 hippo_canvas_text_button_press_event (HippoCanvasItem
*item
,
705 /* HippoCanvasText *text = HIPPO_CANVAS_TEXT(item); */
707 /* see if a child wants it */
708 return item_parent_class
->button_press_event(item
, event
);