gtktextlayout.c 96.3 KB
Newer Older
1 2
/* GTK - The GIMP Toolkit
 * gtktextlayout.c - calculate the layout of the text
3 4 5 6 7 8 9
 *
 * 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
 * Pango support by Owen Taylor
 *
10 11
 * This file can be used under your choice of two licenses, the LGPL
 * and the original Tk license.
12
 *
13
 * LGPL:
14
 *
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
 * 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:
30 31 32 33 34
 *
 * 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.
35
 *
36 37 38 39 40 41 42 43 44
 * 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.
45
 *
46 47 48 49 50
 * 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.
51
 *
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
 * 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.
70 71 72 73 74 75 76
 *
 */
/*
 * 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/.
77 78
 */

79
#define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API
80
#include <config.h>
81
#include "gtkmarshalers.h"
82 83 84 85 86
#include "gtktextlayout.h"
#include "gtktextbtree.h"
#include "gtktextiterprivate.h"

#include <stdlib.h>
87
#include <string.h>
88

89 90 91 92 93 94 95 96 97 98 99 100
#define GTK_TEXT_LAYOUT_GET_PRIVATE(o)  (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_TYPE_TEXT_LAYOUT, GtkTextLayoutPrivate))

typedef struct _GtkTextLayoutPrivate GtkTextLayoutPrivate;

struct _GtkTextLayoutPrivate
{
  /* Cache the line that the cursor is positioned on, as the keyboard
     direction only influences the direction of the cursor line.
  */
  GtkTextLine *cursor_line;
};

101
static GtkTextLineData *gtk_text_layout_real_wrap (GtkTextLayout *layout,
102 103 104
                                                   GtkTextLine *line,
                                                   /* may be NULL */
                                                   GtkTextLineData *line_data);
105 106 107

static void gtk_text_layout_invalidated     (GtkTextLayout     *layout);

108 109 110 111 112 113 114 115 116
static void gtk_text_layout_real_invalidate        (GtkTextLayout     *layout,
						    const GtkTextIter *start,
						    const GtkTextIter *end);
static void gtk_text_layout_invalidate_cache       (GtkTextLayout     *layout,
						    GtkTextLine       *line);
static void gtk_text_layout_invalidate_cursor_line (GtkTextLayout     *layout);
static void gtk_text_layout_real_free_line_data    (GtkTextLayout     *layout,
						    GtkTextLine       *line,
						    GtkTextLineData   *line_data);
Owen Taylor's avatar
Owen Taylor committed
117 118 119 120
static void gtk_text_layout_emit_changed           (GtkTextLayout     *layout,
						    gint               y,
						    gint               old_height,
						    gint               new_height);
121 122 123 124 125

static void gtk_text_layout_invalidate_all (GtkTextLayout *layout);

static PangoAttribute *gtk_text_attr_appearance_new (const GtkTextAppearance *appearance);

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
static void gtk_text_layout_mark_set_handler    (GtkTextBuffer     *buffer,
						 const GtkTextIter *location,
						 GtkTextMark       *mark,
						 gpointer           data);
static void gtk_text_layout_buffer_insert_text  (GtkTextBuffer     *textbuffer,
						 GtkTextIter       *iter,
						 gchar             *str,
						 gint               len,
						 gpointer           data);
static void gtk_text_layout_buffer_delete_range (GtkTextBuffer     *textbuffer,
						 GtkTextIter       *start,
						 GtkTextIter       *end,
						 gpointer           data);

static void gtk_text_layout_update_cursor_line (GtkTextLayout *layout);

142 143 144
enum {
  INVALIDATED,
  CHANGED,
145
  ALLOCATE_CHILD,
146 147 148 149 150 151 152 153
  LAST_SIGNAL
};

enum {
  ARG_0,
  LAST_ARG
};

154 155
#define PIXEL_BOUND(d) (((d) + PANGO_SCALE - 1) / PANGO_SCALE)

156
static void gtk_text_layout_init       (GtkTextLayout      *text_layout);
157
static void gtk_text_layout_class_init (GtkTextLayoutClass *klass);
158
static void gtk_text_layout_finalize   (GObject            *object);
159 160 161 162 163 164 165


static GtkObjectClass *parent_class = NULL;
static guint signals[LAST_SIGNAL] = { 0 };

PangoAttrType gtk_text_attr_appearance_type = 0;

166
GType
167 168
gtk_text_layout_get_type (void)
{
169
  static GType our_type = 0;
170 171 172

  if (our_type == 0)
    {
173
      static const GTypeInfo our_info =
174 175
      {
        sizeof (GtkTextLayoutClass),
176 177 178 179 180 181 182 183
        (GBaseInitFunc) NULL,
        (GBaseFinalizeFunc) NULL,
        (GClassInitFunc) gtk_text_layout_class_init,
        NULL,           /* class_finalize */
        NULL,           /* class_data */
        sizeof (GtkTextLayout),
        0,              /* n_preallocs */
        (GInstanceInitFunc) gtk_text_layout_init
184 185
      };

Manish Singh's avatar
Manish Singh committed
186 187
      our_type = g_type_register_static (G_TYPE_OBJECT, "GtkTextLayout",
                                         &our_info, 0);
188
    }
189 190 191 192 193 194 195

  return our_type;
}

