X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtktextdisplay.c;h=24cc0ec6b4850970c08c3b46f341184232b7b328;hb=ca2368dba958d6fecf2fce417307bb9cbbe6eb6b;hp=d5cd2ed336a9e2c4b725396b51f4db93cdfecc60;hpb=244996ffec3b76c4cbef2e9684c0557ffed7e8fa;p=~andy%2Fgtk diff --git a/gtk/gtktextdisplay.c b/gtk/gtktextdisplay.c index d5cd2ed33..24cc0ec6b 100644 --- a/gtk/gtktextdisplay.c +++ b/gtk/gtktextdisplay.c @@ -5,12 +5,31 @@ * Copyright (c) 2000 Red Hat, Inc. * Tk->Gtk port by Havoc Pennington * + * This file can be used under your choice of two licenses, the LGPL + * and the original Tk license. + * + * LGPL: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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, see .Free + * + * Original Tk license: * * This software is copyrighted by the Regents of the University of * California, Sun Microsystems, Inc., and other parties. The * following terms apply to all files associated with the software * unless explicitly disclaimed in individual files. - * + * * The authors hereby grant permission to use, copy, modify, * distribute, and license this software and its documentation for any * purpose, provided that existing copyright notices are retained in @@ -20,13 +39,13 @@ * software may be copyrighted by their authors and need not follow * the licensing terms described here, provided that the new terms are * clearly indicated on the first page of each file where they apply. - * + * * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL * DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, * OR ANY DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. - * + * * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND @@ -45,560 +64,800 @@ * foregoing, the authors grant the U.S. Government and others acting * in its behalf permission to use and distribute the software in * accordance with the terms specified in this license. - * + * + */ +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ +#define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API +#include "config.h" #include "gtktextdisplay.h" -#include "gtktextiterprivate.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 _GtkTextRenderState GtkTextRenderState; +typedef struct _GtkTextRenderer GtkTextRenderer; +typedef struct _GtkTextRendererClass GtkTextRendererClass; + +enum { + NORMAL, + SELECTED, + CURSOR +}; -struct _GtkTextRenderState +struct _GtkTextRenderer { + PangoRenderer parent_instance; + GtkWidget *widget; + cairo_t *cr; - GtkTextAppearance *last_appearance; - GdkGC *fg_gc; - GdkGC *bg_gc; - GdkRectangle clip_rect; + GdkRGBA *error_color; /* Error underline color for this widget */ + GList *widgets; /* widgets encountered when drawing */ + + 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); - - g_free (state); + PangoRenderer *renderer = PANGO_RENDERER (text_renderer); + PangoColor dummy = { 0, }; + + 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; - if (!state->last_appearance || - new_appearance->fg_stipple != state->last_appearance->fg_stipple) + text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_BACKGROUND, bg_rgba); + + 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; + + gtk_style_context_get (context, state, "color", &fg_rgba, NULL); } - - if (new_appearance->draw_bg) + else if (text_renderer->state == CURSOR && gtk_widget_has_focus (text_renderer->widget)) { - if (!state->last_appearance || - !gdk_color_equal (&new_appearance->bg_color, &state->last_appearance->bg_color)) - gtk_text_render_state_set_color (state, state->bg_gc, &new_appearance->bg_color); - - if (!state->last_appearance || - new_appearance->bg_stipple != state->last_appearance->bg_stipple) - { - if (new_appearance->bg_stipple) + 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 (appearance->underline == PANGO_UNDERLINE_ERROR) + { + if (!text_renderer->error_color) + { + GdkColor *color = NULL; + + gtk_style_context_get_style (context, + "error-underline-color", &color, + NULL); + + if (color) { - gdk_gc_set_fill(state->bg_gc, GDK_STIPPLED); - gdk_gc_set_stipple(state->bg_gc, new_appearance->bg_stipple); + 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 { - gdk_gc_set_fill(state->bg_gc, GDK_SOLID); + static const GdkRGBA red = { 1, 0, 0, 1 }; + text_renderer->error_color = gdk_rgba_copy (&red); } - } + } + + 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 -render_layout_line (GdkDrawable *drawable, - GtkTextRenderState *render_state, - PangoLayoutLine *line, - GSList **pixbuf_pointer, - int x, - int y, - gboolean selected) +static void +set_color (GtkTextRenderer *text_renderer, + PangoRenderPart part) { - GSList *tmp_list = line->runs; - PangoRectangle overall_rect; - PangoRectangle logical_rect; - PangoRectangle ink_rect; - - gint x_off = 0; + cairo_save (text_renderer->cr); - pango_layout_line_get_extents (line, NULL, &overall_rect); + if (text_renderer->rgba_set[part]) + gdk_cairo_set_source_rgba (text_renderer->cr, &text_renderer->rgba[part]); +} - while (tmp_list) - { - PangoLayoutRun *run = tmp_list->data; - GtkTextAppearance *appearance; - - tmp_list = tmp_list->next; +static void +unset_color (GtkTextRenderer *text_renderer) +{ + cairo_restore (text_renderer->cr); +} - get_item_properties (run->item, &appearance); +static void +gtk_text_renderer_draw_glyphs (PangoRenderer *renderer, + PangoFont *font, + PangoGlyphString *glyphs, + int x, + int y) +{ + GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); - if (appearance) /* A text segment */ - { - GdkGC *fg_gc; - - if (selected) - { - fg_gc = render_state->widget->style->fg_gc[GTK_STATE_SELECTED]; - } - else - { - gtk_text_render_state_update (render_state, appearance); + set_color (text_renderer, PANGO_RENDER_PART_FOREGROUND); - fg_gc = render_state->fg_gc; - } - - if (appearance->underline == PANGO_UNDERLINE_NONE && !appearance->strikethrough) - pango_glyph_string_extents (run->glyphs, run->item->analysis.font, - NULL, &logical_rect); - else - pango_glyph_string_extents (run->glyphs, run->item->analysis.font, - &ink_rect, &logical_rect); + cairo_move_to (text_renderer->cr, (double)x / PANGO_SCALE, (double)y / PANGO_SCALE); + pango_cairo_show_glyph_string (text_renderer->cr, font, glyphs); - if (appearance->draw_bg && !selected) - gdk_draw_rectangle (drawable, render_state->bg_gc, TRUE, - x + (x_off + logical_rect.x) / PANGO_SCALE, - y + logical_rect.y / PANGO_SCALE, - logical_rect.width / PANGO_SCALE, - logical_rect.height / PANGO_SCALE); + unset_color (text_renderer); +} - gdk_draw_glyphs (drawable, fg_gc, - run->item->analysis.font, - x + x_off / PANGO_SCALE, y, run->glyphs); +static void +gtk_text_renderer_draw_glyph_item (PangoRenderer *renderer, + const char *text, + PangoGlyphItem *glyph_item, + int x, + int y) +{ + GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); - switch (appearance->underline) - { - case PANGO_UNDERLINE_NONE: - break; - case PANGO_UNDERLINE_DOUBLE: - gdk_draw_line (drawable, fg_gc, - x + (x_off + ink_rect.x) / PANGO_SCALE - 1, y + 4, - x + (x_off + ink_rect.x + ink_rect.width) / PANGO_SCALE, y + 4); - /* Fall through */ - case PANGO_UNDERLINE_SINGLE: - gdk_draw_line (drawable, fg_gc, - x + (x_off + ink_rect.x) / PANGO_SCALE - 1, y + 2, - x + (x_off + ink_rect.x + ink_rect.width) / PANGO_SCALE, y + 2); - break; - case PANGO_UNDERLINE_LOW: - gdk_draw_line (drawable, fg_gc, - x + (x_off + ink_rect.x) / PANGO_SCALE - 1, y + (ink_rect.y + ink_rect.height) / PANGO_SCALE + 2, - x + (x_off + ink_rect.x + ink_rect.width) / PANGO_SCALE, y + (ink_rect.y + ink_rect.height) / PANGO_SCALE + 2); - break; - } + set_color (text_renderer, PANGO_RENDER_PART_FOREGROUND); - if (appearance->strikethrough) - { - gint strikethrough_y = y + (0.3 * logical_rect.y) / PANGO_SCALE; - 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_move_to (text_renderer->cr, (double)x / PANGO_SCALE, (double)y / PANGO_SCALE); + pango_cairo_show_glyph_item (text_renderer->cr, text, glyph_item); - x_off += logical_rect.width; - } - else /* Pixbuf segment */ - { - GtkTextPixbuf *pixbuf = (*pixbuf_pointer)->data; - gint width, height; - GdkRectangle pixbuf_rect, draw_rect; - GdkBitmap *mask = NULL; - - *pixbuf_pointer = (*pixbuf_pointer)->next; + unset_color (text_renderer); +} - width = gdk_pixbuf_get_width (pixbuf->pixbuf); - height = gdk_pixbuf_get_height (pixbuf->pixbuf); +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); - pixbuf_rect.x = x + x_off / PANGO_SCALE; - pixbuf_rect.y = y - height; - pixbuf_rect.width = width; - pixbuf_rect.height = height; - - if (gdk_rectangle_intersect (&pixbuf_rect, &render_state->clip_rect, - &draw_rect)) - { - if (gdk_pixbuf_get_has_alpha (pixbuf->pixbuf)) - { - mask = gdk_pixmap_new (drawable, - gdk_pixbuf_get_width (pixbuf->pixbuf), - gdk_pixbuf_get_height (pixbuf->pixbuf), - 1); - - gdk_pixbuf_render_threshold_alpha (pixbuf->pixbuf, mask, - 0, 0, 0, 0, - gdk_pixbuf_get_width (pixbuf->pixbuf), - gdk_pixbuf_get_height (pixbuf->pixbuf), - 128); - - } + set_color (text_renderer, part); - 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->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)); - } - } - - x_off += width * PANGO_SCALE; - } - } + 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 -render_para (GdkDrawable *drawable, - GtkTextRenderState *render_state, - GtkTextLineDisplay *line_display, - int x, - int y, - int selection_start_index, - int selection_end_index) +static void +gtk_text_renderer_draw_trapezoid (PangoRenderer *renderer, + PangoRenderPart part, + double y1_, + double x11, + double x21, + double y2, + double x12, + double x22) { - PangoRectangle logical_rect; - GSList *pixbuf_pointer = line_display->pixbufs; - GSList *tmp_list; - PangoAlignment align; - PangoLayout *layout = line_display->layout; - int indent; - int total_width; - int y_offset = 0; - int byte_offset = 0; + GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); + cairo_t *cr; + cairo_matrix_t matrix; - gboolean first = TRUE; - - indent = pango_layout_get_indent (layout); - total_width = pango_layout_get_width (layout); - align = pango_layout_get_alignment (layout); + set_color (text_renderer, part); - if (total_width < 0) - total_width = line_display->total_width * PANGO_SCALE; + cr = text_renderer->cr; - tmp_list = pango_layout_get_lines (layout); - while (tmp_list) + 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) { - PangoLayoutLine *line = tmp_list->data; - int x_offset; - int selection_y, selection_height; + /* This happens if we have an empty widget anchor. Draw + * something empty-looking. + */ + GdkRectangle shape_rect; + cairo_t *cr; + + 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; + + 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); - pango_layout_line_get_extents (line, NULL, &logical_rect); - - x_offset = line_display->left_margin * PANGO_SCALE; - - switch (align) - { - case PANGO_ALIGN_RIGHT: - x_offset += total_width - logical_rect.width; - break; - case PANGO_ALIGN_CENTER: - x_offset += (total_width - logical_rect.width) / 2; - break; - default: - break; - } + cairo_save (cr); - if (first) - { - if (indent > 0) - { - if (align == PANGO_ALIGN_LEFT) - x_offset += indent; - else - x_offset -= indent; - } - } - else - { - if (indent < 0) - { - if (align == PANGO_ALIGN_LEFT) - x_offset -= indent; - else - x_offset += indent; - } - } + gdk_cairo_set_source_pixbuf (cr, pixbuf, + PANGO_PIXELS (x), + PANGO_PIXELS (y) - gdk_pixbuf_get_height (pixbuf)); + cairo_paint (cr); - selection_y = y + y_offset / PANGO_SCALE; - selection_height = logical_rect.height / PANGO_SCALE; + cairo_restore (cr); + } + else if (GTK_IS_WIDGET (attr->data)) + { + GtkWidget *widget; + + widget = GTK_WIDGET (attr->data); - if (first) - { - selection_y -= line_display->top_margin; - selection_height += line_display->top_margin; - } - if (!tmp_list->next) - selection_height += line_display->bottom_margin; + text_renderer->widgets = g_list_prepend (text_renderer->widgets, + g_object_ref (widget)); + } + else + g_assert_not_reached (); /* not a pixbuf or widget */ +} - first = FALSE; - - if (selection_start_index < byte_offset && - selection_end_index > line->length + byte_offset) /* All selected */ - { - gdk_draw_rectangle (drawable, - render_state->widget->style->bg_gc[GTK_STATE_SELECTED], - TRUE, - x + line_display->left_margin, selection_y, - total_width / PANGO_SCALE, selection_height); - render_layout_line (drawable, render_state, line, &pixbuf_pointer, - x + x_offset / PANGO_SCALE, y + (y_offset - logical_rect.y) / PANGO_SCALE, - TRUE); - } - else - { - GSList *pixbuf_pointer_tmp = pixbuf_pointer; - - render_layout_line (drawable, render_state, line, &pixbuf_pointer, - x + x_offset / PANGO_SCALE, y + (y_offset - logical_rect.y) / PANGO_SCALE, - FALSE); - - if (selection_start_index < byte_offset + line->length && - selection_end_index > byte_offset) /* Some selected */ - { - GdkRegion *clip_region = get_selected_clip (render_state, layout, line, - x + line_display->x_offset, - selection_y, selection_height, - selection_start_index, selection_end_index); - - gdk_gc_set_clip_region (render_state->widget->style->fg_gc [GTK_STATE_SELECTED], clip_region); - gdk_gc_set_clip_region (render_state->widget->style->bg_gc [GTK_STATE_SELECTED], clip_region); - - gdk_draw_rectangle (drawable, - render_state->widget->style->bg_gc[GTK_STATE_SELECTED], - TRUE, - x + x_offset / PANGO_SCALE, selection_y, - logical_rect.width / PANGO_SCALE, - selection_height); - - render_layout_line (drawable, render_state, line, &pixbuf_pointer_tmp, - x + x_offset / PANGO_SCALE, y + (y_offset - logical_rect.y) / PANGO_SCALE, - TRUE); - - gdk_gc_set_clip_region (render_state->widget->style->fg_gc [GTK_STATE_SELECTED], NULL); - gdk_gc_set_clip_region (render_state->widget->style->bg_gc [GTK_STATE_SELECTED], NULL); - - gdk_region_destroy (clip_region); - - /* Paint in the ends of the line */ - if (x_offset > 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, - render_state->widget->style->bg_gc[GTK_STATE_SELECTED], - TRUE, - x + line_display->left_margin, selection_y, - x + x_offset / PANGO_SCALE - line_display->left_margin, - selection_height); - } - - if (x_offset + logical_rect.width < line_display->left_margin * PANGO_SCALE + total_width && - ((line_display->direction == GTK_TEXT_DIR_LTR && selection_end_index > byte_offset + line->length) || - (line_display->direction == GTK_TEXT_DIR_RTL && selection_start_index < byte_offset))) - { - - - gdk_draw_rectangle (drawable, - render_state->widget->style->bg_gc[GTK_STATE_SELECTED], - TRUE, - x + (x_offset + logical_rect.width) / PANGO_SCALE, - selection_y, - x + (line_display->left_margin * PANGO_SCALE + total_width - x_offset - logical_rect.width) / PANGO_SCALE, - selection_height); - } - } - } +static void +gtk_text_renderer_finalize (GObject *object) +{ + G_OBJECT_CLASS (_gtk_text_renderer_parent_class)->finalize (object); +} - byte_offset += line->length; - y_offset += logical_rect.height; - tmp_list = tmp_list->next; +static void +_gtk_text_renderer_init (GtkTextRenderer *renderer) +{ +} + +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; +} + +static void +text_renderer_set_state (GtkTextRenderer *text_renderer, + int state) +{ + text_renderer->state = state; +} + +static void +text_renderer_begin (GtkTextRenderer *text_renderer, + GtkWidget *widget, + cairo_t *cr) +{ + GtkStyleContext *context; + GtkStateFlags state; + GdkRGBA color; + + text_renderer->widget = widget; + text_renderer->cr = cr; + + context = gtk_widget_get_style_context (widget); + + gtk_style_context_save (context); + gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW); + + state = gtk_widget_get_state_flags (widget); + gtk_style_context_get_color (context, state, &color); + + cairo_save (cr); + + 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 GdkRegion * -get_selected_clip (GtkTextRenderState *render_state, - PangoLayout *layout, - PangoLayoutLine *line, - int x, - int y, - int height, - int start_index, - int end_index) +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; - GdkRegion *clip_region = gdk_region_new (); - GdkRegion *tmp_region; - PangoRectangle logical_rect; + cairo_region_t *clip_region = cairo_region_create (); - pango_layout_line_get_extents (line, NULL, &logical_rect); 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 + ranges[2*i] / PANGO_SCALE; + rect.x = x + PANGO_PIXELS (ranges[2*i]); rect.y = y; - rect.width = (ranges[2*i + 1] - ranges[2*i]) / PANGO_SCALE; + rect.width = PANGO_PIXELS (ranges[2*i + 1]) - PANGO_PIXELS (ranges[2*i]); rect.height = height; - - gdk_region_union_with_rect (clip_region, &rect); + + cairo_region_union_rectangle (clip_region, &rect); } - tmp_region = gdk_region_rectangle (&render_state->clip_rect); - gdk_region_intersect (clip_region, tmp_region); - gdk_region_destroy (tmp_region); - g_free (ranges); return clip_region; } static void -get_item_properties (PangoItem *item, - GtkTextAppearance **appearance) +render_para (GtkTextRenderer *text_renderer, + GtkTextLineDisplay *line_display, + int selection_start_index, + int selection_end_index) { - GSList *tmp_list = item->extra_attrs; - - *appearance = NULL; + GtkStyleContext *context; + GtkStateFlags state; + PangoLayout *layout = line_display->layout; + int byte_offset = 0; + PangoLayoutIter *iter; + PangoRectangle layout_logical; + int screen_width; + GdkRGBA selection; + gboolean first = TRUE; + + iter = pango_layout_get_iter (layout); + + pango_layout_iter_get_layout_extents (iter, NULL, &layout_logical); + + /* Adjust for margins */ - while (tmp_list) + layout_logical.x += line_display->x_offset * PANGO_SCALE; + layout_logical.y += line_display->top_margin * PANGO_SCALE; + + screen_width = line_display->total_width; + + 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 { - PangoAttribute *attr = tmp_list->data; + 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); + pango_layout_iter_get_line_yrange (iter, &first_y, &last_y); + + /* Adjust for margins */ - if (attr->klass->type == gtk_text_attr_appearance_type) - { - *appearance = &((GtkTextAttrAppearance *)attr)->appearance; - } + line_rect.x += line_display->x_offset * PANGO_SCALE; + line_rect.y += line_display->top_margin * PANGO_SCALE; + baseline += line_display->top_margin * PANGO_SCALE; - tmp_list = tmp_list->next; + /* Selection is the height of the line, plus top/bottom + * margin if we're the first/last line + */ + selection_y = PANGO_PIXELS (first_y) + line_display->top_margin; + selection_height = PANGO_PIXELS (last_y) - PANGO_PIXELS (first_y); + + if (first) + { + selection_y -= line_display->top_margin; + selection_height += line_display->top_margin; + } + + at_last_line = pango_layout_iter_at_last_line (iter); + if (at_last_line) + selection_height += line_display->bottom_margin; + + first = FALSE; + + if (selection_start_index < byte_offset && + selection_end_index > line->length + byte_offset) /* All selected */ + { + 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 + { + if (line_display->pg_bg_rgba) + { + cairo_t *cr = text_renderer->cr; + + 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); + + 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) + { + 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); + + cairo_save (cr); + gdk_cairo_region (cr, clip_region); + cairo_clip (cr); + cairo_region_destroy (clip_region); + + 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); + + text_renderer_set_state (text_renderer, SELECTED); + pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer), + line, + line_rect.x, + baseline); + + 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))) + { + 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 < + (screen_width + line_display->left_margin) * PANGO_SCALE && + ((line_display->direction == GTK_TEXT_DIR_LTR && selection_end_index > byte_offset + line->length) || + (line_display->direction == GTK_TEXT_DIR_RTL && selection_start_index < byte_offset))) + { + int nonlayout_width; + + nonlayout_width = + line_display->left_margin + screen_width - + PANGO_PIXELS (line_rect.x) - PANGO_PIXELS (line_rect.width); + + 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; + + /* 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); + + 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; + + cairo_save (cr); + + gdk_cairo_rectangle (cr, &cursor_rect); + cairo_clip (cr); + + gdk_cairo_set_source_rgba (cr, &cursor_color); + cairo_paint (cr); + + /* draw text under the cursor if any */ + if (!line_display->cursor_at_line_end) + { + GdkRGBA color; + + state = gtk_widget_get_state_flags (text_renderer->widget); + gtk_style_context_get_background_color (context, state, &color); + + gdk_cairo_set_source_rgba (cr, &color); + + text_renderer_set_state (text_renderer, CURSOR); + + pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer), + line, + line_rect.x, + baseline); + } + + cairo_restore (cr); + } + } + + 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, - /* Location of the layout - in buffer coordinates */ - gint x_offset, - gint y_offset, - /* Region of the layout to - render */ - gint x, - gint y, - gint width, - gint height) + GtkWidget *widget, + cairo_t *cr, + GList **widgets) { - GdkRectangle clip; - gint current_y; + GtkStyleContext *context; + gint offset_y; + GtkTextRenderer *text_renderer; + GtkTextIter selection_start, selection_end; + gboolean have_selection; GSList *line_list; GSList *tmp_list; - GSList *cursor_list; - GtkTextRenderState *render_state; - GtkTextIter selection_start, selection_end; - gboolean have_selection = FALSE; - + 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 (x_offset >= 0); - g_return_if_fail (y_offset >= 0); - 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; - - render_state = gtk_text_render_state_new (widget, drawable, &clip); + text_renderer = get_text_renderer (); + text_renderer_begin (text_renderer, widget, cr); - 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) { @@ -607,73 +866,78 @@ gtk_text_layout_draw (GtkTextLayout *layout, gint selection_end_index = -1; GtkTextLine *line = tmp_list->data; - + line_display = gtk_text_layout_get_line_display (layout, line, FALSE); - if (have_selection) - { - GtkTextIter line_start, line_end; - gint byte_count = gtk_text_line_byte_count (line); - - gtk_text_btree_get_iter_at_line (_gtk_text_buffer_get_btree (layout->buffer), - &line_start, - line, 0); - gtk_text_btree_get_iter_at_line (_gtk_text_buffer_get_btree (layout->buffer), - &line_end, - line, byte_count - 1); - - 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); - 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); - else - selection_end_index = byte_count; - } - } - - render_para (drawable, render_state, line_display, - - x_offset, - current_y + line_display->top_margin, - selection_start_index, selection_end_index); + if (line_display->height > 0) + { + g_assert (line_display->layout != NULL); + + if (have_selection) + { + GtkTextIter line_start, line_end; + gint byte_count; + + gtk_text_layout_get_iter_at_line (layout, + &line_start, + line, 0); + line_end = line_start; + 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_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_visible_line_index (&selection_end); + else + selection_end_index = byte_count + 1; /* + 1 to flag past-the-end */ + } + } - - /* We paint the cursors last, because they overlap another chunk - and need to appear on top. */ - - cursor_list = line_display->cursors; - while (cursor_list) - { - GtkTextCursorDisplay *cursor = cursor_list->data; - GdkGC *gc; - - if (cursor->is_strong) - gc = widget->style->bg_gc[GTK_STATE_SELECTED]; - else - gc = widget->style->fg_gc[GTK_STATE_NORMAL]; - - gdk_draw_line (drawable, gc, - line_display->x_offset + cursor->x, - current_y + line_display->top_margin + cursor->y, - line_display->x_offset + cursor->x, - current_y + line_display->top_margin + cursor->y + cursor->height); - - cursor_list = cursor_list->next; - } - - current_y += line_display->height; + 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. + */ + if (line_display->cursors != NULL) + { + int i; + + 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 */ + + cairo_translate (cr, 0, line_display->height); gtk_text_layout_free_line_display (layout, line_display); 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); }