gtktextdisplay.c 31.8 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 "gtktextattributesprivate.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
  GdkRGBA *error_color;	/* Error underline color for this widget */
112
  GList *widgets;      	/* widgets encountered when drawing */
113 114

  guint state : 2;
115 116
};

117
struct _GtkTextRendererClass
118
{
119
  PangoRendererClass parent_class;
120
};
121

122 123
GType _gtk_text_renderer_get_type (void);

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 color = { 0, };
  guint16 alpha;
134

135 136
  if (rgba)
    {
137 138 139 140 141 142
      color.red = (guint16)(rgba->red * 65535);
      color.green = (guint16)(rgba->green * 65535);
      color.blue = (guint16)(rgba->blue * 65535);
      alpha = (guint16)(rgba->alpha * 65535);
      pango_renderer_set_color (renderer, part, &color);
      pango_renderer_set_alpha (renderer, part, alpha);
143 144
    }
  else
145 146 147 148
    {
      pango_renderer_set_color (renderer, part, NULL);
      pango_renderer_set_alpha (renderer, part, 0);
    }
149 150
}

151 152
static GtkTextAppearance *
get_item_appearance (PangoItem *item)
153
{
154 155 156 157 158 159 160 161 162 163 164 165 166
  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;
167 168
}

169 170 171
extern GtkCssNode *gtk_text_view_get_text_node      (GtkTextView *text_view);
extern GtkCssNode *gtk_text_view_get_selection_node (GtkTextView *text_view);

172
static void
173 174
gtk_text_renderer_prepare_run (PangoRenderer  *renderer,
			       PangoLayoutRun *run)
175
{
176
  GtkStyleContext *context;
177
  GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer);
178
  GdkRGBA *bg_rgba = NULL;
179
  GdkRGBA *fg_rgba = NULL;
180 181
  GtkTextAppearance *appearance;

Matthias Clasen's avatar
Matthias Clasen committed
182
  PANGO_RENDERER_CLASS (_gtk_text_renderer_parent_class)->prepare_run (renderer, run);
183 184 185

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

187 188
  context = gtk_widget_get_style_context (text_renderer->widget);

189
  if (appearance->draw_bg && text_renderer->state == NORMAL)
190
    bg_rgba = appearance->rgba[0];
191
  else
192
    bg_rgba = NULL;
193

194
  text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_BACKGROUND, bg_rgba);
195

196
  if (text_renderer->state == SELECTED)
197
    {
198
      GtkCssNode *selection_node;
199

200 201
      selection_node = gtk_text_view_get_selection_node ((GtkTextView *)text_renderer->widget);
      gtk_style_context_save_to_node (context, selection_node);
202

203 204 205
      gtk_style_context_get (context, gtk_style_context_get_state (context),
                             "color", &fg_rgba,
                             NULL);
206 207

      gtk_style_context_restore (context);
208
    }
209
  else if (text_renderer->state == CURSOR && gtk_widget_has_focus (text_renderer->widget))
210
    {
211
      gtk_style_context_get (context, gtk_style_context_get_state (context),
212 213 214
                             "background-color", &fg_rgba,
                              NULL);
    }
215
  else
216
    fg_rgba = appearance->rgba[1];
217

218
  text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_FOREGROUND, fg_rgba);
219

220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
  if (GTK_TEXT_APPEARANCE_GET_STRIKETHROUGH_RGBA_SET (appearance))
    {
      GdkRGBA rgba;

      GTK_TEXT_APPEARANCE_GET_STRIKETHROUGH_RGBA (appearance, &rgba);
      text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_STRIKETHROUGH, &rgba);
    }
  else
    text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_STRIKETHROUGH, fg_rgba);

  if (GTK_TEXT_APPEARANCE_GET_UNDERLINE_RGBA_SET (appearance))
    {
      GdkRGBA rgba;

      GTK_TEXT_APPEARANCE_GET_UNDERLINE_RGBA (appearance, &rgba);
      text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_UNDERLINE, &rgba);
    }
  else if (appearance->underline == PANGO_UNDERLINE_ERROR)
238 239 240
    {
      if (!text_renderer->error_color)
        {
241 242
	  GdkColor *color = NULL;

243
          gtk_style_context_get_style (context,
244
                                       "error-underline-color", &color,
245 246
                                       NULL);

247 248
	  if (color)
	    {
249
	      GdkRGBA rgba;
250 251 252 253 254

	      rgba.red = color->red / 65535.;
	      rgba.green = color->green / 65535.;
	      rgba.blue = color->blue / 65535.;
	      rgba.alpha = 1;
Matthias Clasen's avatar
Matthias Clasen committed
255
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
256
	      gdk_color_free (color);
Matthias Clasen's avatar
Matthias Clasen committed
257
G_GNUC_END_IGNORE_DEPRECATIONS
258 259 260 261 262 263 264 265

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

268
      text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_UNDERLINE, text_renderer->error_color);
269 270
    }
  else
271 272 273 274
    text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_UNDERLINE, fg_rgba);

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