static void
gtk_text_layout_class_init (GtkTextLayoutClass *klass)
{
196
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
197

198 199 200
  parent_class = g_type_class_peek_parent (klass);
  
  object_class->finalize = gtk_text_layout_finalize;
201 202 203 204 205

  klass->wrap = gtk_text_layout_real_wrap;
  klass->invalidate = gtk_text_layout_real_invalidate;
  klass->free_line_data = gtk_text_layout_real_free_line_data;

206
  signals[INVALIDATED] =
207 208 209 210 211
    g_signal_new ("invalidated",
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkTextLayoutClass, invalidated),
                  NULL, NULL,
212
                  _gtk_marshal_VOID__VOID,
Manish Singh's avatar
Manish Singh committed
213
                  G_TYPE_NONE,
214
                  0);
215 216

  signals[CHANGED] =
217 218 219 220 221
    g_signal_new ("changed",
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkTextLayoutClass, changed),
                  NULL, NULL,
222
                  _gtk_marshal_VOID__INT_INT_INT,
Manish Singh's avatar
Manish Singh committed
223
                  G_TYPE_NONE,
224
                  3,
Manish Singh's avatar
Manish Singh committed
225 226 227
                  G_TYPE_INT,
                  G_TYPE_INT,
                  G_TYPE_INT);
228

229
  signals[ALLOCATE_CHILD] =
230 231 232 233 234
    g_signal_new ("allocate_child",
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkTextLayoutClass, allocate_child),
                  NULL, NULL,
235
                  _gtk_marshal_VOID__OBJECT_INT_INT,
Manish Singh's avatar
Manish Singh committed
236
                  G_TYPE_NONE,
237 238
                  3,
                  GTK_TYPE_OBJECT,
Manish Singh's avatar
Manish Singh committed
239 240
                  G_TYPE_INT,
                  G_TYPE_INT);
241 242
  
  g_type_class_add_private (object_class, sizeof (GtkTextLayoutPrivate));
243 244
}

245
static void
246 247
gtk_text_layout_init (GtkTextLayout *text_layout)
{
248
  text_layout->cursor_visible = TRUE;
249 250 251 252 253
}

GtkTextLayout*
gtk_text_layout_new (void)
{
Manish Singh's avatar
Manish Singh committed
254
  return g_object_new (GTK_TYPE_TEXT_LAYOUT, NULL);
255 256 257 258 259 260 261
}

static void
free_style_cache (GtkTextLayout *text_layout)
{
  if (text_layout->one_style_cache)
    {
262
      gtk_text_attributes_unref (text_layout->one_style_cache);
263 264 265 266 267
      text_layout->one_style_cache = NULL;
    }
}

static void
268
gtk_text_layout_finalize (GObject *object)
269 270 271 272 273
{
  GtkTextLayout *layout;

  layout = GTK_TEXT_LAYOUT (object);

274
  gtk_text_layout_set_buffer (layout, NULL);
275 276

  if (layout->default_style)
277
    gtk_text_attributes_unref (layout->default_style);
278 279 280 281
  layout->default_style = NULL;

  if (layout->ltr_context)
    {
Manish Singh's avatar
Manish Singh committed
282
      g_object_unref (layout->ltr_context);
283 284 285 286
      layout->ltr_context = NULL;
    }
  if (layout->rtl_context)
    {
Manish Singh's avatar
Manish Singh committed
287
      g_object_unref (layout->rtl_context);
288 289
      layout->rtl_context = NULL;
    }
290
  
291 292 293 294 295 296 297
  if (layout->one_display_cache) 
    {
      GtkTextLineDisplay *tmp_display = layout->one_display_cache;
      layout->one_display_cache = NULL;
      gtk_text_layout_free_line_display (layout, tmp_display);
    }

298 299 300 301 302 303 304 305 306
  (* G_OBJECT_CLASS (parent_class)->finalize) (object);
}

