gtktextdisplay.c 30.3 KB
Newer Older
1 2 3 4 5 6 7
/* gtktextdisplay.c - display layed-out text
 *
 * Copyright (c) 1992-1994 The Regents of the University of California.
 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
 * Copyright (c) 2000 Red Hat, Inc.
 * Tk->Gtk port by Havoc Pennington
 *
8 9
 * This file can be used under your choice of two licenses, the LGPL
 * and the original Tk license.
10
 *
11
 * LGPL:
12
 *
13 14 15 16 17 18 19 20 21 22 23
 * 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
Javier Jardón's avatar
Javier Jardón committed
24
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.Free
25 26
 *
 * Original Tk license:
27 28 29 30 31
 *
 * 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.
32
 *
33 34 35 36 37 38 39 40 41
 * 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
 * all copies and that this notice is included verbatim in any
 * distributions. No written agreement, license, or royalty fee is
 * required for any of the authorized uses.  Modifications to this
 * 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.
42
 *
43 44 45 46 47
 * 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.
48
 *
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
 * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
 * NON-INFRINGEMENT.  THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
 * AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE
 * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 * GOVERNMENT USE: If you are acquiring this software on behalf of the
 * U.S. government, the Government shall have only "Restricted Rights"
 * in the software and related documentation as defined in the Federal
 * Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
 * are acquiring the software on behalf of the Department of Defense,
 * the software shall be classified as "Commercial Computer Software"
 * and the Government shall have only "Restricted Rights" as defined
 * in Clause 252.227-7013 (c) (1) of DFARs.  Notwithstanding the
 * 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.
67 68 69 70 71 72 73
 *
 */
/*
 * 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/.
74 75
 */

76
#define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API
77
#include "config.h"
78
#include "gtktextdisplay.h"
79
#include "gtkwidgetprivate.h"
80
#include "gtkstylecontextprivate.h"
81
#include "gtkintl.h"
82

83 84 85
/* DO NOT go putting private headers in here. This file should only
 * use the semi-public headers, as with gtktextview.c.
 */
86

Matthias Clasen's avatar
Matthias Clasen committed
87
#define GTK_TYPE_TEXT_RENDERER            (_gtk_text_renderer_get_type())
88 89 90 91 92
#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))
93

94 95
typedef struct _GtkTextRenderer      GtkTextRenderer;
typedef struct _GtkTextRendererClass GtkTextRendererClass;
96

97 98 99 100 101 102
enum {
  NORMAL,
  SELECTED,
  CURSOR
};

103
struct _GtkTextRenderer
104
{
105
  PangoRenderer parent_instance;
106 107

  GtkWidget *widget;
108
  cairo_t *cr;
109
  
110
  GdkRGBA *error_color;	/* Error underline color for this widget */
111
  GList *widgets;      	/* widgets encountered when drawing */
112 113 114 115 116

  GdkRGBA rgba[4];
  guint8  rgba_set[4];

  guint state : 2;
117 118
};

119
struct _GtkTextRendererClass
120
{
121
  PangoRendererClass parent_class;
122
};
123

124
G_DEFINE_TYPE (GtkTextRenderer, _gtk_text_renderer, PANGO_TYPE_RENDERER)
125

126 127 128
static void
text_renderer_set_rgba (GtkTextRenderer *text_renderer,
			PangoRenderPart  part,
129
			const GdkRGBA   *rgba)
130 131
{
  PangoRenderer *renderer = PANGO_RENDERER (text_renderer);
132 133
  PangoColor     dummy = { 0, };

134 135
  if (rgba)
    {
136 137
      text_renderer->rgba[part] = *rgba;
      pango_renderer_set_color (renderer, part, &dummy);
138 139
    }
  else
140
    pango_renderer_set_color (renderer, part, NULL);
141

142
  text_renderer->rgba_set[part] = (rgba != NULL);
143 144
}

145 146
static GtkTextAppearance *
get_item_appearance (PangoItem *item)
147
{
148 149 150 151 152 153 154 155 156 157 158 159 160
  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;
161 162 163
}

