X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtktextdisplay.c;h=24cc0ec6b4850970c08c3b46f341184232b7b328;hb=ee44ed75ca50cb078ba8d5cb62c6a5d9d568f0e6;hp=b3acdab830d9b62946e51651dcafebe50661b85d;hpb=8f5c2c76253447124abc6cf33b69ad53492f6184;p=~andy%2Fgtk diff --git a/gtk/gtktextdisplay.c b/gtk/gtktextdisplay.c index b3acdab83..24cc0ec6b 100644 --- a/gtk/gtktextdisplay.c +++ b/gtk/gtktextdisplay.c @@ -21,8 +21,7 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free - * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * License along with this library. If not, see .Free * * Original Tk license: * @@ -75,416 +74,503 @@ */ #define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API +#include "config.h" #include "gtktextdisplay.h" +#include "gtkwidgetprivate.h" +#include "gtkstylecontextprivate.h" +#include "gtkintl.h" + /* DO NOT go putting private headers in here. This file should only * use the semi-public headers, as with gtktextview.c. */ -#include +#define GTK_TYPE_TEXT_RENDERER (_gtk_text_renderer_get_type()) +#define GTK_TEXT_RENDERER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GTK_TYPE_TEXT_RENDERER, GtkTextRenderer)) +#define GTK_IS_TEXT_RENDERER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GTK_TYPE_TEXT_RENDERER)) +#define GTK_TEXT_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TEXT_RENDERER, GtkTextRendererClass)) +#define GTK_IS_TEXT_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TEXT_RENDERER)) +#define GTK_TEXT_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TEXT_RENDERER, GtkTextRendererClass)) + +typedef struct _GtkTextRenderer GtkTextRenderer; +typedef struct _GtkTextRendererClass GtkTextRendererClass; -typedef struct _GtkTextRenderState GtkTextRenderState; +enum { + NORMAL, + SELECTED, + CURSOR +}; -struct _GtkTextRenderState +struct _GtkTextRenderer { + PangoRenderer parent_instance; + GtkWidget *widget; + cairo_t *cr; + + GdkRGBA *error_color; /* Error underline color for this widget */ + GList *widgets; /* widgets encountered when drawing */ - GtkTextAppearance *last_appearance; - GtkTextAppearance *last_bg_appearance; - GdkGC *fg_gc; - GdkGC *bg_gc; - GdkRectangle clip_rect; + GdkRGBA rgba[4]; + guint8 rgba_set[4]; + + guint state : 2; }; -static void get_item_properties (PangoItem *item, - GtkTextAppearance **appearance); -static GdkRegion *get_selected_clip (GtkTextRenderState *render_state, - PangoLayout *layout, - PangoLayoutLine *line, - int x, - int y, - int height, - int start_index, - int end_index); - -static GtkTextRenderState * -gtk_text_render_state_new (GtkWidget *widget, - GdkDrawable *drawable, - GdkRectangle *clip_rect) +struct _GtkTextRendererClass { - GtkTextRenderState *state = g_new0 (GtkTextRenderState, 1); + PangoRendererClass parent_class; +}; - state->widget = widget; - state->fg_gc = gdk_gc_new (drawable); - state->bg_gc = gdk_gc_new (drawable); - state->clip_rect = *clip_rect; +GType _gtk_text_renderer_get_type (void); - return state; -} +G_DEFINE_TYPE (GtkTextRenderer, _gtk_text_renderer, PANGO_TYPE_RENDERER) static void -gtk_text_render_state_destroy (GtkTextRenderState *state) +text_renderer_set_rgba (GtkTextRenderer *text_renderer, + PangoRenderPart part, + const GdkRGBA *rgba) { - gdk_gc_unref (state->fg_gc); - gdk_gc_unref (state->bg_gc); + PangoRenderer *renderer = PANGO_RENDERER (text_renderer); + PangoColor dummy = { 0, }; - g_free (state); + if (rgba) + { + text_renderer->rgba[part] = *rgba; + pango_renderer_set_color (renderer, part, &dummy); + } + else + pango_renderer_set_color (renderer, part, NULL); + + text_renderer->rgba_set[part] = (rgba != NULL); } -static void -gtk_text_render_state_set_color (GtkTextRenderState *state, - GdkGC *gc, - GdkColor *color) +static GtkTextAppearance * +get_item_appearance (PangoItem *item) { - gdk_colormap_alloc_color (gtk_widget_get_colormap (state->widget), color, FALSE, TRUE); - gdk_gc_set_foreground (gc, color); + GSList *tmp_list = item->analysis.extra_attrs; + + while (tmp_list) + { + PangoAttribute *attr = tmp_list->data; + + if (attr->klass->type == gtk_text_attr_appearance_type) + return &((GtkTextAttrAppearance *)attr)->appearance; + + tmp_list = tmp_list->next; + } + + return NULL; } static void -gtk_text_render_state_update (GtkTextRenderState *state, - GtkTextAppearance *new_appearance) +gtk_text_renderer_prepare_run (PangoRenderer *renderer, + PangoLayoutRun *run) { - if (!state->last_appearance || - !gdk_color_equal (&new_appearance->fg_color, &state->last_appearance->fg_color)) - gtk_text_render_state_set_color (state, state->fg_gc, &new_appearance->fg_color); + GtkStyleContext *context; + GtkStateFlags state; + GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); + GdkRGBA *bg_rgba = NULL; + GdkRGBA *fg_rgba = NULL; + GtkTextAppearance *appearance; + + PANGO_RENDERER_CLASS (_gtk_text_renderer_parent_class)->prepare_run (renderer, run); + + appearance = get_item_appearance (run->item); + g_assert (appearance != NULL); + + context = gtk_widget_get_style_context (text_renderer->widget); + state = gtk_widget_get_state_flags (text_renderer->widget); + + if (appearance->draw_bg && text_renderer->state == NORMAL) + bg_rgba = appearance->rgba[0]; + else + bg_rgba = NULL; + + text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_BACKGROUND, bg_rgba); - if (!state->last_appearance || - new_appearance->fg_stipple != state->last_appearance->fg_stipple) + if (text_renderer->state == SELECTED) { - if (new_appearance->fg_stipple) - { - gdk_gc_set_fill (state->fg_gc, GDK_STIPPLED); - gdk_gc_set_stipple (state->fg_gc, new_appearance->fg_stipple); - } - else - { - gdk_gc_set_fill (state->fg_gc, GDK_SOLID); - } - } + state |= GTK_STATE_FLAG_SELECTED; - if (new_appearance->draw_bg) + gtk_style_context_get (context, state, "color", &fg_rgba, NULL); + } + else if (text_renderer->state == CURSOR && gtk_widget_has_focus (text_renderer->widget)) { - if (!state->last_bg_appearance || - !gdk_color_equal (&new_appearance->bg_color, &state->last_bg_appearance->bg_color)) - gtk_text_render_state_set_color (state, state->bg_gc, &new_appearance->bg_color); + gtk_style_context_get (context, state, + "background-color", &fg_rgba, + NULL); + } + else + fg_rgba = appearance->rgba[1]; + + text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_FOREGROUND, fg_rgba); + text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_STRIKETHROUGH, fg_rgba); - if (!state->last_bg_appearance || - new_appearance->bg_stipple != state->last_bg_appearance->bg_stipple) + if (appearance->underline == PANGO_UNDERLINE_ERROR) + { + if (!text_renderer->error_color) { - if (new_appearance->bg_stipple) - { - gdk_gc_set_fill (state->bg_gc, GDK_STIPPLED); - gdk_gc_set_stipple (state->bg_gc, new_appearance->bg_stipple); - } - else - { - gdk_gc_set_fill (state->bg_gc, GDK_SOLID); - } + GdkColor *color = NULL; + + gtk_style_context_get_style (context, + "error-underline-color", &color, + NULL); + + if (color) + { + GdkRGBA rgba; + + rgba.red = color->red / 65535.; + rgba.green = color->green / 65535.; + rgba.blue = color->blue / 65535.; + rgba.alpha = 1; + gdk_color_free (color); + + text_renderer->error_color = gdk_rgba_copy (&rgba); + } + else + { + static const GdkRGBA red = { 1, 0, 0, 1 }; + text_renderer->error_color = gdk_rgba_copy (&red); + } } - state->last_bg_appearance = new_appearance; + text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_UNDERLINE, text_renderer->error_color); } + else + text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_UNDERLINE, fg_rgba); - state->last_appearance = new_appearance; + if (fg_rgba != appearance->rgba[1]) + gdk_rgba_free (fg_rgba); } static void -get_shape_extents (PangoLayoutRun *run, - PangoRectangle *ink_rect, - PangoRectangle *logical_rect) +set_color (GtkTextRenderer *text_renderer, + PangoRenderPart part) { - GSList *tmp_list = run->item->analysis.extra_attrs; - - while (tmp_list) - { - PangoAttribute *attr = tmp_list->data; + cairo_save (text_renderer->cr); - if (attr->klass->type == PANGO_ATTR_SHAPE) - { - if (logical_rect) - *logical_rect = ((PangoAttrShape *)attr)->logical_rect; + if (text_renderer->rgba_set[part]) + gdk_cairo_set_source_rgba (text_renderer->cr, &text_renderer->rgba[part]); +} - if (ink_rect) - *ink_rect = ((PangoAttrShape *)attr)->ink_rect; +static void +unset_color (GtkTextRenderer *text_renderer) +{ + cairo_restore (text_renderer->cr); +} - return; - } +static void +gtk_text_renderer_draw_glyphs (PangoRenderer *renderer, + PangoFont *font, + PangoGlyphString *glyphs, + int x, + int y) +{ + GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); - tmp_list = tmp_list->next; - } + set_color (text_renderer, PANGO_RENDER_PART_FOREGROUND); + + cairo_move_to (text_renderer->cr, (double)x / PANGO_SCALE, (double)y / PANGO_SCALE); + pango_cairo_show_glyph_string (text_renderer->cr, font, glyphs); - g_assert_not_reached (); + unset_color (text_renderer); } static void -render_layout_line (GdkDrawable *drawable, - GtkTextRenderState *render_state, - PangoLayoutLine *line, - GSList **shaped_pointer, - int x, - int y, - gboolean selected, - GList **widgets) +gtk_text_renderer_draw_glyph_item (PangoRenderer *renderer, + const char *text, + PangoGlyphItem *glyph_item, + int x, + int y) { - GSList *tmp_list = line->runs; - PangoRectangle overall_rect; - PangoRectangle logical_rect; - PangoRectangle ink_rect; - gint x_off = 0; - GdkGC *fg_gc; - - pango_layout_line_get_extents (line, NULL, &overall_rect); + GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); - while (tmp_list) + set_color (text_renderer, PANGO_RENDER_PART_FOREGROUND); + + cairo_move_to (text_renderer->cr, (double)x / PANGO_SCALE, (double)y / PANGO_SCALE); + pango_cairo_show_glyph_item (text_renderer->cr, text, glyph_item); + + unset_color (text_renderer); +} + +static void +gtk_text_renderer_draw_rectangle (PangoRenderer *renderer, + PangoRenderPart part, + int x, + int y, + int width, + int height) +{ + GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); + + set_color (text_renderer, part); + + cairo_rectangle (text_renderer->cr, + (double)x / PANGO_SCALE, (double)y / PANGO_SCALE, + (double)width / PANGO_SCALE, (double)height / PANGO_SCALE); + cairo_fill (text_renderer->cr); + + unset_color (text_renderer); +} + +static void +gtk_text_renderer_draw_trapezoid (PangoRenderer *renderer, + PangoRenderPart part, + double y1_, + double x11, + double x21, + double y2, + double x12, + double x22) +{ + GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); + cairo_t *cr; + cairo_matrix_t matrix; + + set_color (text_renderer, part); + + cr = text_renderer->cr; + + cairo_get_matrix (cr, &matrix); + matrix.xx = matrix.yy = 1.0; + matrix.xy = matrix.yx = 0.0; + cairo_set_matrix (cr, &matrix); + + cairo_move_to (cr, x11, y1_); + cairo_line_to (cr, x21, y1_); + cairo_line_to (cr, x22, y2); + cairo_line_to (cr, x12, y2); + cairo_close_path (cr); + + cairo_fill (cr); + + unset_color (text_renderer); +} + +static void +gtk_text_renderer_draw_error_underline (PangoRenderer *renderer, + int x, + int y, + int width, + int height) +{ + GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); + + set_color (text_renderer, PANGO_RENDER_PART_UNDERLINE); + + pango_cairo_show_error_underline (text_renderer->cr, + (double)x / PANGO_SCALE, (double)y / PANGO_SCALE, + (double)width / PANGO_SCALE, (double)height / PANGO_SCALE); + + unset_color (text_renderer); +} + +static void +gtk_text_renderer_draw_shape (PangoRenderer *renderer, + PangoAttrShape *attr, + int x, + int y) +{ + GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); + + if (attr->data == NULL) { - PangoLayoutRun *run = tmp_list->data; - GtkTextAppearance *appearance; - gint risen_y; - gint shaped_width_pixels = 0; - gboolean need_ink = FALSE; - - tmp_list = tmp_list->next; + /* This happens if we have an empty widget anchor. Draw + * something empty-looking. + */ + GdkRectangle shape_rect; + cairo_t *cr; - get_item_properties (run->item, &appearance); + shape_rect.x = PANGO_PIXELS (x); + shape_rect.y = PANGO_PIXELS (y + attr->logical_rect.y); + shape_rect.width = PANGO_PIXELS (x + attr->logical_rect.width) - shape_rect.x; + shape_rect.height = PANGO_PIXELS (y + attr->logical_rect.y + attr->logical_rect.height) - shape_rect.y; - g_assert (appearance != NULL); - - risen_y = y - PANGO_PIXELS (appearance->rise); - - if (selected) - { - if (GTK_WIDGET_HAS_FOCUS (render_state->widget)) - fg_gc = render_state->widget->style->text_gc[GTK_STATE_SELECTED]; - else - fg_gc = render_state->widget->style->text_gc [GTK_STATE_ACTIVE]; - } - else - { - gtk_text_render_state_update (render_state, appearance); - - fg_gc = render_state->fg_gc; - } - - if (appearance->underline != PANGO_UNDERLINE_NONE || - appearance->strikethrough) - need_ink = TRUE; + set_color (text_renderer, PANGO_RENDER_PART_FOREGROUND); + + cr = text_renderer->cr; + + cairo_set_line_width (cr, 1.0); + + cairo_rectangle (cr, + shape_rect.x + 0.5, shape_rect.y + 0.5, + shape_rect.width - 1, shape_rect.height - 1); + cairo_move_to (cr, shape_rect.x + 0.5, shape_rect.y + 0.5); + cairo_line_to (cr, + shape_rect.x + shape_rect.width - 0.5, + shape_rect.y + shape_rect.height - 0.5); + cairo_move_to (cr, shape_rect.x + 0.5, + shape_rect.y + shape_rect.height - 0.5); + cairo_line_to (cr, shape_rect.x + shape_rect.width - 0.5, + shape_rect.y + 0.5); + + cairo_stroke (cr); + + unset_color (text_renderer); + } + else if (GDK_IS_PIXBUF (attr->data)) + { + cairo_t *cr = text_renderer->cr; + GdkPixbuf *pixbuf = GDK_PIXBUF (attr->data); - if (appearance->is_text) - { - if (need_ink) - pango_glyph_string_extents (run->glyphs, run->item->analysis.font, - &ink_rect, &logical_rect); - else - pango_glyph_string_extents (run->glyphs, run->item->analysis.font, - NULL, &logical_rect); - } - else - { - if (need_ink) - get_shape_extents (run, &ink_rect, &logical_rect); - else - get_shape_extents (run, NULL, &logical_rect); - } + cairo_save (cr); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, + PANGO_PIXELS (x), + PANGO_PIXELS (y) - gdk_pixbuf_get_height (pixbuf)); + cairo_paint (cr); + + cairo_restore (cr); + } + else if (GTK_IS_WIDGET (attr->data)) + { + GtkWidget *widget; - if (appearance->draw_bg && !selected) - gdk_draw_rectangle (drawable, render_state->bg_gc, TRUE, - x + PANGO_PIXELS (x_off) + PANGO_PIXELS (logical_rect.x), - risen_y + PANGO_PIXELS (logical_rect.y), - PANGO_PIXELS (logical_rect.width), - PANGO_PIXELS (logical_rect.height)); - - if (appearance->is_text) - gdk_draw_glyphs (drawable, fg_gc, - run->item->analysis.font, - x + PANGO_PIXELS (x_off), - risen_y, run->glyphs); - else - { - GObject *shaped = (*shaped_pointer)->data; + widget = GTK_WIDGET (attr->data); - *shaped_pointer = (*shaped_pointer)->next; + text_renderer->widgets = g_list_prepend (text_renderer->widgets, + g_object_ref (widget)); + } + else + g_assert_not_reached (); /* not a pixbuf or widget */ +} - if (shaped == NULL) - { - /* This happens if we have an empty widget anchor. Draw - * something empty-looking. - */ - GdkRectangle shape_rect, draw_rect; - - shape_rect.x = x + x_off / PANGO_SCALE; - shape_rect.y = risen_y - PANGO_PIXELS (logical_rect.height); - shape_rect.width = PANGO_PIXELS (logical_rect.width); - shape_rect.height = PANGO_PIXELS (logical_rect.height); - - if (gdk_rectangle_intersect (&shape_rect, &render_state->clip_rect, - &draw_rect)) - { - gdk_draw_rectangle (drawable, render_state->fg_gc, - FALSE, shape_rect.x, shape_rect.y, - shape_rect.width, shape_rect.height); - - gdk_draw_line (drawable, render_state->fg_gc, - shape_rect.x, shape_rect.y, - shape_rect.x + shape_rect.width, - shape_rect.y + shape_rect.height); - - gdk_draw_line (drawable, render_state->fg_gc, - shape_rect.x + shape_rect.width, shape_rect.y, - shape_rect.x, - shape_rect.y + shape_rect.height); - } +static void +gtk_text_renderer_finalize (GObject *object) +{ + G_OBJECT_CLASS (_gtk_text_renderer_parent_class)->finalize (object); +} - shaped_width_pixels = shape_rect.width; - } - else if (GDK_IS_PIXBUF (shaped)) - { - gint width, height; - GdkRectangle pixbuf_rect, draw_rect; - GdkPixbuf *pixbuf; +static void +_gtk_text_renderer_init (GtkTextRenderer *renderer) +{ +} - pixbuf = GDK_PIXBUF (shaped); - - width = gdk_pixbuf_get_width (pixbuf); - height = gdk_pixbuf_get_height (pixbuf); +static void +_gtk_text_renderer_class_init (GtkTextRendererClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + PangoRendererClass *renderer_class = PANGO_RENDERER_CLASS (klass); + + renderer_class->prepare_run = gtk_text_renderer_prepare_run; + renderer_class->draw_glyphs = gtk_text_renderer_draw_glyphs; + renderer_class->draw_glyph_item = gtk_text_renderer_draw_glyph_item; + renderer_class->draw_rectangle = gtk_text_renderer_draw_rectangle; + renderer_class->draw_trapezoid = gtk_text_renderer_draw_trapezoid; + renderer_class->draw_error_underline = gtk_text_renderer_draw_error_underline; + renderer_class->draw_shape = gtk_text_renderer_draw_shape; + + object_class->finalize = gtk_text_renderer_finalize; +} - pixbuf_rect.x = x + x_off / PANGO_SCALE; - pixbuf_rect.y = risen_y - height; - pixbuf_rect.width = width; - pixbuf_rect.height = height; +static void +text_renderer_set_state (GtkTextRenderer *text_renderer, + int state) +{ + text_renderer->state = state; +} - if (gdk_rectangle_intersect (&pixbuf_rect, &render_state->clip_rect, - &draw_rect)) - { - GdkBitmap *mask = NULL; - - if (gdk_pixbuf_get_has_alpha (pixbuf)) - { - mask = gdk_pixmap_new (drawable, - gdk_pixbuf_get_width (pixbuf), - gdk_pixbuf_get_height (pixbuf), - 1); - - gdk_pixbuf_render_threshold_alpha (pixbuf, mask, - 0, 0, 0, 0, - gdk_pixbuf_get_width (pixbuf), - gdk_pixbuf_get_height (pixbuf), - 128); - - } - - if (mask) - { - gdk_gc_set_clip_mask (render_state->fg_gc, mask); - gdk_gc_set_clip_origin (render_state->fg_gc, - pixbuf_rect.x, pixbuf_rect.y); - } - - gdk_pixbuf_render_to_drawable (pixbuf, - drawable, - render_state->fg_gc, - draw_rect.x - pixbuf_rect.x, - draw_rect.y - pixbuf_rect.y, - draw_rect.x, draw_rect.y, - draw_rect.width, - draw_rect.height, - GDK_RGB_DITHER_NORMAL, - 0, 0); - - if (mask) - { - gdk_gc_set_clip_rectangle (render_state->fg_gc, - &render_state->clip_rect); - g_object_unref (G_OBJECT (mask)); - } - } +static void +text_renderer_begin (GtkTextRenderer *text_renderer, + GtkWidget *widget, + cairo_t *cr) +{ + GtkStyleContext *context; + GtkStateFlags state; + GdkRGBA color; - shaped_width_pixels = width; - } - else if (GTK_IS_WIDGET (shaped)) - { - GtkWidget *widget; - - widget = GTK_WIDGET (shaped); - - shaped_width_pixels = widget->allocation.width; + text_renderer->widget = widget; + text_renderer->cr = cr; - if (widgets) - { - g_object_ref (G_OBJECT (widget)); - *widgets = g_list_prepend (*widgets, widget); - } - } - else - g_assert_not_reached (); /* not a pixbuf or widget */ - } + context = gtk_widget_get_style_context (widget); - switch (appearance->underline) - { - case PANGO_UNDERLINE_NONE: - break; - case PANGO_UNDERLINE_DOUBLE: - g_assert (need_ink); - gdk_draw_line (drawable, fg_gc, - x + (x_off + ink_rect.x) / PANGO_SCALE - 1, - risen_y + 3, - x + (x_off + ink_rect.x + ink_rect.width) / PANGO_SCALE, - risen_y + 3); - /* Fall through */ - case PANGO_UNDERLINE_SINGLE: - g_assert (need_ink); - gdk_draw_line (drawable, fg_gc, - x + (x_off + ink_rect.x) / PANGO_SCALE - 1, - risen_y + 1, - x + (x_off + ink_rect.x + ink_rect.width) / PANGO_SCALE, - risen_y + 1); - break; - case PANGO_UNDERLINE_LOW: - g_assert (need_ink); - gdk_draw_line (drawable, fg_gc, - x + (x_off + ink_rect.x) / PANGO_SCALE - 1, - risen_y + (ink_rect.y + ink_rect.height) / PANGO_SCALE + 1, - x + (x_off + ink_rect.x + ink_rect.width) / PANGO_SCALE, - risen_y + (ink_rect.y + ink_rect.height) / PANGO_SCALE + 1); - break; - } + gtk_style_context_save (context); + gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW); - if (appearance->strikethrough) - { - gint strikethrough_y = risen_y + (0.3 * logical_rect.y) / PANGO_SCALE; + state = gtk_widget_get_state_flags (widget); + gtk_style_context_get_color (context, state, &color); - g_assert (need_ink); - - gdk_draw_line (drawable, fg_gc, - x + (x_off + ink_rect.x) / PANGO_SCALE - 1, strikethrough_y, - x + (x_off + ink_rect.x + ink_rect.width) / PANGO_SCALE, strikethrough_y); - } + cairo_save (cr); - if (appearance->is_text) - x_off += logical_rect.width; - else - x_off += shaped_width_pixels * PANGO_SCALE; + gdk_cairo_set_source_rgba (cr, &color); +} + +/* Returns a GSList of (referenced) widgets encountered while drawing. + */ +static GList * +text_renderer_end (GtkTextRenderer *text_renderer) +{ + GtkStyleContext *context; + GList *widgets = text_renderer->widgets; + + cairo_restore (text_renderer->cr); + + context = gtk_widget_get_style_context (text_renderer->widget); + + gtk_style_context_restore (context); + + text_renderer->widget = NULL; + text_renderer->cr = NULL; + + text_renderer->widgets = NULL; + + if (text_renderer->error_color) + { + gdk_rgba_free (text_renderer->error_color); + text_renderer->error_color = NULL; } + + return widgets; +} + +static cairo_region_t * +get_selected_clip (GtkTextRenderer *text_renderer, + PangoLayout *layout, + PangoLayoutLine *line, + int x, + int y, + int height, + int start_index, + int end_index) +{ + gint *ranges; + gint n_ranges, i; + cairo_region_t *clip_region = cairo_region_create (); + + pango_layout_line_get_x_ranges (line, start_index, end_index, &ranges, &n_ranges); + + for (i=0; i < n_ranges; i++) + { + GdkRectangle rect; + + rect.x = x + PANGO_PIXELS (ranges[2*i]); + rect.y = y; + rect.width = PANGO_PIXELS (ranges[2*i + 1]) - PANGO_PIXELS (ranges[2*i]); + rect.height = height; + + cairo_region_union_rectangle (clip_region, &rect); + } + + g_free (ranges); + return clip_region; } static void -render_para (GdkDrawable *drawable, - GtkTextRenderState *render_state, +render_para (GtkTextRenderer *text_renderer, GtkTextLineDisplay *line_display, - /* Top-left corner of paragraph including all margins */ - int x, - int y, int selection_start_index, - int selection_end_index, - GList **widgets) + int selection_end_index) { - GSList *shaped_pointer = line_display->shaped_objects; + GtkStyleContext *context; + GtkStateFlags state; PangoLayout *layout = line_display->layout; int byte_offset = 0; PangoLayoutIter *iter; PangoRectangle layout_logical; int screen_width; - GdkGC *fg_gc, *bg_gc; - gint state; - + GdkRGBA selection; gboolean first = TRUE; iter = pango_layout_get_iter (layout); @@ -497,22 +583,22 @@ render_para (GdkDrawable *drawable, layout_logical.y += line_display->top_margin * PANGO_SCALE; screen_width = line_display->total_width; - - if (GTK_WIDGET_HAS_FOCUS (render_state->widget)) - state = GTK_STATE_SELECTED; - else - state = GTK_STATE_ACTIVE; - fg_gc = render_state->widget->style->text_gc [state]; - bg_gc = render_state->widget->style->base_gc [state]; + context = gtk_widget_get_style_context (text_renderer->widget); + state = gtk_widget_get_state_flags (text_renderer->widget); + + state |= GTK_STATE_FLAG_SELECTED; + + gtk_style_context_get_background_color (context, state, &selection); do { - PangoLayoutLine *line = pango_layout_iter_get_line (iter); + PangoLayoutLine *line = pango_layout_iter_get_line_readonly (iter); int selection_y, selection_height; int first_y, last_y; PangoRectangle line_rect; int baseline; + gboolean at_last_line; pango_layout_iter_get_line_extents (iter, NULL, &line_rect); baseline = pango_layout_iter_get_baseline (iter); @@ -527,7 +613,7 @@ render_para (GdkDrawable *drawable, /* Selection is the height of the line, plus top/bottom * margin if we're the first/last line */ - selection_y = y + PANGO_PIXELS (first_y) + line_display->top_margin; + selection_y = PANGO_PIXELS (first_y) + line_display->top_margin; selection_height = PANGO_PIXELS (last_y) - PANGO_PIXELS (first_y); if (first) @@ -535,8 +621,9 @@ render_para (GdkDrawable *drawable, selection_y -= line_display->top_margin; selection_height += line_display->top_margin; } - - if (pango_layout_iter_at_last_line (iter)) + + at_last_line = pango_layout_iter_at_last_line (iter); + if (at_last_line) selection_height += line_display->bottom_margin; first = FALSE; @@ -544,73 +631,97 @@ render_para (GdkDrawable *drawable, if (selection_start_index < byte_offset && selection_end_index > line->length + byte_offset) /* All selected */ { - gdk_draw_rectangle (drawable, - bg_gc, - TRUE, - x + line_display->left_margin, - selection_y, - screen_width, - selection_height); - - render_layout_line (drawable, render_state, line, &shaped_pointer, - x + PANGO_PIXELS (line_rect.x), - y + PANGO_PIXELS (baseline), - TRUE, - widgets); + cairo_t *cr = text_renderer->cr; + + cairo_save (cr); + gdk_cairo_set_source_rgba (cr, &selection); + cairo_rectangle (cr, + line_display->left_margin, selection_y, + screen_width, selection_height); + cairo_fill (cr); + cairo_restore(cr); + + text_renderer_set_state (text_renderer, SELECTED); + pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer), + line, + line_rect.x, + baseline); } else { - GSList *shaped_pointer_tmp = shaped_pointer; + if (line_display->pg_bg_rgba) + { + cairo_t *cr = text_renderer->cr; - render_layout_line (drawable, render_state, - line, &shaped_pointer, - x + PANGO_PIXELS (line_rect.x), - y + PANGO_PIXELS (baseline), - FALSE, - widgets); + cairo_save (cr); + + gdk_cairo_set_source_rgba (text_renderer->cr, line_display->pg_bg_rgba); + cairo_rectangle (cr, + line_display->left_margin, selection_y, + screen_width, selection_height); + cairo_fill (cr); - if (selection_start_index <= byte_offset + line->length && - selection_end_index > byte_offset) /* Some selected */ + cairo_restore (cr); + } + + text_renderer_set_state (text_renderer, NORMAL); + pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer), + line, + line_rect.x, + baseline); + + /* Check if some part of the line is selected; the newline + * that is after line->length for the last line of the + * paragraph counts as part of the line for this + */ + if ((selection_start_index < byte_offset + line->length || + (selection_start_index == byte_offset + line->length && pango_layout_iter_at_last_line (iter))) && + selection_end_index > byte_offset) { - GdkRegion *clip_region = get_selected_clip (render_state, layout, line, - x + line_display->x_offset, + cairo_t *cr = text_renderer->cr; + cairo_region_t *clip_region = get_selected_clip (text_renderer, layout, line, + line_display->x_offset, selection_y, selection_height, selection_start_index, selection_end_index); - gdk_gc_set_clip_region (fg_gc, clip_region); - gdk_gc_set_clip_region (bg_gc, clip_region); - gdk_draw_rectangle (drawable, - bg_gc, - TRUE, - x + PANGO_PIXELS (line_rect.x), - selection_y, - PANGO_PIXELS (line_rect.width), - selection_height); + cairo_save (cr); + gdk_cairo_region (cr, clip_region); + cairo_clip (cr); + cairo_region_destroy (clip_region); - render_layout_line (drawable, render_state, line, &shaped_pointer_tmp, - x + PANGO_PIXELS (line_rect.x), - y + PANGO_PIXELS (baseline), - TRUE, - widgets); + gdk_cairo_set_source_rgba (cr, &selection); + cairo_rectangle (cr, + PANGO_PIXELS (line_rect.x), + selection_y, + PANGO_PIXELS (line_rect.width), + selection_height); + cairo_fill (cr); - gdk_gc_set_clip_region (fg_gc, NULL); - gdk_gc_set_clip_region (bg_gc, NULL); + text_renderer_set_state (text_renderer, SELECTED); + pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer), + line, + line_rect.x, + baseline); - gdk_region_destroy (clip_region); + cairo_restore (cr); /* Paint in the ends of the line */ if (line_rect.x > line_display->left_margin * PANGO_SCALE && ((line_display->direction == GTK_TEXT_DIR_LTR && selection_start_index < byte_offset) || (line_display->direction == GTK_TEXT_DIR_RTL && selection_end_index > byte_offset + line->length))) { - gdk_draw_rectangle (drawable, - bg_gc, - TRUE, - x + line_display->left_margin, - selection_y, - PANGO_PIXELS (line_rect.x) - line_display->left_margin, - selection_height); + cairo_save (cr); + + gdk_cairo_set_source_rgba (cr, &selection); + cairo_rectangle (cr, + line_display->left_margin, + selection_y, + PANGO_PIXELS (line_rect.x) - line_display->left_margin, + selection_height); + cairo_fill (cr); + + cairo_restore (cr); } if (line_rect.x + line_rect.width < @@ -624,141 +735,128 @@ render_para (GdkDrawable *drawable, line_display->left_margin + screen_width - PANGO_PIXELS (line_rect.x) - PANGO_PIXELS (line_rect.width); - gdk_draw_rectangle (drawable, - bg_gc, - TRUE, - x + PANGO_PIXELS (line_rect.x) + PANGO_PIXELS (line_rect.width), - selection_y, - nonlayout_width, - selection_height); + cairo_save (cr); + + gdk_cairo_set_source_rgba (cr, &selection); + cairo_rectangle (cr, + PANGO_PIXELS (line_rect.x) + PANGO_PIXELS (line_rect.width), + selection_y, + nonlayout_width, + selection_height); + cairo_fill (cr); + + cairo_restore (cr); } } - } + else if (line_display->has_block_cursor && + gtk_widget_has_focus (text_renderer->widget) && + byte_offset <= line_display->insert_index && + (line_display->insert_index < byte_offset + line->length || + (at_last_line && line_display->insert_index == byte_offset + line->length))) + { + GdkRectangle cursor_rect; + GdkRGBA cursor_color; + cairo_t *cr = text_renderer->cr; - byte_offset += line->length; - } - while (pango_layout_iter_next_line (iter)); + /* we draw text using base color on filled cursor rectangle of cursor color + * (normally white on black) */ + _gtk_style_context_get_cursor_color (context, &cursor_color, NULL); - pango_layout_iter_free (iter); -} + cursor_rect.x = line_display->x_offset + line_display->block_cursor.x; + cursor_rect.y = line_display->block_cursor.y + line_display->top_margin; + cursor_rect.width = line_display->block_cursor.width; + cursor_rect.height = line_display->block_cursor.height; -static GdkRegion * -get_selected_clip (GtkTextRenderState *render_state, - PangoLayout *layout, - PangoLayoutLine *line, - int x, - int y, - int height, - int start_index, - int end_index) -{ - gint *ranges; - gint n_ranges, i; - GdkRegion *clip_region = gdk_region_new (); - GdkRegion *tmp_region; + cairo_save (cr); - pango_layout_line_get_x_ranges (line, start_index, end_index, &ranges, &n_ranges); + gdk_cairo_rectangle (cr, &cursor_rect); + cairo_clip (cr); - for (i=0; i < n_ranges; i++) - { - GdkRectangle rect; + gdk_cairo_set_source_rgba (cr, &cursor_color); + cairo_paint (cr); - rect.x = x + PANGO_PIXELS (ranges[2*i]); - rect.y = y; - rect.width = PANGO_PIXELS (ranges[2*i + 1]) - PANGO_PIXELS (ranges[2*i]); - rect.height = height; - - gdk_region_union_with_rect (clip_region, &rect); - } + /* draw text under the cursor if any */ + if (!line_display->cursor_at_line_end) + { + GdkRGBA color; - tmp_region = gdk_region_rectangle (&render_state->clip_rect); - gdk_region_intersect (clip_region, tmp_region); - gdk_region_destroy (tmp_region); + state = gtk_widget_get_state_flags (text_renderer->widget); + gtk_style_context_get_background_color (context, state, &color); - g_free (ranges); - return clip_region; -} + gdk_cairo_set_source_rgba (cr, &color); -static void -get_item_properties (PangoItem *item, - GtkTextAppearance **appearance) -{ - GSList *tmp_list = item->analysis.extra_attrs; + text_renderer_set_state (text_renderer, CURSOR); - *appearance = NULL; - - while (tmp_list) - { - PangoAttribute *attr = tmp_list->data; + pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer), + line, + line_rect.x, + baseline); + } - if (attr->klass->type == gtk_text_attr_appearance_type) - { - *appearance = &((GtkTextAttrAppearance *)attr)->appearance; + cairo_restore (cr); + } } - tmp_list = tmp_list->next; + byte_offset += line->length; } + while (pango_layout_iter_next_line (iter)); + + pango_layout_iter_free (iter); +} + +static GtkTextRenderer * +get_text_renderer (void) +{ + static GtkTextRenderer *text_renderer = NULL; + + if (!text_renderer) + text_renderer = g_object_new (GTK_TYPE_TEXT_RENDERER, NULL); + + return text_renderer; } void gtk_text_layout_draw (GtkTextLayout *layout, GtkWidget *widget, - GdkDrawable *drawable, - GdkGC *cursor_gc, - /* Location of the drawable - in layout coordinates */ - gint x_offset, - gint y_offset, - /* Region of the layout to - render */ - gint x, - gint y, - gint width, - gint height, - /* widgets to expose */ + cairo_t *cr, GList **widgets) { - GdkRectangle clip; - gint current_y; - GSList *cursor_list; - GtkTextRenderState *render_state; + GtkStyleContext *context; + gint offset_y; + GtkTextRenderer *text_renderer; GtkTextIter selection_start, selection_end; - gboolean have_selection = FALSE; + gboolean have_selection; GSList *line_list; GSList *tmp_list; - + GList *tmp_widgets; + GdkRectangle clip; + g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout)); g_return_if_fail (layout->default_style != NULL); g_return_if_fail (layout->buffer != NULL); - g_return_if_fail (drawable != NULL); - g_return_if_fail (width >= 0); - g_return_if_fail (height >= 0); + g_return_if_fail (cr != NULL); - if (width == 0 || height == 0) + if (!gdk_cairo_get_clip_rectangle (cr, &clip)) return; - line_list = gtk_text_layout_get_lines (layout, y + y_offset, y + y_offset + height, ¤t_y); - current_y -= y_offset; + context = gtk_widget_get_style_context (widget); + + line_list = gtk_text_layout_get_lines (layout, clip.y, clip.y + clip.height, &offset_y); if (line_list == NULL) return; /* nothing on the screen */ - clip.x = x; - clip.y = y; - clip.width = width; - clip.height = height; + text_renderer = get_text_renderer (); + text_renderer_begin (text_renderer, widget, cr); - render_state = gtk_text_render_state_new (widget, drawable, &clip); - - gdk_gc_set_clip_rectangle (render_state->fg_gc, &clip); - gdk_gc_set_clip_rectangle (render_state->bg_gc, &clip); + /* text_renderer_begin/end does cairo_save/restore */ + cairo_translate (cr, 0, offset_y); gtk_text_layout_wrap_loop_start (layout); - if (gtk_text_buffer_get_selection_bounds (layout->buffer, - &selection_start, - &selection_end)) - have_selection = TRUE; + have_selection = gtk_text_buffer_get_selection_bounds (layout->buffer, + &selection_start, + &selection_end); tmp_list = line_list; while (tmp_list != NULL) @@ -766,8 +864,6 @@ gtk_text_layout_draw (GtkTextLayout *layout, GtkTextLineDisplay *line_display; gint selection_start_index = -1; gint selection_end_index = -1; - gboolean have_strong; - gboolean have_weak; GtkTextLine *line = tmp_list->data; @@ -786,96 +882,62 @@ gtk_text_layout_draw (GtkTextLayout *layout, &line_start, line, 0); line_end = line_start; - gtk_text_iter_forward_to_line_end (&line_end); - byte_count = gtk_text_iter_get_line_index (&line_end); + if (!gtk_text_iter_ends_line (&line_end)) + gtk_text_iter_forward_to_line_end (&line_end); + byte_count = gtk_text_iter_get_visible_line_index (&line_end); if (gtk_text_iter_compare (&selection_start, &line_end) <= 0 && gtk_text_iter_compare (&selection_end, &line_start) >= 0) { if (gtk_text_iter_compare (&selection_start, &line_start) >= 0) - selection_start_index = gtk_text_iter_get_line_index (&selection_start); + selection_start_index = gtk_text_iter_get_visible_line_index (&selection_start); else selection_start_index = -1; if (gtk_text_iter_compare (&selection_end, &line_end) <= 0) - selection_end_index = gtk_text_iter_get_line_index (&selection_end); + selection_end_index = gtk_text_iter_get_visible_line_index (&selection_end); else - selection_end_index = byte_count; + selection_end_index = byte_count + 1; /* + 1 to flag past-the-end */ } } - render_para (drawable, render_state, line_display, - - x_offset, - current_y, - selection_start_index, selection_end_index, - widgets); + render_para (text_renderer, line_display, + selection_start_index, selection_end_index); /* We paint the cursors last, because they overlap another chunk - and need to appear on top. */ - - have_strong = FALSE; - have_weak = FALSE; - - cursor_list = line_display->cursors; - while (cursor_list) - { - GtkTextCursorDisplay *cursor = cursor_list->data; - if (cursor->is_strong) - have_strong = TRUE; - else - have_weak = TRUE; - - cursor_list = cursor_list->next; - } - - cursor_list = line_display->cursors; - while (cursor_list) + * and need to appear on top. + */ + if (line_display->cursors != NULL) { - GtkTextCursorDisplay *cursor = cursor_list->data; - GtkTextDirection dir; - GdkRectangle cursor_location; - - GdkGC *gc; - - if (cursor->is_strong) - gc = cursor_gc; - else - gc = widget->style->text_gc[GTK_STATE_NORMAL]; - - if (have_strong && have_weak) - { - dir = line_display->direction; - if (!cursor->is_strong) - dir = (dir == GTK_TEXT_DIR_RTL) ? GTK_TEXT_DIR_LTR : GTK_TEXT_DIR_RTL; - } - else - { - dir = GTK_TEXT_DIR_NONE; - } - - cursor_location.x = line_display->x_offset + cursor->x - x_offset; - cursor_location.y = current_y + line_display->top_margin + cursor->y; - cursor_location.width = 0; - cursor_location.height = cursor->height; - - gdk_gc_set_clip_rectangle(gc, &clip); - _gtk_draw_insertion_cursor (widget, drawable, gc, &cursor_location, dir); - gdk_gc_set_clip_rectangle (gc, NULL); + int i; - cursor_list = cursor_list->next; + for (i = 0; i < line_display->cursors->len; i++) + { + int index; + PangoDirection dir; + + index = g_array_index(line_display->cursors, int, i); + dir = (line_display->direction == GTK_TEXT_DIR_RTL) ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR; + gtk_render_insertion_cursor (context, cr, + line_display->x_offset, line_display->top_margin, + line_display->layout, index, dir); + } } } /* line_display->height > 0 */ - - current_y += line_display->height; + + cairo_translate (cr, 0, line_display->height); gtk_text_layout_free_line_display (layout, line_display); - render_state->last_appearance = NULL; - render_state->last_bg_appearance = NULL; tmp_list = g_slist_next (tmp_list); } gtk_text_layout_wrap_loop_end (layout); - gtk_text_render_state_destroy (render_state); + + tmp_widgets = text_renderer_end (text_renderer); + if (widgets) + *widgets = tmp_widgets; + else + g_list_free_full (tmp_widgets, g_object_unref); g_slist_free (line_list); }