void
gtk_text_layout_set_buffer (GtkTextLayout *layout,
                            GtkTextBuffer *buffer)
{
  g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
  g_return_if_fail (buffer == NULL || GTK_IS_TEXT_BUFFER (buffer));
307

308 309 310 311
  if (layout->buffer == buffer)
    return;

  free_style_cache (layout);
312

313 314
  if (layout->buffer)
    {
315
      _gtk_text_btree_remove_view (_gtk_text_buffer_get_btree (layout->buffer),
316
                                  layout);
317

318 319 320 321 322 323 324 325 326 327
      g_signal_handlers_disconnect_by_func (layout->buffer, 
                                            G_CALLBACK (gtk_text_layout_mark_set_handler), 
                                            layout);
      g_signal_handlers_disconnect_by_func (layout->buffer, 
                                            G_CALLBACK (gtk_text_layout_buffer_insert_text), 
                                            layout);
      g_signal_handlers_disconnect_by_func (layout->buffer, 
                                            G_CALLBACK (gtk_text_layout_buffer_delete_range), 
                                            layout);

Manish Singh's avatar
Manish Singh committed
328
      g_object_unref (layout->buffer);
329 330 331 332 333 334 335
      layout->buffer = NULL;
    }

  if (buffer)
    {
      layout->buffer = buffer;

Manish Singh's avatar
Manish Singh committed
336
      g_object_ref (buffer);
337

338
      _gtk_text_btree_add_view (_gtk_text_buffer_get_btree (buffer), layout);
339 340 341 342 343 344 345 346

      /* Bind to all signals that move the insert mark. */
      g_signal_connect_after (layout->buffer, "mark_set",
                              G_CALLBACK (gtk_text_layout_mark_set_handler), layout);
      g_signal_connect_after (layout->buffer, "insert_text",
                              G_CALLBACK (gtk_text_layout_buffer_insert_text), layout);
      g_signal_connect_after (layout->buffer, "delete_range",
                              G_CALLBACK (gtk_text_layout_buffer_delete_range), layout);
347 348

      gtk_text_layout_update_cursor_line (layout);
349 350 351 352 353 354 355
    }
}

void
gtk_text_layout_default_style_changed (GtkTextLayout *layout)
{
  g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
356

357
  DV (g_print ("invalidating all due to default style change (%s)\n", G_STRLOC));
358 359 360 361 362
  gtk_text_layout_invalidate_all (layout);
}

void
gtk_text_layout_set_default_style (GtkTextLayout *layout,
363
                                   GtkTextAttributes *values)
364 365 366 367 368 369 370
{
  g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
  g_return_if_fail (values != NULL);

  if (values == layout->default_style)
    return;

371
  gtk_text_attributes_ref (values);
372

373
  if (layout->default_style)
374
    gtk_text_attributes_unref (layout->default_style);
375 376 377 378 379 380 381 382

  layout->default_style = values;

  gtk_text_layout_default_style_changed (layout);
}

void
gtk_text_layout_set_contexts (GtkTextLayout *layout,
383 384
                              PangoContext  *ltr_context,
                              PangoContext  *rtl_context)
385 386 387 388
{
  g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));

  if (layout->ltr_context)
Manish Singh's avatar
Manish Singh committed
389
    g_object_unref (ltr_context);
390 391

  layout->ltr_context = ltr_context;
Manish Singh's avatar
Manish Singh committed
392
  g_object_ref (ltr_context);
393

394
  if (layout->rtl_context)
Manish Singh's avatar
Manish Singh committed
395
    g_object_unref (rtl_context);
396 397

  layout->rtl_context = rtl_context;
Manish Singh's avatar
Manish Singh committed
398
  g_object_ref (rtl_context);
399

400
  DV (g_print ("invalidating all due to new pango contexts (%s)\n", G_STRLOC));
401 402 403
  gtk_text_layout_invalidate_all (layout);
}

404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
/**
 * gtk_text_layout_set_cursor_direction:
 * @direction: the new direction(s) for which to draw cursors.
 *             %GTK_TEXT_DIR_NONE means draw cursors for both
 *             left-to-right insertion and right-to-left insertion.
 *             (The two cursors will be visually distinguished.)
 * 
 * Sets which text directions (left-to-right and/or right-to-left) for
 * which cursors will be drawn for the insertion point. The visual
 * point at which new text is inserted depends on whether the new
 * text is right-to-left or left-to-right, so it may be desired to
 * make the drawn position of the cursor depend on the keyboard state.
 **/
void
gtk_text_layout_set_cursor_direction (GtkTextLayout   *layout,
				      GtkTextDirection direction)
{
  if (direction != layout->cursor_direction)
    {
      layout->cursor_direction = direction;
      gtk_text_layout_invalidate_cursor_line (layout);
    }
}

428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
/**
 * gtk_text_layout_set_keyboard_direction:
 * @keyboard_dir: the current direction of the keyboard.
 *
 * Sets the keyboard direction; this is used as for the bidirectional
 * base direction for the line with the cursor if the line contains
 * only neutral characters.
 **/
void
gtk_text_layout_set_keyboard_direction (GtkTextLayout   *layout,
					GtkTextDirection keyboard_dir)
{
  if (keyboard_dir != layout->keyboard_direction)
    {
      layout->keyboard_direction = keyboard_dir;
      gtk_text_layout_invalidate_cursor_line (layout);
    }
}

447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
/**
 * gtk_text_layout_get_buffer:
 * @layout: a #GtkTextLayout
 *
 * Gets the text buffer used by the layout. See
 * gtk_text_layout_set_buffer().
 *
 * Return value: the text buffer used by the layout.
 **/