static void
164 165
gtk_text_renderer_prepare_run (PangoRenderer  *renderer,
			       PangoLayoutRun *run)
166
{
167 168
  GtkStyleContext *context;
  GtkStateFlags state;
169
  GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer);
170
  GdkRGBA *bg_rgba = NULL;
171
  GdkRGBA *fg_rgba = NULL;
172 173
  GtkTextAppearance *appearance;

Matthias Clasen's avatar
Matthias Clasen committed
174
  PANGO_RENDERER_CLASS (_gtk_text_renderer_parent_class)->prepare_run (renderer, run);
175 176 177

  appearance = get_item_appearance (run->item);
  g_assert (appearance != NULL);
178

179
  context = gtk_widget_get_style_context (text_renderer->widget);
180
  state   = gtk_widget_get_state_flags (text_renderer->widget);
181

182
  if (appearance->draw_bg && text_renderer->state == NORMAL)
183
    bg_rgba = appearance->rgba[0];
184
  else
185
    bg_rgba = NULL;
186
  
187
  text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_BACKGROUND, bg_rgba);
188

189
  if (text_renderer->state == SELECTED)
190
    {
191 192
      state |= GTK_STATE_FLAG_SELECTED;

193
      gtk_style_context_get (context, state, "color", &fg_rgba, NULL);
194
    }
195
  else if (text_renderer->state == CURSOR && gtk_widget_has_focus (text_renderer->widget))
196 197 198 199 200
    {
      gtk_style_context_get (context, state,
                             "background-color", &fg_rgba,
                              NULL);
    }
201
  else
202
    fg_rgba = appearance->rgba[1];
203

204
  text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_FOREGROUND, fg_rgba);
205
  text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_STRIKETHROUGH, fg_rgba);
206

207
  if (appearance->underline == PANGO_UNDERLINE_ERROR)
208 209 210
    {
      if (!text_renderer->error_color)
        {
211 212
	  GdkColor *color = NULL;

213
          gtk_style_context_get_style (context,
214
                                       "error-underline-color", &color,
215 216
                                       NULL);

217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
	  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);
	    }
234
        }
235

236
      text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_UNDERLINE, text_renderer->error_color);
237 238
    }
  else
239 240 241 242
    text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_UNDERLINE, fg_rgba);

  if (fg_rgba != appearance->rgba[1])
    gdk_rgba_free (fg_rgba);
243 244
}

245 246 247 248 249 250
static void
set_color (GtkTextRenderer *text_renderer,
           PangoRenderPart  part)
{
  cairo_save (text_renderer->cr);

251 252
  if (text_renderer->rgba_set[part])
    gdk_cairo_set_source_rgba (text_renderer->cr, &text_renderer->rgba[part]);
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
}

static void
unset_color (GtkTextRenderer *text_renderer)
{
  cairo_restore (text_renderer->cr);
}

static void
gtk_text_renderer_draw_glyphs (PangoRenderer     *renderer,
                               PangoFont         *font,
                               PangoGlyphString  *glyphs,
                               int                x,
                               int                y)
{
  GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer);

  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);

  unset_color (text_renderer);
}

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);

  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);
}

367 368 369 370 371
static void
gtk_text_renderer_draw_shape (PangoRenderer   *renderer,
			      PangoAttrShape  *attr,
			      int              x,
			      int              y)
372
{
373
  GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer);
374

375
  if (attr->data == NULL)
376
    {
377 378 379
      /* This happens if we have an empty widget anchor. Draw
       * something empty-looking.
       */
380 381
      GdkRectangle shape_rect;
      cairo_t *cr;
382

383 384 385 386
      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;
387

388 389 390
      set_color (text_renderer, PANGO_RENDER_PART_FOREGROUND);

      cr = text_renderer->cr;
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406

      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);
407 408

      unset_color (text_renderer);
