/* * GooCanvas. Copyright (C) 2005 Damon Chaplin. * Released under the GNU LGPL license. See COPYING for details. * * goocanvastextview.c - view for text item. */ /** * SECTION:goocanvastextview * @Title: GooCanvasTextView * @Short_Description: a view for a #GooCanvasText item. * * #GooCanvasTextView represents a view of a #GooCanvasText item. * * It implements the #GooCanvasItemView interface, so you can use the * #GooCanvasItemView functions such as goo_canvas_item_view_get_item() * and goo_canvas_item_view_get_bounds(). * * Applications do not normally need to create item views themselves, as * they are created automatically by #GooCanvasView when needed. * * To respond to events such as mouse clicks in the ellipse view you can * connect to one of the #GooCanvasItemView signals such as * #GooCanvasItemView::button-press-event. You can connect to these signals * when the view is created. (See goo_canvas_view_get_item_view() and * #GooCanvasView::item-view-created.) */ #include #include #include #include "goocanvasview.h" #include "goocanvastextview.h" #include "goocanvastext.h" static void canvas_item_view_interface_init (GooCanvasItemViewIface *iface); G_DEFINE_TYPE_WITH_CODE (GooCanvasTextView, goo_canvas_text_view, GOO_TYPE_CANVAS_ITEM_VIEW_SIMPLE, G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM_VIEW, canvas_item_view_interface_init)) static void goo_canvas_text_view_class_init (GooCanvasTextViewClass *klass) { /* Create the font options once and reuse it. */ klass->font_options = cairo_font_options_create (); cairo_font_options_set_hint_metrics (klass->font_options, CAIRO_HINT_METRICS_OFF); cairo_font_options_set_hint_style (klass->font_options, CAIRO_HINT_STYLE_NONE); } static void goo_canvas_text_view_init (GooCanvasTextView *text_view) { } /** * goo_canvas_text_view_new: * @canvas_view: the canvas view. * @parent_view: the parent view. * @text: the text item. * * Creates a new #GooCanvasTextView for the given #GooCanvasText item. * * This is not normally used by application code, as the views are created * automatically by #GooCanvasView. * * Returns: a new #GooCanvasTextView. **/ GooCanvasItemView* goo_canvas_text_view_new (GooCanvasView *canvas_view, GooCanvasItemView *parent_view, GooCanvasText *text) { GooCanvasItemViewSimple *view; view = g_object_new (GOO_TYPE_CANVAS_TEXT_VIEW, NULL); view->canvas_view = canvas_view; view->parent_view = parent_view; view->item = g_object_ref (text); goo_canvas_item_view_simple_setup_accessibility (view); g_signal_connect (text, "changed", G_CALLBACK (goo_canvas_item_view_simple_item_changed), view); return (GooCanvasItemView*) view; } static PangoLayout* goo_canvas_text_view_create_layout (GooCanvasTextView *text_view, GooCanvasText *text, cairo_t *cr, GooCanvasBounds *bounds) { PangoLayout *layout; PangoContext *context; PangoRectangle logical_rect; double width, height; gchar *string; string = text->text ? text->text : ""; layout = pango_cairo_create_layout (cr); /* Set the font options to ensure the text layout is the same whatever the scale is. */ context = pango_layout_get_context (layout); pango_cairo_context_set_font_options (context, GOO_CANVAS_TEXT_VIEW_GET_CLASS (text_view)->font_options); if (text->width > 0) pango_layout_set_width (layout, (double) text->width * PANGO_SCALE); if (text->use_markup) pango_layout_set_markup (layout, string, -1); else pango_layout_set_text (layout, string, -1); if (text->font_desc) pango_layout_set_font_description (layout, text->font_desc); if (text->alignment != PANGO_ALIGN_LEFT) pango_layout_set_alignment (layout, text->alignment); /* FIXME: Sometimes we should be using the ink_rect rather than the logical rect, e.g. for the actual bounds of the item view. */ if (bounds) { /* Get size of the text, so we can position it according to anchor. */ pango_layout_get_extents (layout, NULL, &logical_rect); width = (double) logical_rect.width / PANGO_SCALE; height = (double) logical_rect.height / PANGO_SCALE; bounds->x1 = text->x; bounds->y1 = text->y; switch (text->anchor) { case GTK_ANCHOR_N: case GTK_ANCHOR_CENTER: case GTK_ANCHOR_S: bounds->x1 -= width / 2.0; break; case GTK_ANCHOR_NE: case GTK_ANCHOR_E: case GTK_ANCHOR_SE: bounds->x1 -= width; break; default: break; } switch (text->anchor) { case GTK_ANCHOR_W: case GTK_ANCHOR_CENTER: case GTK_ANCHOR_E: bounds->y1 -= height / 2.0; break; case GTK_ANCHOR_SW: case GTK_ANCHOR_S: case GTK_ANCHOR_SE: bounds->y1 -= height; break; default: break; } bounds->x2 = bounds->x1 + width; bounds->y2 = bounds->y1 + height; } return layout; } static void goo_canvas_text_view_update (GooCanvasItemView *view, gboolean entire_tree, cairo_t *cr, GooCanvasBounds *bounds) { GooCanvasItemViewSimple *simple_view = (GooCanvasItemViewSimple*) view; GooCanvasTextView *text_view = (GooCanvasTextView*) view; GooCanvasItemSimple *simple = simple_view->item; GooCanvasText *text = (GooCanvasText*) simple; PangoLayout *layout; if (entire_tree || (simple_view->flags & GOO_CANVAS_ITEM_VIEW_NEED_UPDATE)) { simple_view->flags &= ~GOO_CANVAS_ITEM_VIEW_NEED_UPDATE; cairo_save (cr); if (simple->transform) cairo_transform (cr, simple->transform); if (simple_view->transform) cairo_transform (cr, simple_view->transform); /* Request a redraw of the existing bounds. */ goo_canvas_view_request_redraw (simple_view->canvas_view, &simple_view->bounds); /* Compute the new bounds. */ layout = goo_canvas_text_view_create_layout (text_view, text, cr, &simple_view->bounds); g_object_unref (layout); goo_canvas_item_simple_user_bounds_to_device (simple, cr, &simple_view->bounds); /* Request a redraw of the new bounds. */ goo_canvas_view_request_redraw (simple_view->canvas_view, &simple_view->bounds); cairo_restore (cr); } *bounds = simple_view->bounds; } static GooCanvasItemView* goo_canvas_text_view_get_item_view_at (GooCanvasItemView *view, gdouble x, gdouble y, cairo_t *cr, gboolean is_pointer_event, gboolean parent_visible) { GooCanvasItemViewSimple *simple_view = (GooCanvasItemViewSimple*) view; GooCanvasTextView *text_view = (GooCanvasTextView*) view; GooCanvasItemSimple *simple = simple_view->item; GooCanvasText *text = (GooCanvasText*) simple; GooCanvasItemView *found_view = NULL; PangoLayout *layout; GooCanvasBounds bounds; PangoLayoutIter *iter; PangoRectangle log_rect; int px, py; double user_x = x, user_y = y; /* If there is no text just return. */ if (!text->text || !text->text[0]) return NULL; if (simple_view->flags & GOO_CANVAS_ITEM_VIEW_NEED_UPDATE) goo_canvas_item_view_ensure_updated (view); /* Check if the item should receive events. */ if (is_pointer_event) { if (simple->pointer_events == GOO_CANVAS_EVENTS_NONE) return NULL; if (simple->pointer_events & GOO_CANVAS_EVENTS_VISIBLE_MASK && (!parent_visible || simple->visibility == GOO_CANVAS_ITEM_INVISIBLE || (simple->visibility == GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD && simple_view->canvas_view->scale < simple->visibility_threshold))) return NULL; if (simple->pointer_events & GOO_CANVAS_EVENTS_PAINTED_MASK) { if (simple->style && (simple->style->mask & GOO_CANVAS_STYLE_FILL_PATTERN) && simple->style->fill_pattern == NULL) return NULL; } } cairo_save (cr); if (simple->transform) cairo_transform (cr, simple->transform); if (simple_view->transform) cairo_transform (cr, simple_view->transform); cairo_device_to_user (cr, &user_x, &user_y); layout = goo_canvas_text_view_create_layout (text_view, text, cr, &bounds); /* Convert the coordinates into Pango units. */ px = (user_x - bounds.x1) * PANGO_SCALE; py = (user_y - bounds.y1) * PANGO_SCALE; /* We use line extents here. Note that SVG uses character cells to determine hits so we have slightly different behavior. */ iter = pango_layout_get_iter (layout); do { pango_layout_iter_get_line_extents (iter, NULL, &log_rect); if (px >= log_rect.x && px < log_rect.x + log_rect.width && py >= log_rect.y && py < log_rect.y + log_rect.height) { found_view = view; break; } } while (pango_layout_iter_next_line (iter)); pango_layout_iter_free (iter); g_object_unref (layout); cairo_restore (cr); return found_view; } static void goo_canvas_text_view_paint (GooCanvasItemView *view, cairo_t *cr, GooCanvasBounds *bounds, gdouble scale) { GooCanvasItemViewSimple *simple_view = (GooCanvasItemViewSimple*) view; GooCanvasTextView *text_view = (GooCanvasTextView*) view; GooCanvasItemSimple *simple = simple_view->item; GooCanvasText *text = (GooCanvasText*) simple; PangoLayout *layout; GooCanvasBounds layout_bounds; /* If there is no text just return. */ if (!text->text || !text->text[0]) return; /* Check if the item should be visible. */ if (simple->visibility == GOO_CANVAS_ITEM_INVISIBLE || (simple->visibility == GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD && scale < simple->visibility_threshold)) return; /* If the fill pattern has been explicitly set to NULL, don't paint. */ if (simple->style && (simple->style->mask & GOO_CANVAS_STYLE_FILL_PATTERN) && simple->style->fill_pattern == NULL) return; cairo_save (cr); if (simple->transform) cairo_transform (cr, simple->transform); if (simple_view->transform) cairo_transform (cr, simple_view->transform); goo_canvas_item_simple_set_fill_options (simple, cr); cairo_new_path (cr); layout = goo_canvas_text_view_create_layout (text_view, text, cr, &layout_bounds); cairo_move_to (cr, layout_bounds.x1, layout_bounds.y1); pango_cairo_show_layout (cr, layout); g_object_unref (layout); cairo_restore (cr); } static void canvas_item_view_interface_init (GooCanvasItemViewIface *iface) { iface->get_item_view_at = goo_canvas_text_view_get_item_view_at; iface->update = goo_canvas_text_view_update; iface->paint = goo_canvas_text_view_paint; }