277 278 279 280
static void
set_color (GtkTextRenderer *text_renderer,
           PangoRenderPart  part)
{
281 282 283 284
  PangoColor *color;
  GdkRGBA rgba;
  guint16 alpha;

285 286
  cairo_save (text_renderer->cr);

287 288 289 290 291 292 293 294 295 296
  color = pango_renderer_get_color (PANGO_RENDERER (text_renderer), part);
  alpha = pango_renderer_get_alpha (PANGO_RENDERER (text_renderer), part);
  if (color)
    {
      rgba.red = color->red / 65535.;
      rgba.green = color->green / 65535.;
      rgba.blue = color->blue / 65535.;
      rgba.alpha = alpha / 65535.;
      gdk_cairo_set_source_rgba (text_renderer->cr, &rgba);
    }
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 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
}

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

411 412 413 414 415
static void
gtk_text_renderer_draw_shape (PangoRenderer   *renderer,
			      PangoAttrShape  *attr,
			      int              x,
			      int              y)
416
{
417
  GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer);
418

419
  if (attr->data == NULL)
420
    {
421 422 423
      /* This happens if we have an empty widget anchor. Draw
       * something empty-looking.
       */
424 425
      GdkRectangle shape_rect;
      cairo_t *cr;
426

427 428 429 430
      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;
431

432 433 434
      set_color (text_renderer, PANGO_RENDER_PART_FOREGROUND);

      cr = text_renderer->cr;
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450

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

      unset_color (text_renderer);
453 454 455
    }
  else if (GDK_IS_PIXBUF (attr->data))
    {
456
      cairo_t *cr = text_renderer->cr;
457
      GdkPixbuf *pixbuf = GDK_PIXBUF (attr->data);
458
      
459
      cairo_save (cr);
460

461 462 463 464
      gdk_cairo_set_source_pixbuf (cr, pixbuf,
                                   PANGO_PIXELS (x),
                                   PANGO_PIXELS (y) -  gdk_pixbuf_get_height (pixbuf));
      cairo_paint (cr);
465

466
      cairo_restore (cr);
467
    }
468 469
  else if (GTK_IS_WIDGET (attr->data))
    {
470 471 472 473 474 475
      GtkWidget *widget;
      
      widget = GTK_WIDGET (attr->data);

      text_renderer->widgets = g_list_prepend (text_renderer->widgets,
					       g_object_ref (widget));
476 477 478
    }
  else
    g_assert_not_reached (); /* not a pixbuf or widget */
479 480
}

481
static void
482
gtk_text_renderer_finalize (GObject *object)
483
{
Matthias Clasen's avatar
Matthias Clasen committed
484
  G_OBJECT_CLASS (_gtk_text_renderer_parent_class)->finalize (object);
485
}
486

487
static void
Matthias Clasen's avatar
Matthias Clasen committed
488
_gtk_text_renderer_init (GtkTextRenderer *renderer)
489
{
490 491
}

492
static void
Matthias Clasen's avatar
Matthias Clasen committed
493
_gtk_text_renderer_class_init (GtkTextRendererClass *klass)
494
{
495
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
496
  
497 498 499
  PangoRendererClass *renderer_class = PANGO_RENDERER_CLASS (klass);
  
  renderer_class->prepare_run = gtk_text_renderer_prepare_run;
500 501 502 503 504
  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;
505
  renderer_class->draw_shape = gtk_text_renderer_draw_shape;
506

507 508
  object_class->finalize = gtk_text_renderer_finalize;
}
509

510
static void
511 512
text_renderer_set_state (GtkTextRenderer *text_renderer,
			 int              state)
513
{
514
  text_renderer->state = state;
515
}
516

517 518
static void
text_renderer_begin (GtkTextRenderer *text_renderer,
519 520
                     GtkWidget       *widget,
                     cairo_t         *cr)
521
{
522 523 524
  GtkStyleContext *context;
  GtkStateFlags state;
  GdkRGBA color;
525
  GtkCssNode *text_node;
526

527
  text_renderer->widget = widget;
528
  text_renderer->cr = cr;
529 530 531

  context = gtk_widget_get_style_context (widget);

532 533
  text_node = gtk_text_view_get_text_node ((GtkTextView *)widget);
  gtk_style_context_save_to_node (context, text_node);
534

535
  state = gtk_style_context_get_state (context);
536 537 538 539 540
  gtk_style_context_get_color (context, state, &color);

  cairo_save (cr);

  gdk_cairo_set_source_rgba (cr, &color);
541
}
542