409 410 411
    }
  else if (GDK_IS_PIXBUF (attr->data))
    {
412
      cairo_t *cr = text_renderer->cr;
413
      GdkPixbuf *pixbuf = GDK_PIXBUF (attr->data);
414
      
415
      cairo_save (cr);
416

417 418 419 420
      gdk_cairo_set_source_pixbuf (cr, pixbuf,
                                   PANGO_PIXELS (x),
                                   PANGO_PIXELS (y) -  gdk_pixbuf_get_height (pixbuf));
      cairo_paint (cr);
421

422
      cairo_restore (cr);
423
    }
424 425
  else if (GTK_IS_WIDGET (attr->data))
    {
426 427 428 429 430 431
      GtkWidget *widget;
      
      widget = GTK_WIDGET (attr->data);

      text_renderer->widgets = g_list_prepend (text_renderer->widgets,
					       g_object_ref (widget));
432 433 434
    }
  else
    g_assert_not_reached (); /* not a pixbuf or widget */
435 436
}

437
static void
438
gtk_text_renderer_finalize (GObject *object)
439
{
Matthias Clasen's avatar
Matthias Clasen committed
440
  G_OBJECT_CLASS (_gtk_text_renderer_parent_class)->finalize (object);
441
}
442

443
static void
Matthias Clasen's avatar
Matthias Clasen committed
444
_gtk_text_renderer_init (GtkTextRenderer *renderer)
445
{
446 447
}

448
static void
Matthias Clasen's avatar
Matthias Clasen committed
449
_gtk_text_renderer_class_init (GtkTextRendererClass *klass)
450
{
451
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
452
  
453 454 455
  PangoRendererClass *renderer_class = PANGO_RENDERER_CLASS (klass);
  
  renderer_class->prepare_run = gtk_text_renderer_prepare_run;
456 457 458 459 460
  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;
461
  renderer_class->draw_shape = gtk_text_renderer_draw_shape;
462

463 464
  object_class->finalize = gtk_text_renderer_finalize;
}
465

466
static void
467 468
text_renderer_set_state (GtkTextRenderer *text_renderer,
			 int              state)
469
{
470
  text_renderer->state = state;
471
}
472

473 474
static void
text_renderer_begin (GtkTextRenderer *text_renderer,
475 476
                     GtkWidget       *widget,
                     cairo_t         *cr)
477
{
478 479 480 481
  GtkStyleContext *context;
  GtkStateFlags state;
  GdkRGBA color;

482
  text_renderer->widget = widget;
483
  text_renderer->cr = cr;
484 485 486 487 488 489 490 491 492 493 494 495

  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);
496
}
497

498 499
/* Returns a GSList of (referenced) widgets encountered while drawing.
 */
500
static GList *
501 502
text_renderer_end (GtkTextRenderer *text_renderer)
{
503
  GtkStyleContext *context;
504
  GList *widgets = text_renderer->widgets;
505

506 507 508 509 510 511
  cairo_restore (text_renderer->cr);

  context = gtk_widget_get_style_context (text_renderer->widget);

  gtk_style_context_restore (context);

512
  text_renderer->widget = NULL;
513
  text_renderer->cr = NULL;
514

515 516
  text_renderer->widgets = NULL;

517 518
  if (text_renderer->error_color)
    {
519
      gdk_rgba_free (text_renderer->error_color);
520 521
      text_renderer->error_color = NULL;
    }
522 523

  return widgets;
524
}
525

526
static cairo_region_t *
527 528 529 530 531 532 533 534 535 536 537
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;
538
  cairo_region_t *clip_region = cairo_region_create ();
539

540
  pango_layout_line_get_x_ranges (line, start_index, end_index, &ranges, &n_ranges);
541

542 543 544
  for (i=0; i < n_ranges; i++)
    {
      GdkRectangle rect;
545

546 547 548 549 550
      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;
      
551
      cairo_region_union_rectangle (clip_region, &rect);
552
    }
553 554 555

  g_free (ranges);
  return clip_region;
556 557
}

558
static void
559
render_para (GtkTextRenderer    *text_renderer,
560 561
             GtkTextLineDisplay *line_display,
             int                 selection_start_index,
562
             int                 selection_end_index)
