[AdgEdges] Added guard against invalid "source" properties
[adg.git] / nodist / AdgText / hippo-canvas-text.c
blob5c47a09f3bca2ce6f6719e90016e54c955854795
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"
7 #include <gdk/gdk.h>
8 #include <pango/pangocairo.h>
9 #include <stdlib.h>
10 #include <string.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,
18 guint prop_id,
19 const GValue *value,
20 GParamSpec *pspec);
21 static void hippo_canvas_text_get_property (GObject *object,
22 guint prop_id,
23 GValue *value,
24 GParamSpec *pspec);
27 /* Canvas item methods */
28 static gboolean hippo_canvas_text_button_press_event (HippoCanvasItem *item,
29 HippoEvent *event);
30 static void hippo_canvas_text_set_context (HippoCanvasItem *item,
31 HippoCanvasContext *context);
32 static char* hippo_canvas_text_get_tooltip (HippoCanvasItem *item,
33 int x,
34 int y,
35 HippoRectangle *for_area);
37 /* Box methods */
38 static void hippo_canvas_text_paint_below_children (HippoCanvasBox *box,
39 cairo_t *cr,
40 GdkRegion *damaged_region);
41 static void hippo_canvas_text_get_content_width_request (HippoCanvasBox *box,
42 int *min_width_p,
43 int *natural_width_p);
44 static void hippo_canvas_text_get_content_height_request (HippoCanvasBox *box,
45 int for_width,
46 int *min_height_p,
47 int *natural_height_p);
49 enum {
50 NO_SIGNALS_YET,
51 LAST_SIGNAL
54 /* static int signals[LAST_SIGNAL]; */
56 enum {
57 PROP_0,
58 PROP_TEXT,
59 PROP_MARKUP,
60 PROP_ATTRIBUTES,
61 PROP_FONT_SCALE,
62 PROP_SIZE_MODE
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));
68 static void
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;
77 static void
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;
88 static void
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,
109 PROP_TEXT,
110 g_param_spec_string("text",
111 _("Text"),
112 _("Text to display"),
113 NULL,
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,
123 PROP_MARKUP,
124 g_param_spec_string("markup",
125 _("Markup"),
126 _("Marked-up text to display"),
127 NULL,
128 G_PARAM_WRITABLE));
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,
138 PROP_ATTRIBUTES,
139 g_param_spec_boxed ("attributes",
140 _("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,
155 PROP_FONT_SCALE,
156 g_param_spec_double("font-scale",
157 _("Font scale"),
158 _("Scale factor for fonts"),
159 0.0,
160 100.0,
161 1.0,
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,
181 PROP_SIZE_MODE,
182 g_param_spec_enum("size-mode",
183 _("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));
190 static void
191 hippo_canvas_text_finalize(GObject *object)
193 HippoCanvasText *text = HIPPO_CANVAS_TEXT(object);
195 g_free(text->text);
196 text->text = NULL;
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);
206 HippoCanvasItem*
207 hippo_canvas_text_new(void)
209 HippoCanvasText *text = g_object_new(HIPPO_TYPE_CANVAS_TEXT, NULL);
213 return HIPPO_CANVAS_ITEM(text);
217 static void
218 hippo_canvas_text_set_property(GObject *object,
219 guint prop_id,
220 const GValue *value,
221 GParamSpec *pspec)
223 HippoCanvasText *text;
225 text = HIPPO_CANVAS_TEXT(object);
227 switch (prop_id) {
228 case PROP_TEXT:
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))) {
234 g_free(text->text);
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);
240 break;
241 case PROP_ATTRIBUTES:
243 PangoAttrList *attrs = g_value_get_boxed(value);
244 if (attrs)
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);
252 break;
253 case PROP_MARKUP:
255 char *text;
256 PangoAttrList *attrs;
257 GError *error = NULL;
259 if (!pango_parse_markup(g_value_get_string(value),
262 &attrs,
263 &text,
264 NULL,
265 &error)) {
266 g_error("Failed to set markup: %s", error->message);
267 return;
269 g_object_set(object, "text", text, "attributes", attrs, NULL);
270 pango_attr_list_unref(attrs);
271 g_free(text);
273 break;
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);
278 break;
279 case PROP_SIZE_MODE:
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);
283 break;
284 default:
285 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
286 break;
290 static void
291 hippo_canvas_text_get_property(GObject *object,
292 guint prop_id,
293 GValue *value,
294 GParamSpec *pspec)
296 HippoCanvasText *text;
298 text = HIPPO_CANVAS_TEXT (object);
300 switch (prop_id) {
301 case PROP_TEXT:
302 g_value_set_string(value, text->text);
303 break;
304 case PROP_ATTRIBUTES:
305 g_value_set_boxed(value, text->attributes);
306 break;
307 case PROP_FONT_SCALE:
308 g_value_set_double(value, text->font_scale);
309 break;
310 case PROP_SIZE_MODE:
311 g_value_set_enum(value, text->size_mode);
312 break;
313 default:
314 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
315 break;
319 static void
320 hippo_canvas_text_set_context(HippoCanvasItem *item,
321 HippoCanvasContext *context)
323 HippoCanvasBox *box = HIPPO_CANVAS_BOX(item);
324 gboolean changed;
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
332 * is set.
334 if (changed) {
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);
341 static char *
342 hippo_canvas_text_get_tooltip (HippoCanvasItem *item,
343 int x,
344 int y,
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) {
352 for_area->x = 0;
353 for_area->y = 0;
354 for_area->width = box->allocated_width;
355 for_area->height = box->allocated_height;
357 return g_strdup(text->text);
358 } else
359 return item_parent_class->get_tooltip(item, x, y, for_area);
362 static char*
363 remove_newlines(const char *text)
365 char *s;
366 char *p;
368 s = g_strdup(text);
370 for (p = s; *p != '\0'; ++p) {
371 if (*p == '\n' || *p == '\r')
372 *p = ' ';
375 return s;
378 static PangoLayout*
379 create_layout(HippoCanvasText *text,
380 int allocation_width)
382 HippoCanvasBox *box = HIPPO_CANVAS_BOX(text);
383 PangoLayout *layout;
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);
395 } else {
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);
405 else
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
446 * this if required.
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);
457 } else {
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);
478 g_free(new_text);
484 return layout;
487 static gboolean
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;
494 int n_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)
500 return FALSE;
502 pango_layout_get_log_attrs(layout, &log_attrs, &n_attrs);
504 iter = pango_layout_get_iter(layout);
505 do {
506 PangoGlyphItem *run;
507 int n_glyphs;
508 int start_index;
509 int n_graphemes;
510 int i;
512 run = pango_layout_iter_get_run(iter);
513 if (!run)
514 continue;
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])
523 continue;
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.
529 n_graphemes = 0;
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)
532 n_graphemes++;
534 if (n_graphemes < 3)
535 continue;
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.
541 result = TRUE;
542 break;
544 } while (pango_layout_iter_next_run(iter));
546 pango_layout_iter_free(iter);
548 g_free(log_attrs);
550 return result;
553 static void
554 hippo_canvas_text_paint_below_children(HippoCanvasBox *box,
555 cairo_t *cr,
556 GdkRegion *damaged_region)
558 HippoCanvasText *text = HIPPO_CANVAS_TEXT(box);
559 guint32 color_rgba;
561 if (box->color_set) {
562 color_rgba = box->color_rgba;
563 } else {
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
577 * tooltip for it.
579 text->is_ellipsized = FALSE;
581 if ((color_rgba & 0xff) != 0 && text->text != NULL) {
582 PangoLayout *layout;
583 int layout_width, layout_height;
584 int x, y, w, h;
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,
603 &x, &y, &w, &h);
605 /* we can't really "fill" so we fall back to center if we seem to be
606 * in fill mode
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.
619 cairo_save(cr);
620 cairo_rectangle(cr, 0, 0, allocation_width, allocation_height);
621 cairo_clip(cr);
623 cairo_move_to (cr, x, y);
624 hippo_cairo_set_source_rgba32(cr, color_rgba);
625 pango_cairo_show_layout(cr, layout);
626 cairo_restore(cr);
628 g_object_unref(layout);
632 static void
633 hippo_canvas_text_get_content_width_request(HippoCanvasBox *box,
634 int *min_width_p,
635 int *natural_width_p)
637 HippoCanvasText *text = HIPPO_CANVAS_TEXT(box);
638 int children_min_width, children_natural_width;
639 int layout_width;
641 HIPPO_CANVAS_BOX_CLASS(hippo_canvas_text_parent_class)->get_content_width_request(box,
642 &children_min_width,
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);
649 } else {
650 layout_width = 0;
653 if (min_width_p) {
654 if (text->size_mode == HIPPO_CANVAS_SIZE_FULL_WIDTH)
655 *min_width_p = MAX(children_min_width, layout_width);
656 else
657 *min_width_p = children_min_width;
660 if (natural_width_p) {
661 *natural_width_p = MAX(children_natural_width, layout_width);
665 static void
666 hippo_canvas_text_get_content_height_request(HippoCanvasBox *box,
667 int for_width,
668 int *min_height_p,
669 int *natural_height_p)
671 HippoCanvasText *text = HIPPO_CANVAS_TEXT(box);
672 int children_min_height, children_natural_height;
673 PangoLayout *layout;
674 int layout_height;
676 HIPPO_CANVAS_BOX_CLASS(hippo_canvas_text_parent_class)->get_content_height_request(box,
677 for_width,
678 &children_min_height,
679 &children_natural_height);
681 if (for_width > 0) {
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);
688 } else {
689 layout_height = 0;
691 } else {
692 layout_height = 0;
695 if (min_height_p)
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);
701 static gboolean
702 hippo_canvas_text_button_press_event (HippoCanvasItem *item,
703 HippoEvent *event)
705 /* HippoCanvasText *text = HIPPO_CANVAS_TEXT(item); */
707 /* see if a child wants it */
708 return item_parent_class->button_press_event(item, event);