543 544
/* Returns a GSList of (referenced) widgets encountered while drawing.
 */
545
static GList *
546 547
text_renderer_end (GtkTextRenderer *text_renderer)
{
548
  GtkStyleContext *context;
549
  GList *widgets = text_renderer->widgets;
550

551 552 553 554 555 556
  cairo_restore (text_renderer->cr);

  context = gtk_widget_get_style_context (text_renderer->widget);

  gtk_style_context_restore (context);

557
  text_renderer->widget = NULL;
558
  text_renderer->cr = NULL;
559

560 561
  text_renderer->widgets = NULL;

562 563
  if (text_renderer->error_color)
    {
564
      gdk_rgba_free (text_renderer->error_color);
565 566
      text_renderer->error_color = NULL;
    }
567 568

  return widgets;
569
}
570

571
static cairo_region_t *
572 573 574 575 576 577 578 579 580 581 582
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;
583
  cairo_region_t *clip_region = cairo_region_create ();
584

585
  pango_layout_line_get_x_ranges (line, start_index, end_index, &ranges, &n_ranges);
586

587 588 589
  for (i=0; i < n_ranges; i++)
    {
      GdkRectangle rect;
590

591 592 593 594 595
      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;
      
596
      cairo_region_union_rectangle (clip_region, &rect);
597
    }
598 599 600

  g_free (ranges);
  return clip_region;
601 602
}

603
static void
604
render_para (GtkTextRenderer    *text_renderer,
605 606
             GtkTextLineDisplay *line_display,
             int                 selection_start_index,
607
             int                 selection_end_index)
608
{
609
  GtkStyleContext *context;
610
  PangoLayout *layout = line_display->layout;
611
  int byte_offset = 0;
612 613
  PangoLayoutIter *iter;
  int screen_width;
614
  GdkRGBA selection;
615
  gboolean first = TRUE;
616
  GtkCssNode *selection_node;
617

618 619
  iter = pango_layout_get_iter (layout);
  screen_width = line_display->total_width;
620 621

  context = gtk_widget_get_style_context (text_renderer->widget);
622 623
  selection_node = gtk_text_view_get_selection_node ((GtkTextView*)text_renderer->widget);
  gtk_style_context_save_to_node (context, selection_node);
624

625
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
626
  gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), &selection);
627
G_GNUC_END_IGNORE_DEPRECATIONS
628

629 630
 gtk_style_context_restore (context);

631 632
  do
    {
633
      PangoLayoutLine *line = pango_layout_iter_get_line_readonly (iter);
634
      int selection_y, selection_height;
635 636 637
      int first_y, last_y;
      PangoRectangle line_rect;
      int baseline;
638
      gboolean at_last_line;
639 640 641 642 643 644 645 646 647 648 649 650 651 652
      
      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
       */
653
      selection_y = PANGO_PIXELS (first_y) + line_display->top_margin;
654
      selection_height = PANGO_PIXELS (last_y) - PANGO_PIXELS (first_y);
655 656

      if (first)
657 658 659 660
        {
          selection_y -= line_display->top_margin;
          selection_height += line_display->top_margin;
        }
661 662 663

      at_last_line = pango_layout_iter_at_last_line (iter);
      if (at_last_line)
664
        selection_height += line_display->bottom_margin;
665
      
666
      first = FALSE;
667

668
      if (selection_start_index < byte_offset &&
669 670
          selection_end_index > line->length + byte_offset) /* All selected */
        {
671 672 673
          cairo_t *cr = text_renderer->cr;

          cairo_save (cr);
674
          gdk_cairo_set_source_rgba (cr, &selection);
675
          cairo_rectangle (cr, 
676
                           line_display->left_margin, selection_y,
677 678
                           screen_width, selection_height);
          cairo_fill (cr);
679
          cairo_restore(cr);
680

681
	  text_renderer_set_state (text_renderer, SELECTED);
682 683
	  pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer),
					   line, 
684 685
					   line_rect.x,
					   baseline);
686
        }
687
      else