563
{
564 565
  GtkStyleContext *context;
  GtkStateFlags state;
566
  PangoLayout *layout = line_display->layout;
567
  int byte_offset = 0;
568 569 570
  PangoLayoutIter *iter;
  PangoRectangle layout_logical;
  int screen_width;
571
  GdkRGBA selection;
572
  gboolean first = TRUE;
573

574
  iter = pango_layout_get_iter (layout);
575

576
  pango_layout_iter_get_layout_extents (iter, NULL, &layout_logical);
577

578 579 580 581 582 583
  /* Adjust for margins */
  
  layout_logical.x += line_display->x_offset * PANGO_SCALE;
  layout_logical.y += line_display->top_margin * PANGO_SCALE;

  screen_width = line_display->total_width;
584 585

  context = gtk_widget_get_style_context (text_renderer->widget);
586
  state = gtk_widget_get_state_flags (text_renderer->widget);
587

588
  state |= GTK_STATE_FLAG_SELECTED;
589

590
  gtk_style_context_get_background_color (context, state, &selection);
591

592 593
  do
    {
594
      PangoLayoutLine *line = pango_layout_iter_get_line_readonly (iter);
595
      int selection_y, selection_height;
596 597 598
      int first_y, last_y;
      PangoRectangle line_rect;
      int baseline;
599
      gboolean at_last_line;
600 601 602 603 604 605 606 607 608 609 610 611 612 613
      
      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 */

      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;

      /* Selection is the height of the line, plus top/bottom
       * margin if we're the first/last line
       */
614
      selection_y = PANGO_PIXELS (first_y) + line_display->top_margin;
615
      selection_height = PANGO_PIXELS (last_y) - PANGO_PIXELS (first_y);
616 617

      if (first)
618 619 620 621
        {
          selection_y -= line_display->top_margin;
          selection_height += line_display->top_margin;
        }
622 623 624

      at_last_line = pango_layout_iter_at_last_line (iter);
      if (at_last_line)
625
        selection_height += line_display->bottom_margin;
626
      
627
      first = FALSE;
628

629
      if (selection_start_index < byte_offset &&
630 631
          selection_end_index > line->length + byte_offset) /* All selected */
        {
632 633 634
          cairo_t *cr = text_renderer->cr;

          cairo_save (cr);
635
          gdk_cairo_set_source_rgba (cr, &selection);
636
          cairo_rectangle (cr, 
637
                           line_display->left_margin, selection_y,
638 639
                           screen_width, selection_height);
          cairo_fill (cr);
640
          cairo_restore(cr);
641

642
	  text_renderer_set_state (text_renderer, SELECTED);
643 644
	  pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer),
					   line, 
645 646
					   line_rect.x,
					   baseline);
647
        }
648
      else
649
        {
650
          if (line_display->pg_bg_rgba)
651
            {
652 653 654
              cairo_t *cr = text_renderer->cr;

              cairo_save (cr);
655 656
 
	      gdk_cairo_set_source_rgba (text_renderer->cr, line_display->pg_bg_rgba);
657
              cairo_rectangle (cr, 
658
                               line_display->left_margin, selection_y,
659 660 661
                               screen_width, selection_height);
              cairo_fill (cr);

662
              cairo_restore (cr);
663 664
            }
        
665
	  text_renderer_set_state (text_renderer, NORMAL);
666 667
	  pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer),
					   line, 
668 669
					   line_rect.x,
					   baseline);
670

671 672 673 674 675 676 677
	  /* 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)
678
            {
679
              cairo_t *cr = text_renderer->cr;
680
              cairo_region_t *clip_region = get_selected_clip (text_renderer, layout, line,
681
                                                          line_display->x_offset,
682 683
                                                          selection_y,
                                                          selection_height,
684
                                                          selection_start_index, selection_end_index);
685

686
              cairo_save (cr);
687 688
              gdk_cairo_region (cr, clip_region);
              cairo_clip (cr);
689
              cairo_region_destroy (clip_region);
690

691
              gdk_cairo_set_source_rgba (cr, &selection);
692
              cairo_rectangle (cr,
693
                               PANGO_PIXELS (line_rect.x),
694 695 696 697 698
                               selection_y,
                               PANGO_PIXELS (line_rect.width),
                               selection_height);
              cairo_fill (cr);

699
	      text_renderer_set_state (text_renderer, SELECTED);
700 701
	      pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer),
					       line, 
702 703
					       line_rect.x,
					       baseline);
704

705
              cairo_restore (cr);
706 707

              /* Paint in the ends of the line */