GtkTextBuffer *
gtk_text_layout_get_buffer (GtkTextLayout *layout)
{
  g_return_val_if_fail (GTK_IS_TEXT_LAYOUT (layout), NULL);

  return layout->buffer;
}

464 465 466 467 468 469
void
gtk_text_layout_set_screen_width (GtkTextLayout *layout, gint width)
{
  g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
  g_return_if_fail (width >= 0);
  g_return_if_fail (layout->wrap_loop_count == 0);
470

471 472
  if (layout->screen_width == width)
    return;
473

474
  layout->screen_width = width;
475

476
  DV (g_print ("invalidating all due to new screen width (%s)\n", G_STRLOC));
477 478 479
  gtk_text_layout_invalidate_all (layout);
}

480 481 482 483
/**
 * gtk_text_layout_set_cursor_visible:
 * @layout: a #GtkTextLayout
 * @cursor_visible: If %FALSE, then the insertion cursor will not
484 485
 *   be shown, even if the text is editable.
 *
486 487 488 489 490 491
 * Sets whether the insertion cursor should be shown. Generally,
 * widgets using #GtkTextLayout will hide the cursor when the
 * widget does not have the input focus.
 **/
void
gtk_text_layout_set_cursor_visible (GtkTextLayout *layout,
492
                                    gboolean       cursor_visible)
493 494 495 496 497 498 499
{
  cursor_visible = (cursor_visible != FALSE);

  if (layout->cursor_visible != cursor_visible)
    {
      GtkTextIter iter;
      gint y, height;
500

501 502 503 504 505
      layout->cursor_visible = cursor_visible;

      /* Now queue a redraw on the paragraph containing the cursor
       */
      gtk_text_buffer_get_iter_at_mark (layout->buffer, &iter,
506
                                        gtk_text_buffer_get_mark (layout->buffer, "insert"));
507 508

      gtk_text_layout_get_line_yrange (layout, &iter, &y, &height);
Owen Taylor's avatar
Owen Taylor committed
509
      gtk_text_layout_emit_changed (layout, y, height, height);
510

511
      gtk_text_layout_invalidate_cache (layout, _gtk_text_iter_get_text_line (&iter));
512 513 514 515 516 517
    }
}

/**
 * gtk_text_layout_get_cursor_visible:
 * @layout: a #GtkTextLayout
518
 *
519
 * Returns whether the insertion cursor will be shown.
520
 *
521 522 523 524 525 526 527 528 529
 * Return value: if %FALSE, the insertion cursor will not be
    shown, even if the text is editable.
 **/
gboolean
gtk_text_layout_get_cursor_visible (GtkTextLayout *layout)
{
  return layout->cursor_visible;
}

Owen Taylor's avatar
Owen Taylor committed
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
/**
 * gtk_text_layout_set_preedit_string:
 * @layout: a #PangoLayout
 * @preedit_string: a string to display at the insertion point
 * @preedit_attrs: a #PangoAttrList of attributes that apply to @preedit_string
 * @cursor_pos: position of cursor within preedit string in chars
 * 
 * Set the preedit string and attributes. The preedit string is a
 * string showing text that is currently being edited and not
 * yet committed into the buffer.
 **/
void
gtk_text_layout_set_preedit_string (GtkTextLayout *layout,
				    const gchar   *preedit_string,
				    PangoAttrList *preedit_attrs,
				    gint           cursor_pos)
{
  g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
  g_return_if_fail (preedit_attrs != NULL || preedit_string == NULL);

  if (layout->preedit_string)
    g_free (layout->preedit_string);

  if (layout->preedit_attrs)
    pango_attr_list_unref (layout->preedit_attrs);

  if (preedit_string)
    {
      layout->preedit_string = g_strdup (preedit_string);
      layout->preedit_len = strlen (layout->preedit_string);
      pango_attr_list_ref (preedit_attrs);
      layout->preedit_attrs = preedit_attrs;

      cursor_pos = CLAMP (cursor_pos, 0, g_utf8_strlen (layout->preedit_string, -1));
      layout->preedit_cursor = g_utf8_offset_to_pointer (layout->preedit_string, cursor_pos) - layout->preedit_string;
    }
  else
    {
      layout->preedit_string = NULL;
      layout->preedit_len = 0;
      layout->preedit_attrs = NULL;
      layout->preedit_cursor = 0;
    }

574
  gtk_text_layout_invalidate_cursor_line (layout);
Owen Taylor's avatar
Owen Taylor committed
575 576
}

577 578 579 580 581 582
void
gtk_text_layout_get_size (GtkTextLayout *layout,
                          gint *width,
                          gint *height)
{
  g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
583

584 585 586 587 588 589 590 591 592 593
  if (width)
    *width = layout->width;

  if (height)
    *height = layout->height;
}