688
        {
689
          if (line_display->pg_bg_rgba)
690
            {
691 692 693
              cairo_t *cr = text_renderer->cr;

              cairo_save (cr);
694 695
 
	      gdk_cairo_set_source_rgba (text_renderer->cr, line_display->pg_bg_rgba);
696
              cairo_rectangle (cr, 
697
                               line_display->left_margin, selection_y,
698 699 700
                               screen_width, selection_height);
              cairo_fill (cr);

701
              cairo_restore (cr);
702 703
            }
        
704
	  text_renderer_set_state (text_renderer, NORMAL);
705 706
	  pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer),
					   line, 
707 708
					   line_rect.x,
					   baseline);
709

710 711 712 713 714 715 716
	  /* 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)
717
            {
718
              cairo_t *cr = text_renderer->cr;
719
              cairo_region_t *clip_region = get_selected_clip (text_renderer, layout, line,
720
                                                          line_display->x_offset,
721 722
                                                          selection_y,
                                                          selection_height,
723
                                                          selection_start_index, selection_end_index);
724

725
              cairo_save (cr);
726 727
              gdk_cairo_region (cr, clip_region);
              cairo_clip (cr);
728
              cairo_region_destroy (clip_region);
729

730
              gdk_cairo_set_source_rgba (cr, &selection);
731
              cairo_rectangle (cr,
732
                               PANGO_PIXELS (line_rect.x),
733 734 735 736 737
                               selection_y,
                               PANGO_PIXELS (line_rect.width),
                               selection_height);
              cairo_fill (cr);

738
	      text_renderer_set_state (text_renderer, SELECTED);
739 740
	      pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer),
					       line, 
741 742
					       line_rect.x,
					       baseline);
743

744
              cairo_restore (cr);
745 746

              /* Paint in the ends of the line */
747
              if (line_rect.x > line_display->left_margin * PANGO_SCALE &&
748 749 750
                  ((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)))
                {
751
                  cairo_save (cr);
752

753
                  gdk_cairo_set_source_rgba (cr, &selection);
754
                  cairo_rectangle (cr,
755
                                   line_display->left_margin,
756 757 758 759
                                   selection_y,
                                   PANGO_PIXELS (line_rect.x) - line_display->left_margin,
                                   selection_height);
                  cairo_fill (cr);
760 761

                  cairo_restore (cr);
762 763
                }

764 765
              if (line_rect.x + line_rect.width <
                  (screen_width + line_display->left_margin) * PANGO_SCALE &&
766 767 768
                  ((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)))
                {
769
                  int nonlayout_width;
770

771 772 773
                  nonlayout_width =
                    line_display->left_margin + screen_width -
                    PANGO_PIXELS (line_rect.x) - PANGO_PIXELS (line_rect.width);
774

775 776
                  cairo_save (cr);

777
                  gdk_cairo_set_source_rgba (cr, &selection);
778
                  cairo_rectangle (cr,
779
                                   PANGO_PIXELS (line_rect.x) + PANGO_PIXELS (line_rect.width),
780 781 782 783
                                   selection_y,
                                   nonlayout_width,
                                   selection_height);
                  cairo_fill (cr);
784 785

                  cairo_restore (cr);
786 787
                }
            }
788
	  else if (line_display->has_block_cursor &&
789
		   gtk_widget_has_focus (text_renderer->widget) &&
790 791 792 793 794
		   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;
795
              GdkRGBA cursor_color;
796
              cairo_t *cr = text_renderer->cr;
797

798 799 800
              /* 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);
801

802 803
	      cursor_rect.x = line_display->x_offset + line_display->block_cursor.x;
	      cursor_rect.y = line_display->block_cursor.y + line_display->top_margin;
804
	      cursor_rect.width = line_display->block_cursor.width;
805
	      cursor_rect.height = line_display->block_cursor.height;
806

807 808
              cairo_save (cr);

809 810
              gdk_cairo_rectangle (cr, &cursor_rect);
              cairo_clip (cr);
811

812
              gdk_cairo_set_source_rgba (cr, &cursor_color);
813
              cairo_paint (cr);
814

815 816 817 818 819
              /* draw text under the cursor if any */
              if (!line_display->cursor_at_line_end)
                {
                  GdkRGBA color;

820
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
821
                  gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), &color);
822
G_GNUC_END_IGNORE_DEPRECATIONS
823

824
                  gdk_cairo_set_source_rgba (cr, &color);
825 826 827 828 829

		  text_renderer_set_state (text_renderer, CURSOR);

		  pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer),
						   line,
830 831
						   line_rect.x,
						   baseline);
832
                }
833 834

              cairo_restore (cr);
835
	    }
836
        }
837 838 839

      byte_offset += line->length;
    }
840 841 842
  while (pango_layout_iter_next_line (iter));

  pango_layout_iter_free (iter);
843 844
}