708
              if (line_rect.x > line_display->left_margin * PANGO_SCALE &&
709 710 711
                  ((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)))
                {
712
                  cairo_save (cr);
713

714
                  gdk_cairo_set_source_rgba (cr, &selection);
715
                  cairo_rectangle (cr,
716
                                   line_display->left_margin,
717 718 719 720
                                   selection_y,
                                   PANGO_PIXELS (line_rect.x) - line_display->left_margin,
                                   selection_height);
                  cairo_fill (cr);
721 722

                  cairo_restore (cr);
723 724
                }

725 726
              if (line_rect.x + line_rect.width <
                  (screen_width + line_display->left_margin) * PANGO_SCALE &&
727 728 729
                  ((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)))
                {
730
                  int nonlayout_width;
731

732 733 734
                  nonlayout_width =
                    line_display->left_margin + screen_width -
                    PANGO_PIXELS (line_rect.x) - PANGO_PIXELS (line_rect.width);
735

736 737
                  cairo_save (cr);

738
                  gdk_cairo_set_source_rgba (cr, &selection);
739
                  cairo_rectangle (cr,
740
                                   PANGO_PIXELS (line_rect.x) + PANGO_PIXELS (line_rect.width),
741 742 743 744
                                   selection_y,
                                   nonlayout_width,
                                   selection_height);
                  cairo_fill (cr);
745 746

                  cairo_restore (cr);
747 748
                }
            }