static void
gtk_text_layout_invalidated (GtkTextLayout *layout)
{
Manish Singh's avatar
Manish Singh committed
594
  g_signal_emit (layout, signals[INVALIDATED], 0);
595 596
}

Owen Taylor's avatar
Owen Taylor committed
597 598 599 600 601 602 603 604 605
static void
gtk_text_layout_emit_changed (GtkTextLayout *layout,
			      gint           y,
			      gint           old_height,
			      gint           new_height)
{
  g_signal_emit (layout, signals[CHANGED], 0, y, old_height, new_height);
}

606 607
void
gtk_text_layout_changed (GtkTextLayout *layout,
608 609 610
                         gint           y,
                         gint           old_height,
                         gint           new_height)
611
{
Owen Taylor's avatar
Owen Taylor committed
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626
  /* Check if the range intersects our cached line display,
   * and invalidate the cached line if so.
   */
  if (layout->one_display_cache)
    {
      GtkTextLine *line = layout->one_display_cache->line;
      gint cache_y = _gtk_text_btree_find_line_top (_gtk_text_buffer_get_btree (layout->buffer),
						    line, layout);
      gint cache_height = layout->one_display_cache->height;

      if (cache_y + cache_height > y && cache_y < y + old_height)
	gtk_text_layout_invalidate_cache (layout, line);
    }
  
  gtk_text_layout_emit_changed (layout, y, old_height, new_height);
627 628 629 630
}

void
gtk_text_layout_free_line_data (GtkTextLayout     *layout,
631 632
                                GtkTextLine       *line,
                                GtkTextLineData   *line_data)
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
{
  (* GTK_TEXT_LAYOUT_GET_CLASS (layout)->free_line_data)
    (layout, line, line_data);
}

void
gtk_text_layout_invalidate (GtkTextLayout *layout,
                            const GtkTextIter *start_index,
                            const GtkTextIter *end_index)
{
  (* GTK_TEXT_LAYOUT_GET_CLASS (layout)->invalidate)
    (layout, start_index, end_index);
}

GtkTextLineData*
gtk_text_layout_wrap (GtkTextLayout *layout,
649 650 651
                      GtkTextLine  *line,
                      /* may be NULL */
                      GtkTextLineData *line_data)
652 653 654 655 656 657 658
{
  return (* GTK_TEXT_LAYOUT_GET_CLASS (layout)->wrap) (layout, line, line_data);
}

GSList*
gtk_text_layout_get_lines (GtkTextLayout *layout,
                           /* [top_y, bottom_y) */
659
                           gint top_y,
660 661 662 663 664 665 666
                           gint bottom_y,
                           gint *first_line_y)
{
  GtkTextLine *first_btree_line;
  GtkTextLine *last_btree_line;
  GtkTextLine *line;
  GSList *retval;
667

668 669 670 671
  g_return_val_if_fail (GTK_IS_TEXT_LAYOUT (layout), NULL);
  g_return_val_if_fail (bottom_y > top_y, NULL);

  retval = NULL;
672

673
  first_btree_line =
674
    _gtk_text_btree_find_line_by_y (_gtk_text_buffer_get_btree (layout->buffer),
675
                                   layout, top_y, first_line_y);
676 677 678 679 680
  if (first_btree_line == NULL)
    {
      /* off the bottom */
      return NULL;
    }
681

682
  /* -1 since bottom_y is one past */
683
  last_btree_line =
684
    _gtk_text_btree_find_line_by_y (_gtk_text_buffer_get_btree (layout->buffer),
685
                                    layout, bottom_y - 1, NULL);
686 687

  if (!last_btree_line)
688
    last_btree_line =
689
      _gtk_text_btree_get_end_iter_line (_gtk_text_buffer_get_btree (layout->buffer));
690 691 692 693 694 695 696 697 698 699

  g_assert (last_btree_line != NULL);

  line = first_btree_line;
  while (TRUE)
    {
      retval = g_slist_prepend (retval, line);

      if (line == last_btree_line)
        break;
700

701
      line = _gtk_text_line_next_excluding_last (line);
702
    }
703

704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723
  retval = g_slist_reverse (retval);

  return retval;
}

static void
invalidate_cached_style (GtkTextLayout *layout)
{
  free_style_cache (layout);
}

/* These should be called around a loop which wraps a CONTIGUOUS bunch
 * of display lines. If the lines aren't contiguous you can't call
 * these.
 */
void
gtk_text_layout_wrap_loop_start (GtkTextLayout *layout)
{
  g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
  g_return_if_fail (layout->one_style_cache == NULL);
724

725 726 727 728 729 730 731
  layout->wrap_loop_count += 1;
}

void
gtk_text_layout_wrap_loop_end (GtkTextLayout *layout)
{
  g_return_if_fail (layout->wrap_loop_count > 0);
732

733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750
  layout->wrap_loop_count -= 1;

  if (layout->wrap_loop_count == 0)
    {
      /* We cache a some stuff if we're iterating over some lines wrapping
       * them. This cleans it up.
       */
      /* Nuke our cached style */
      invalidate_cached_style (layout);
      g_assert (layout->one_style_cache == NULL);
    }
}