845
static GtkTextRenderer *
846
get_text_renderer (void)
847
{
848
  static GtkTextRenderer *text_renderer = NULL;
849

850
  if (!text_renderer)
851
    text_renderer = g_object_new (GTK_TYPE_TEXT_RENDERER, NULL);
852 853

  return text_renderer;
854 855 856 857
}

void
gtk_text_layout_draw (GtkTextLayout *layout,
858
                      GtkWidget *widget,
859 860
                      cairo_t *cr,
                      GList **widgets)
861
{
862
  GtkStyleContext *context;
863
  gint offset_y;
864
  GtkTextRenderer *text_renderer;
865
  GtkTextIter selection_start, selection_end;
Paolo Borelli's avatar
Paolo Borelli committed
866
  gboolean have_selection;
867 868
  GSList *line_list;
  GSList *tmp_list;
869
  GList *tmp_widgets;
José Aliste's avatar
José Aliste committed
870
  GdkRectangle clip;
871

872 873 874
  g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
  g_return_if_fail (layout->default_style != NULL);
  g_return_if_fail (layout->buffer != NULL);
875
  g_return_if_fail (cr != NULL);
876

José Aliste's avatar
José Aliste committed
877 878
  if (!gdk_cairo_get_clip_rectangle (cr, &clip))
    return;
879

880 881
  context = gtk_widget_get_style_context (widget);

882
  line_list = gtk_text_layout_get_lines (layout, clip.y, clip.y + clip.height, &offset_y);
883 884 885 886

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

887 888
  text_renderer = get_text_renderer ();
  text_renderer_begin (text_renderer, widget, cr);
889

890 891 892
  /* text_renderer_begin/end does cairo_save/restore */
  cairo_translate (cr, 0, offset_y);

893
  gtk_text_layout_wrap_loop_start (layout);
894

Paolo Borelli's avatar
Paolo Borelli committed
895 896 897
  have_selection = gtk_text_buffer_get_selection_bounds (layout->buffer,
                                                         &selection_start,
                                                         &selection_end);
898

899 900 901 902 903 904 905 906
  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;
907

908
      line_display = gtk_text_layout_get_line_display (layout, line, FALSE);
909

910 911 912
      if (line_display->height > 0)
        {
          g_assert (line_display->layout != NULL);
913
          
914
          if (have_selection)
915
            {
916 917
              GtkTextIter line_start, line_end;
              gint byte_count;
918
              
919 920 921
              gtk_text_layout_get_iter_at_line (layout,
                                                &line_start,
                                                line, 0);
922
              line_end = line_start;
923 924
	      if (!gtk_text_iter_ends_line (&line_end))
		gtk_text_iter_forward_to_line_end (&line_end);
925
              byte_count = gtk_text_iter_get_visible_line_index (&line_end);     
926

927 928
              if (gtk_text_iter_compare (&selection_start, &line_end) <= 0 &&
                  gtk_text_iter_compare (&selection_end, &line_start) >= 0)
929 930
                {
                  if (gtk_text_iter_compare (&selection_start, &line_start) >= 0)
931
                    selection_start_index = gtk_text_iter_get_visible_line_index (&selection_start);
932 933 934 935
                  else
                    selection_start_index = -1;

                  if (gtk_text_iter_compare (&selection_end, &line_end) <= 0)
936
                    selection_end_index = gtk_text_iter_get_visible_line_index (&selection_end);
937
                  else
938
                    selection_end_index = byte_count + 1; /* + 1 to flag past-the-end */
939
                }
940 941
            }

942 943
          render_para (text_renderer, line_display,
                       selection_start_index, selection_end_index);
944

945
          /* We paint the cursors last, because they overlap another chunk
946 947 948
           * and need to appear on top.
           */
          if (line_display->cursors != NULL)
949
            {
950
              int i;
951

952 953 954
              for (i = 0; i < line_display->cursors->len; i++)
                {
                  int index;
955
                  PangoDirection dir;
956 957

                  index = g_array_index(line_display->cursors, int, i);
958 959 960 961
                  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);
962
                }
963 964
            }
        } /* line_display->height > 0 */
965

966
      cairo_translate (cr, 0, line_display->height);
967
      gtk_text_layout_free_line_display (layout, line_display);
968
      
969
      tmp_list = tmp_list->next;
970 971 972
    }

  gtk_text_layout_wrap_loop_end (layout);
973

974 975 976 977 978
  tmp_widgets = text_renderer_end (text_renderer);
  if (widgets)
    *widgets = tmp_widgets;
  else
    g_list_free_full (tmp_widgets, g_object_unref);
979 980 981

  g_slist_free (line_list);
}