gtktextdisplay.c 31 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 24 25 26 27
 * 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, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Original Tk license:
28 29 30 31 32
 *
 * 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.
33
 *
34 35 36 37 38 39 40 41 42
 * 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.
43
 *
44 45 46 47 48
 * 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.
49
 *
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
 * 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.
68 69 70 71 72 73 74
 *
 */
/*
 * 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/.
75 76
 */

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

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

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

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

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

104
struct _GtkTextRenderer
105
{
106
  PangoRenderer parent_instance;
107 108

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

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

  guint state : 2;
118 119
};

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

125
G_DEFINE_TYPE (GtkTextRenderer, _gtk_text_renderer, PANGO_TYPE_RENDERER)
126

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

252 253
  if (text_renderer->rgba_set[part])
    gdk_cairo_set_source_rgba (text_renderer->cr, &text_renderer->rgba[part]);
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 367
}

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

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

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

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

389 390 391
      set_color (text_renderer, PANGO_RENDER_PART_FOREGROUND);

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

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

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

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

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

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

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

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

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

464 465
  object_class->finalize = gtk_text_renderer_finalize;
}
466

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

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

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

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

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

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

  context = gtk_widget_get_style_context (text_renderer->widget);

  gtk_style_context_restore (context);

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

516
  text_renderer->widgets = NULL;
517

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

524 525
  return widgets;
}
526

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

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

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

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

  g_free (ranges);
  return clip_region;
557 558
}

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

575
  iter = pango_layout_get_iter (layout);
576

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

579 580 581 582 583 584
  /* 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;
585 586 587 588

  context = gtk_widget_get_style_context (text_renderer->widget);

  state = GTK_STATE_FLAG_SELECTED;
589
  if (gtk_widget_has_focus (text_renderer->widget))
590
    state |= GTK_STATE_FLAG_FOCUSED;
591

592
  gtk_style_context_get_background_color (context, state, &selection);
593

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

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

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

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

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

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

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

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

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

688
              cairo_save (cr);
689 690
              gdk_cairo_region (cr, clip_region);
              cairo_clip (cr);
691
              cairo_region_destroy (clip_region);
692

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

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

707
              cairo_restore (cr);
708 709

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

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

                  cairo_restore (cr);
725 726
                }

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

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

738 739
                  cairo_save (cr);

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

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

761 762 763
              /* 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);
764

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

770 771
              cairo_save (cr);

772 773
              gdk_cairo_rectangle (cr, &cursor_rect);
              cairo_clip (cr);
774

775
              gdk_cairo_set_source_rgba (cr, &cursor_color);
776
              cairo_paint (cr);
777

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

786
                  gdk_cairo_set_source_rgba (cr, &color);
787 788 789 790 791

		  text_renderer_set_state (text_renderer, CURSOR);

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

              cairo_restore (cr);
797
	    }
798
        }
799 800 801

      byte_offset += line->length;
    }
802 803 804
  while (pango_layout_iter_next_line (iter));

  pango_layout_iter_free (iter);
805 806
}

807
static GtkTextRenderer *
808
get_text_renderer (void)
809
{
810
  static GtkTextRenderer *text_renderer = NULL;
811

812
  if (!text_renderer)
813
    text_renderer = g_object_new (GTK_TYPE_TEXT_RENDERER, NULL);
814 815

  return text_renderer;
816 817 818 819
}

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

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

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

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
  tmp_list = line_list;
  while (tmp_list != NULL)
    {
      GtkTextLineDisplay *line_display;
      gint selection_start_index = -1;
      gint selection_end_index = -1;
865 866
      gboolean have_strong;
      gboolean have_weak;
867 868

      GtkTextLine *line = tmp_list->data;
869

870
      line_display = gtk_text_layout_get_line_display (layout, line, FALSE);
871

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

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

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

904 905
          render_para (text_renderer, line_display,
                       selection_start_index, selection_end_index);
906

907
          /* We paint the cursors last, because they overlap another chunk
908 909
         and need to appear on top. */

910 911 912 913 914 915 916 917 918 919 920 921 922 923 924
 	  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;
 	    }
	  
925 926 927 928
          cursor_list = line_display->cursors;
          while (cursor_list)
            {
              GtkTextCursorDisplay *cursor = cursor_list->data;
929 930
	      GtkTextDirection dir;
 	      GdkRectangle cursor_location;
931

932
              dir = line_display->direction;
933 934 935 936 937 938
 	      if (have_strong && have_weak)
 		{
 		  if (!cursor->is_strong)
 		    dir = (dir == GTK_TEXT_DIR_RTL) ? GTK_TEXT_DIR_LTR : GTK_TEXT_DIR_RTL;
 		}
 
939
 	      cursor_location.x = line_display->x_offset + cursor->x;
940
 	      cursor_location.y = line_display->top_margin + cursor->y;
941 942
 	      cursor_location.width = 0;
 	      cursor_location.height = cursor->height;
943

944 945 946
	      gtk_draw_insertion_cursor (widget, cr, &cursor_location,
                                         cursor->is_strong,
                                         dir, have_strong && have_weak);
947

948 949 950 951
              cursor_list = cursor_list->next;
            }
        } /* line_display->height > 0 */
          
952
      cairo_translate (cr, 0, line_display->height);
953
      gtk_text_layout_free_line_display (layout, line_display);
954
      
955 956 957 958
      tmp_list = g_slist_next (tmp_list);
    }

  gtk_text_layout_wrap_loop_end (layout);
959 960 961 962 963 964 965 966 967

  tmp_widgets = text_renderer_end (text_renderer);
  if (widgets)
    *widgets = tmp_widgets;
  else
    {
      g_list_foreach (tmp_widgets, (GFunc)g_object_unref, NULL);
      g_list_free (tmp_widgets);
    }
968 969 970

  g_slist_free (line_list);
}