static void
gtk_text_layout_invalidate_all (GtkTextLayout *layout)
{
  GtkTextIter start;
  GtkTextIter end;
751

752 753 754 755 756 757 758 759
  if (layout->buffer == NULL)
    return;

  gtk_text_buffer_get_bounds (layout->buffer, &start, &end);

  gtk_text_layout_invalidate (layout, &start, &end);
}

760 761
static void
gtk_text_layout_invalidate_cache (GtkTextLayout *layout,
762
                                  GtkTextLine   *line)
763 764 765 766 767 768 769 770 771
{
  if (layout->one_display_cache && line == layout->one_display_cache->line)
    {
      GtkTextLineDisplay *tmp_display = layout->one_display_cache;
      layout->one_display_cache = NULL;
      gtk_text_layout_free_line_display (layout, tmp_display);
    }
}

772 773 774 775 776
/* Now invalidate the paragraph containing the cursor
 */
static void
gtk_text_layout_invalidate_cursor_line (GtkTextLayout *layout)
{
777
  GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
778 779
  GtkTextLineData *line_data;

780 781
  if (priv->cursor_line == NULL)
    return;
782

783
  line_data = _gtk_text_line_get_data (priv->cursor_line, layout);
784 785
  if (line_data)
    {
786 787
      gtk_text_layout_invalidate_cache (layout, priv->cursor_line);
      _gtk_text_line_invalidate_wrap (priv->cursor_line, line_data);
788 789 790 791
      gtk_text_layout_invalidated (layout);
    }
}

792 793 794 795 796 797 798 799 800 801 802 803
static void
gtk_text_layout_update_cursor_line(GtkTextLayout *layout)
{
  GtkTextLayoutPrivate *priv = GTK_TEXT_LAYOUT_GET_PRIVATE (layout);
  GtkTextIter iter;

  gtk_text_buffer_get_iter_at_mark (layout->buffer, &iter,
                                    gtk_text_buffer_get_mark (layout->buffer, "insert"));
  
  priv->cursor_line = _gtk_text_iter_get_text_line (&iter);
}

804 805 806 807 808 809 810
static void
gtk_text_layout_real_invalidate (GtkTextLayout *layout,
                                 const GtkTextIter *start,
                                 const GtkTextIter *end)
{
  GtkTextLine *line;
  GtkTextLine *last_line;
811

812 813 814
  g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
  g_return_if_fail (layout->wrap_loop_count == 0);

815 816 817 818 819 820 821
  /* Because we may be invalidating a mark, it's entirely possible
   * that gtk_text_iter_equal (start, end) in which case we
   * should still invalidate the line they are both on. i.e.
   * we always invalidate the line with "start" even
   * if there's an empty range.
   */
  
822 823 824 825
#if 0
  gtk_text_view_index_spew (start_index, "invalidate start");
  gtk_text_view_index_spew (end_index, "invalidate end");
#endif
826

827 828
  last_line = _gtk_text_iter_get_text_line (end);
  line = _gtk_text_iter_get_text_line (start);
829 830 831

  while (TRUE)
    {
832
      GtkTextLineData *line_data = _gtk_text_line_get_data (line, layout);
833

834 835
      gtk_text_layout_invalidate_cache (layout, line);
      
836
      if (line_data)
837
        _gtk_text_line_invalidate_wrap (line, line_data);
838

839 840
      if (line == last_line)
        break;
841

842
      line = _gtk_text_line_next_excluding_last (line);
843
    }
844

845 846 847 848 849
  gtk_text_layout_invalidated (layout);
}

static void
gtk_text_layout_real_free_line_data (GtkTextLayout     *layout,
850 851
                                     GtkTextLine       *line,
                                     GtkTextLineData   *line_data)
852
{
853
  gtk_text_layout_invalidate_cache (layout, line);
854

855 856 857 858 859 860 861 862
  g_free (line_data);
}



/**
 * gtk_text_layout_is_valid:
 * @layout: a #GtkTextLayout
863
 *
864
 * Check if there are any invalid regions in a #GtkTextLayout's buffer
865
 *
866
 * Return value: %TRUE if any invalid regions were found
867 868 869 870 871
 **/
gboolean
gtk_text_layout_is_valid (GtkTextLayout *layout)
{
  g_return_val_if_fail (GTK_IS_TEXT_LAYOUT (layout), FALSE);
872

873
  return _gtk_text_btree_is_valid (_gtk_text_buffer_get_btree (layout->buffer),
874
                                  layout);
875 876
}

877 878 879
static void
update_layout_size (GtkTextLayout *layout)
{
880
  _gtk_text_btree_get_view_size (_gtk_text_buffer_get_btree (layout->buffer),
881 882 883 884
				layout,
				&layout->width, &layout->height);
}