749
	  else if (line_display->has_block_cursor &&
750
		   gtk_widget_has_focus (text_renderer->widget) &&
751 752 753 754 755
		   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;
756
              GdkRGBA cursor_color;
757
              cairo_t *cr = text_renderer->cr;
758

759 760 761
              /* 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);
762

763 764
	      cursor_rect.x = line_display->x_offset + line_display->block_cursor.x;
	      cursor_rect.y = line_display->block_cursor.y + line_display->top_margin;
765
	      cursor_rect.width = line_display->block_cursor.width;
766
	      cursor_rect.height = line_display->block_cursor.height;
767

768 769
              cairo_save (cr);

770 771
              gdk_cairo_rectangle (cr, &cursor_rect);
              cairo_clip (cr);
772

773
              gdk_cairo_set_source_rgba (cr, &cursor_color);
774
              cairo_paint (cr);
775

776 777 778 779 780 781 782
              /* 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);
783

784
                  gdk_cairo_set_source_rgba (cr, &color);
785 786 787 788 789

		  text_renderer_set_state (text_renderer, CURSOR);

		  pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer),
						   line,
790 791
						   line_rect.x,
						   baseline);
792
                }
793 794

              cairo_restore (cr);
795
	    }
796
        }
797 798 799

      byte_offset += line->length;
    }
800 801 802
  while (pango_layout_iter_next_line (iter));

  pango_layout_iter_free (iter);
803 804
}

805
static GtkTextRenderer *
806
get_text_renderer (void)
807
{
808
  static GtkTextRenderer *text_renderer = NULL;
809

810
  if (!text_renderer)
811
    text_renderer = g_object_new (GTK_TYPE_TEXT_RENDERER, NULL);
812 813

  return text_renderer;
814 815 816 817
}

void
gtk_text_layout_draw (GtkTextLayout *layout,
818
                      GtkWidget *widget,
819 820
                      cairo_t *cr,
                      GList **widgets)
821
{
822
  GtkStyleContext *context;
823
  gint offset_y;
824
  GtkTextRenderer *text_renderer;
825
  GtkTextIter selection_start, selection_end;
Paolo Borelli's avatar
Paolo Borelli committed
826
  gboolean have_selection;
827 828
  GSList *line_list;
  GSList *tmp_list;
829
  GList *tmp_widgets;
José Aliste's avatar
José Aliste committed
830
  GdkRectangle clip;
831

832 833 834
  g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
  g_return_if_fail (layout->default_style != NULL);
  g_return_if_fail (layout->buffer != NULL);
835
  g_return_if_fail (cr != NULL);
836

José Aliste's avatar
José Aliste committed
837 838
  if (!gdk_cairo_get_clip_rectangle (cr, &clip))
    return;
839

840 841
  context = gtk_widget_get_style_context (widget);

842
  line_list = gtk_text_layout_get_lines (layout, clip.y, clip.y + clip.height, &offset_y);
843 844 845 846

  if (line_list == NULL)
    return; /* nothing on the screen */

847 848
  text_renderer = get_text_renderer ();
  text_renderer_begin (text_renderer, widget, cr);
849

850 851 852
  /* text_renderer_begin/end does cairo_save/restore */
  cairo_translate (cr, 0, offset_y);

853
  gtk_text_layout_wrap_loop_start (layout);
854

Paolo Borelli's avatar
Paolo Borelli committed
855 856 857
  have_selection = gtk_text_buffer_get_selection_bounds (layout->buffer,
                                                         &selection_start,
                                                         &selection_end);
858

859 860 861 862 863 864 865 866
  tmp_list = line_list;
  while (tmp_list != NULL)
    {
      GtkTextLineDisplay *line_display;
      gint selection_start_index = -1;
      gint selection_end_index = -1;

      GtkTextLine *line = tmp_list->data;
867

868
      line_display = gtk_text_layout_get_line_display (layout, line, FALSE);
869

870 871 872
      if (line_display->height > 0)
        {
          g_assert (line_display->layout != NULL);
873
          
874
          if (have_selection)
875
            {
876 877
              GtkTextIter line_start, line_end;
              gint byte_count;
878
              
879 880 881
              gtk_text_layout_get_iter_at_line (layout,
                                                &line_start,
                                                line, 0);
882
              line_end = line_start;
883 884
	      if (!gtk_text_iter_ends_line (&line_end))
		gtk_text_iter_forward_to_line_end (&line_end);
885
              byte_count = gtk_text_iter_get_visible_line_index (&line_end);     
886

887 888
              if (gtk_text_iter_compare (&selection_start, &line_end) <= 0 &&
                  gtk_text_iter_compare (&selection_end, &line_start) >= 0)
889 890
                {
                  if (gtk_text_iter_compare (&selection_start, &line_start) >= 0)
891
                    selection_start_index = gtk_text_iter_get_visible_line_index (&selection_start);
892 893 894 895
                  else
                    selection_start_index = -1;

                  if (gtk_text_iter_compare (&selection_end, &line_end) <= 0)
896
                    selection_end_index = gtk_text_iter_get_visible_line_index (&selection_end);
897
                  else
898
                    selection_end_index = byte_count + 1; /* + 1 to flag past-the-end */
899
                }
900 901
            }

902 903
          render_para (text_renderer, line_display,
                       selection_start_index, selection_end_index);
904

905
          /* We paint the cursors last, because they overlap another chunk
906 907 908
           * and need to appear on top.
           */
          if (line_display->cursors != NULL)
909
            {
910
              int i;
911

912 913 914
              for (i = 0; i < line_display->cursors->len; i++)
                {
                  int index;
915
                  PangoDirection dir;
916 917

                  index = g_array_index(line_display->cursors, int, i);
918 919 920 921
                  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);
922
                }
923 924
            }
        } /* line_display->height > 0 */
925

926
      cairo_translate (cr, 0, line_display->height);
927
      gtk_text_layout_free_line_display (layout, line_display);
928
      
929 930 931 932
      tmp_list = g_slist_next (tmp_list);
    }

  gtk_text_layout_wrap_loop_end (layout);
933

934 935 936 937 938
  tmp_widgets = text_renderer_end (text_renderer);
  if (widgets)
    *widgets = tmp_widgets;
  else
    g_list_free_full (tmp_widgets, g_object_unref);
939 940 941

  g_slist_free (line_list);
}