885 886 887 888 889
/**
 * gtk_text_layout_validate_yrange:
 * @layout: a #GtkTextLayout
 * @anchor: iter pointing into a line that will be used as the
 *          coordinate origin
890 891 892 893 894 895
 * @y0_: offset from the top of the line pointed to by @anchor at
 *       which to begin validation. (The offset here is in pixels
 *       after validation.)
 * @y1_: offset from the top of the line pointed to by @anchor at
 *       which to end validation. (The offset here is in pixels
 *       after validation.)
896 897
 *
 * Ensure that a region of a #GtkTextLayout is valid. The ::changed
898 899 900 901
 * signal will be emitted if any lines are validated.
 **/
void
gtk_text_layout_validate_yrange (GtkTextLayout *layout,
902 903 904
                                 GtkTextIter   *anchor,
                                 gint           y0,
                                 gint           y1)
905 906 907 908 909 910
{
  GtkTextLine *line;
  GtkTextLine *first_line = NULL;
  GtkTextLine *last_line = NULL;
  gint seen;
  gint delta_height = 0;
911 912 913
  gint first_line_y = 0;        /* Quiet GCC */
  gint last_line_y = 0;         /* Quiet GCC */

914
  g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
915

916 917 918 919
  if (y0 > 0)
    y0 = 0;
  if (y1 < 0)
    y1 = 0;
920
  
921 922
  /* Validate backwards from the anchor line to y0
   */
923
  line = _gtk_text_iter_get_text_line (anchor);
924
  line = _gtk_text_line_previous (line);
925 926 927
  seen = 0;
  while (line && seen < -y0)
    {
928
      GtkTextLineData *line_data = _gtk_text_line_get_data (line, layout);
929
      if (!line_data || !line_data->valid)
930 931 932
        {
          gint old_height = line_data ? line_data->height : 0;

933
          _gtk_text_btree_validate_line (_gtk_text_buffer_get_btree (layout->buffer),
934
                                         line, layout);
935
          line_data = _gtk_text_line_get_data (line, layout);
936 937

          delta_height += line_data->height - old_height;
938
          
939
          first_line = line;
940
          first_line_y = -seen - line_data->height;
941 942 943
          if (!last_line)
            {
              last_line = line;
944
              last_line_y = -seen;
945 946 947
            }
        }

948
      seen += line_data->height;
949
      line = _gtk_text_line_previous (line);
950 951 952
    }

  /* Validate forwards to y1 */
953
  line = _gtk_text_iter_get_text_line (anchor);
954 955 956
  seen = 0;
  while (line && seen < y1)
    {
957
      GtkTextLineData *line_data = _gtk_text_line_get_data (line, layout);
958
      if (!line_data || !line_data->valid)
959 960 961
        {
          gint old_height = line_data ? line_data->height : 0;

962
          _gtk_text_btree_validate_line (_gtk_text_buffer_get_btree (layout->buffer),
963
                                         line, layout);
964
          line_data = _gtk_text_line_get_data (line, layout);
965 966

          delta_height += line_data->height - old_height;
967
          
968 969 970 971 972 973 974 975 976
          if (!first_line)
            {
              first_line = line;
              first_line_y = seen;
            }
          last_line = line;
          last_line_y = seen + line_data->height;
        }

977
      seen += line_data->height;
978
      line = _gtk_text_line_next_excluding_last (line);
979 980
    }

981 982
  /* If we found and validated any invalid lines, update size and
   * emit the changed signal
983 984 985
   */
  if (first_line)
    {
986 987 988 989
      gint line_top;

      update_layout_size (layout);

990
      line_top = _gtk_text_btree_find_line_top (_gtk_text_buffer_get_btree (layout->buffer),
991
                                                first_line, layout);
992

Owen Taylor's avatar
Owen Taylor committed
993 994 995 996
      gtk_text_layout_emit_changed (layout,
				    line_top,
				    last_line_y - first_line_y - delta_height,
				    last_line_y - first_line_y);
997 998 999 1000 1001 1002 1003 1004
    }
}

/**
 * gtk_text_layout_validate:
 * @tree: a #GtkTextLayout
 * @max_pixels: the maximum number of pixels to validate. (No more
 *              than one paragraph beyond this limit will be validated)
1005
 *
1006 1007 1008 1009 1010
 * Validate regions of a #GtkTextLayout. The ::changed signal will
 * be emitted for each region validated.
 **/
void
gtk_text_layout_validate (GtkTextLayout *layout,
1011
                          gint           max_pixels)
1012 1013 1014 1015
{
  gint y, old_height, new_height;

  g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
1016

1017
  while (max_pixels > 0 &&
1018
         _gtk_text_btree_validate (_gtk_text_buffer_get_btree (layout->buffer),
1019 1020
                                   layout,  max_pixels,
                                   &y, &old_height, &new_height))
1021 1022
    {
      max_pixels -= new_height;
1023 1024

      update_layout_size (layout);
Owen Taylor's avatar
Owen Taylor committed
1025
      gtk_text_layout_emit_changed (layout, y, old_height, new_height);
1026 1027 1028 1029 1030
    }
}

static GtkTextLineData*
gtk_text_layout_real_wrap (GtkTextLayout   *layout,
1031 1032 1033
                           GtkTextLine     *line,
                           /* may be NULL */
                           GtkTextLineData *line_data)
1034 1035
{
  GtkTextLineDisplay *display;
1036

1037
  g_return_val_if_fail (GTK_IS_TEXT_LAYOUT (layout), NULL);
1038 1039
  g_return_val_if_fail (line != NULL, NULL);
  
1040 1041
  if (line_data == NULL)
    {
1042
      line_data = _gtk_text_line_data_new (layout, line);
1043
      _gtk_text_line_add_data (line, line_data);
1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060
    }

  display = gtk_text_layout_get_line_display (layout, line, TRUE);
  line_data->width = display->width;
  line_data->height = display->height;
  line_data->valid = TRUE;
  gtk_text_layout_free_line_display (layout, display);

  return line_data;
}

/*
 * Layout utility functions
 */

/* If you get the style with get_style () you need to call
   release_style () to free it. */
1061
static GtkTextAttributes*
1062
get_style (GtkTextLayout *layout,
1063
           const GtkTextIter *iter)
1064 1065 1066
{
  GtkTextTag** tags;
  gint tag_count = 0;
1067
  GtkTextAttributes *style;
1068

1069 1070 1071 1072 1073 1074
  /* If we have the one-style cache, then it means
     that we haven't seen a toggle since we filled in the
     one-style cache.
  */
  if (layout->one_style_cache != NULL)
    {
1075
      gtk_text_attributes_ref (layout->one_style_cache);
1076 1077 1078 1079
      return layout->one_style_cache;
    }

  g_assert (layout->one_style_cache == NULL);
1080

1081
  /* Get the tags at this spot */
1082
  tags = _gtk_text_btree_get_tags (iter, &tag_count);
1083 1084 1085 1086 1087 1088

  /* No tags, use default style */
  if (tags == NULL || tag_count == 0)
    {
      /* One ref for the return value, one ref for the
         layout->one_style_cache reference */
1089 1090
      gtk_text_attributes_ref (layout->default_style);
      gtk_text_attributes_ref (layout->default_style);
1091 1092 1093 1094 1095 1096 1097
      layout->one_style_cache = layout->default_style;

      if (tags)
        g_free (tags);

      return layout->default_style;
    }
1098

1099
  /* Sort tags in ascending order of priority */
1100
  _gtk_text_tag_array_sort (tags, tag_count);
1101

1102
  style = gtk_text_attributes_new ();
1103

1104 1105
  gtk_text_attributes_copy_values (layout->default_style,
                                   style);
1106

1107 1108 1109
  _gtk_text_attributes_fill_from_tags (style,
                                       tags,
                                       tag_count);
1110 1111 1112 1113 1114 1115 1116

  g_free (tags);

  g_assert (style->refcount == 1);

  /* Leave this style as the last one seen */
  g_assert (layout->one_style_cache == NULL);
1117
  gtk_text_attributes_ref (style); /* ref held by layout->one_style_cache */
1118
  layout->one_style_cache = style;
1119

1120 1121 1122 1123 1124 1125
  /* Returning yet another refcount */
  return style;
}

static void
release_style (GtkTextLayout *layout,
1126
               GtkTextAttributes *style)
1127 1128 1129 1130
{
  g_return_if_fail (style != NULL);
  g_return_if_fail (style->refcount > 0);

1131
  gtk_text_attributes_unref (style);
1132 1133 1134 1135 1136 1137 1138 1139 1140 1141
}

/*
 * Lines
 */

/* This function tries to optimize the case where a line
   is completely invisible */
static gboolean
totally_invisible_line (GtkTextLayout *layout,
1142 1143
                        GtkTextLine   *line,
                        GtkTextIter   *iter)
1144 1145 1146
{
  GtkTextLineSegment *seg;
  int bytes = 0;
1147

1148 1149 1150 1151
  /* Check if the first char is visible, if so we are partially visible.  
   * Note that we have to check this since we don't know the current 
   * invisible/noninvisible toggle state; this function can use the whole btree 
   * to get it right.
1152
   */
1153 1154 1155 1156
  _gtk_text_btree_get_iter_at_line (_gtk_text_buffer_get_btree (layout->buffer),
				    iter, line, 0);
  
  if (!_gtk_text_btree_char_is_invisible (iter))
1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167
    return FALSE;

  bytes = 0;
  seg = line->segments;

  while (seg != NULL)
    {
      if (seg->byte_count > 0)
        bytes += seg->byte_count;

      /* Note that these two tests can cause us to bail out
<