gtktextsegment.c 18.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*
 * gtktextsegment.c --
 *
 * Code for segments in general, and toggle/char segments in particular.
 *
 * Copyright (c) 1992-1994 The Regents of the University of California.
 * Copyright (c) 1994-1995 Sun Microsystems, Inc.
 * Copyright (c) 2000      Red Hat, Inc.
 * Tk -> Gtk port by Havoc Pennington <hp@redhat.com>
 *
 * 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.
15
 *
16 17 18 19 20 21 22 23 24
 * 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.
25
 *
26 27 28 29 30
 * 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.
31
 *
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
 * 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.
50
 *
51 52
 */

53
#define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API
54
#include "config.h"
55 56 57 58 59 60 61 62 63
#include "gtktextbtree.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "gtktexttag.h"
#include "gtktexttagtable.h"
#include "gtktextlayout.h"
#include "gtktextiterprivate.h"
#include "gtkdebug.h"
64
#include "gtkalias.h"
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94

/*
 *--------------------------------------------------------------
 *
 * split_segment --
 *
 *      This procedure is called before adding or deleting
 *      segments.  It does three things: (a) it finds the segment
 *      containing iter;  (b) if there are several such
 *      segments (because some segments have zero length) then
 *      it picks the first segment that does not have left
 *      gravity;  (c) if the index refers to the middle of
 *      a segment then it splits the segment so that the
 *      index now refers to the beginning of a segment.
 *
 * Results:
 *      The return value is a pointer to the segment just
 *      before the segment corresponding to iter (as
 *      described above).  If the segment corresponding to
 *      iter is the first in its line then the return
 *      value is NULL.
 *
 * Side effects:
 *      The segment referred to by iter is split unless
 *      iter refers to its first character.
 *
 *--------------------------------------------------------------
 */

GtkTextLineSegment*
95
gtk_text_line_segment_split (const GtkTextIter *iter)
96 97 98 99 100 101
{
  GtkTextLineSegment *prev, *seg;
  GtkTextBTree *tree;
  GtkTextLine *line;
  int count;

102 103
  line = _gtk_text_iter_get_text_line (iter);
  tree = _gtk_text_iter_get_btree (iter);
104 105 106

  count = gtk_text_iter_get_line_index (iter);

107
  if (gtk_debug_flags & GTK_DEBUG_TEXT)
108
    _gtk_text_iter_check (iter);
109
  
110 111 112 113 114 115 116 117 118 119 120 121 122
  prev = NULL;
  seg = line->segments;

  while (seg != NULL)
    {
      if (seg->byte_count > count)
        {
          if (count == 0)
            {
              return prev;
            }
          else
            {
123 124 125
              g_assert (count != seg->byte_count);
              g_assert (seg->byte_count > 0);

126
              _gtk_text_btree_segments_changed (tree);
127

128
              seg = (*seg->type->splitFunc)(seg, count);
129

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
              if (prev == NULL)
                line->segments = seg;
              else
                prev->next = seg;

              return seg;
            }
        }
      else if ((seg->byte_count == 0) && (count == 0)
               && !seg->type->leftGravity)
        {
          return prev;
        }

      count -= seg->byte_count;
      prev = seg;
      seg = seg->next;
    }
148
  g_error ("split_segment reached end of line!");
149 150 151 152 153 154 155 156
  return NULL;
}


/*
 * Macros that determine how much space to allocate for new segments:
 */

157
#define CSEG_SIZE(chars) ((unsigned) (G_STRUCT_OFFSET (GtkTextLineSegment, body) \
158
        + 1 + (chars)))
159 160
#define TSEG_SIZE ((unsigned) (G_STRUCT_OFFSET (GtkTextLineSegment, body) \
        + sizeof (GtkTextToggleBody)))
161 162 163 164 165 166

/*
 * Type functions
 */

static void
167
char_segment_self_check (GtkTextLineSegment *seg)
168 169 170 171
{
  /* This function checks the segment itself, but doesn't
     assume the segment has been validly inserted into
     the btree. */
172 173 174

  g_assert (seg != NULL);

175 176
  if (seg->byte_count <= 0)
    {
177
      g_error ("segment has size <= 0");
178 179
    }

180
  if (strlen (seg->body.chars) != seg->byte_count)
181
    {
182
      g_error ("segment has wrong size");
183 184
    }

185
  if (g_utf8_strlen (seg->body.chars, seg->byte_count) != seg->char_count)
186
    {
187
      g_error ("char segment has wrong character count");
188 189 190 191
    }
}

GtkTextLineSegment*
192
_gtk_char_segment_new (const gchar *text, guint len)
193 194 195
{
  GtkTextLineSegment *seg;

196 197 198
  g_assert (gtk_text_byte_begins_utf8_char (text));

  seg = g_malloc (CSEG_SIZE (len));
Matthias Clasen's avatar
Matthias Clasen committed
199
  seg->type = (GtkTextLineSegmentClass *)&gtk_text_char_type;
200 201
  seg->next = NULL;
  seg->byte_count = len;
202
  memcpy (seg->body.chars, text, len);
203 204
  seg->body.chars[len] = '\0';

205
  seg->char_count = g_utf8_strlen (seg->body.chars, seg->byte_count);
206 207

  if (gtk_debug_flags & GTK_DEBUG_TEXT)
208 209
    char_segment_self_check (seg);

210 211 212 213
  return seg;
}

GtkTextLineSegment*
214 215 216 217 218 219
_gtk_char_segment_new_from_two_strings (const gchar *text1, 
					guint        len1, 
					guint        chars1,
                                        const gchar *text2, 
					guint        len2, 
					guint        chars2)
220 221 222
{
  GtkTextLineSegment *seg;

223 224 225 226
  g_assert (gtk_text_byte_begins_utf8_char (text1));
  g_assert (gtk_text_byte_begins_utf8_char (text2));

  seg = g_malloc (CSEG_SIZE (len1+len2));
227 228 229
  seg->type = &gtk_text_char_type;
  seg->next = NULL;
  seg->byte_count = len1 + len2;
230 231
  memcpy (seg->body.chars, text1, len1);
  memcpy (seg->body.chars + len1, text2, len2);
232 233
  seg->body.chars[len1+len2] = '\0';

234
  seg->char_count = chars1 + chars2;
235 236

  if (gtk_debug_flags & GTK_DEBUG_TEXT)
237 238
    char_segment_self_check (seg);

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
  return seg;
}

/*
 *--------------------------------------------------------------
 *
 * char_segment_split_func --
 *
 *      This procedure implements splitting for character segments.
 *
 * Results:
 *      The return value is a pointer to a chain of two segments
 *      that have the same characters as segPtr except split
 *      among the two segments.
 *
 * Side effects:
 *      Storage for segPtr is freed.
 *
 *--------------------------------------------------------------
 */

static GtkTextLineSegment *
261
char_segment_split_func (GtkTextLineSegment *seg, int index)
262 263 264
{
  GtkTextLineSegment *new1, *new2;

265 266
  g_assert (index < seg->byte_count);

267 268
  if (gtk_debug_flags & GTK_DEBUG_TEXT)
    {
269
      char_segment_self_check (seg);
270
    }
271

Havoc Pennington's avatar
Havoc Pennington committed
272 273
  new1 = _gtk_char_segment_new (seg->body.chars, index);
  new2 = _gtk_char_segment_new (seg->body.chars + index, seg->byte_count - index);
274

275 276 277 278 279
  g_assert (gtk_text_byte_begins_utf8_char (new1->body.chars));
  g_assert (gtk_text_byte_begins_utf8_char (new2->body.chars));
  g_assert (new1->byte_count + new2->byte_count == seg->byte_count);
  g_assert (new1->char_count + new2->char_count == seg->char_count);

280 281 282 283 284
  new1->next = new2;
  new2->next = seg->next;

  if (gtk_debug_flags & GTK_DEBUG_TEXT)
    {
285 286
      char_segment_self_check (new1);
      char_segment_self_check (new2);
287
    }
288 289

  g_free (seg);
290 291 292 293 294 295 296 297 298 299 300
  return new1;
}

/*
 *--------------------------------------------------------------
 *
 * char_segment_cleanup_func --
 *
 *      This procedure merges adjacent character segments into
 *      a single character segment, if possible.
 *
301 302 303 304 305
 * Arguments:
 *      segPtr: Pointer to the first of two adjacent segments to
 *              join.
 *      line:   Line containing segments (not used).
 *
306 307 308 309 310 311 312 313 314 315 316 317
 * Results:
 *      The return value is a pointer to the first segment in
 *      the (new) list of segments that used to start with segPtr.
 *
 * Side effects:
 *      Storage for the segments may be allocated and freed.
 *
 *--------------------------------------------------------------
 */

        /* ARGSUSED */
static GtkTextLineSegment *
318
char_segment_cleanup_func (GtkTextLineSegment *segPtr, GtkTextLine *line)
319 320 321 322
{
  GtkTextLineSegment *segPtr2, *newPtr;

  if (gtk_debug_flags & GTK_DEBUG_TEXT)
323 324
    char_segment_self_check (segPtr);

325 326 327 328 329 330
  segPtr2 = segPtr->next;
  if ((segPtr2 == NULL) || (segPtr2->type != &gtk_text_char_type))
    {
      return segPtr;
    }

Havoc Pennington's avatar
Havoc Pennington committed
331
  newPtr =
332 333 334 335 336 337
    _gtk_char_segment_new_from_two_strings (segPtr->body.chars, 
					    segPtr->byte_count,
					    segPtr->char_count,
                                            segPtr2->body.chars, 
					    segPtr2->byte_count,
					    segPtr2->char_count);
338 339 340 341

  newPtr->next = segPtr2->next;

  if (gtk_debug_flags & GTK_DEBUG_TEXT)
342 343 344 345
    char_segment_self_check (newPtr);

  g_free (segPtr);
  g_free (segPtr2);
346 347 348 349 350 351 352 353 354 355
  return newPtr;
}

/*
 *--------------------------------------------------------------
 *
 * char_segment_delete_func --
 *
 *      This procedure is invoked to delete a character segment.
 *
356 357 358 359 360 361
 * Arguments:
 *      segPtr   : Segment to delete
 *      line     : Line containing segment
 *      treeGone : Non-zero means the entire tree is being
 *                 deleted, so everything must get cleaned up.
 *
362 363 364 365 366 367 368 369 370 371 372
 * Results:
 *      Always returns 0 to indicate that the segment was deleted.
 *
 * Side effects:
 *      Storage for the segment is freed.
 *
 *--------------------------------------------------------------
 */

        /* ARGSUSED */
static int
373
char_segment_delete_func (GtkTextLineSegment *segPtr, GtkTextLine *line, int treeGone)
374
{
375
  g_free ((char*) segPtr);
376 377 378 379 380 381 382 383 384 385 386
  return 0;
}

/*
 *--------------------------------------------------------------
 *
 * char_segment_check_func --
 *
 *      This procedure is invoked to perform consistency checks
 *      on character segments.
 *
387 388 389 390
 * Arguments:
 *      segPtr : Segment to check
 *      line   : Line containing segment
 *
391 392 393 394 395 396 397 398 399 400 401 402
 * Results:
 *      None.
 *
 * Side effects:
 *      If the segment isn't inconsistent then the procedure
 *      g_errors.
 *
 *--------------------------------------------------------------
 */

        /* ARGSUSED */
static void
403
char_segment_check_func (GtkTextLineSegment *segPtr, GtkTextLine *line)
404
{
405 406
  char_segment_self_check (segPtr);

407
  if (segPtr->next != NULL)
408 409 410
    {
      if (segPtr->next->type == &gtk_text_char_type)
        {
411
          g_error ("adjacent character segments weren't merged");
412 413 414 415 416
        }
    }
}

GtkTextLineSegment*
417
_gtk_toggle_segment_new (GtkTextTagInfo *info, gboolean on)
418 419 420
{
  GtkTextLineSegment *seg;

421
  seg = g_malloc (TSEG_SIZE);
422 423 424 425 426 427 428

  seg->type = on ? &gtk_text_toggle_on_type : &gtk_text_toggle_off_type;

  seg->next = NULL;

  seg->byte_count = 0;
  seg->char_count = 0;
429

430 431 432 433 434 435 436 437 438 439 440 441 442
  seg->body.toggle.info = info;
  seg->body.toggle.inNodeCounts = 0;

  return seg;
}

/*
 *--------------------------------------------------------------
 *
 * toggle_segment_delete_func --
 *
 *      This procedure is invoked to delete toggle segments.
 *
443 444 445 446 447 448
 * Arguments:
 *      segPtr   : Segment to check
 *      line     : Line containing segment
 *      treeGone : Non-zero means the entire tree is being
 *                 deleted so everything must get cleaned up
 *
449 450 451 452 453 454 455 456 457 458 459 460 461
 * Results:
 *      Returns 1 to indicate that the segment may not be deleted,
 *      unless the entire B-tree is going away.
 *
 * Side effects:
 *      If the tree is going away then the toggle's memory is
 *      freed;  otherwise the toggle counts in GtkTextBTreeNodes above the
 *      segment get updated.
 *
 *--------------------------------------------------------------
 */

static int
462
toggle_segment_delete_func (GtkTextLineSegment *segPtr, GtkTextLine *line, int treeGone)
463 464 465
{
  if (treeGone)
    {
466
      g_free ((char *) segPtr);
467 468 469 470 471 472 473 474 475 476 477 478 479
      return 0;
    }

  /*
   * This toggle is in the middle of a range of characters that's
   * being deleted.  Refuse to die.  We'll be moved to the end of
   * the deleted range and our cleanup procedure will be called
   * later.  Decrement GtkTextBTreeNode toggle counts here, and set a flag
   * so we'll re-increment them in the cleanup procedure.
   */

  if (segPtr->body.toggle.inNodeCounts)
    {
480 481
      _gtk_change_node_toggle_count (line->parent,
                                     segPtr->body.toggle.info, -1);
482 483 484 485 486 487 488 489 490 491 492 493 494 495
      segPtr->body.toggle.inNodeCounts = 0;
    }
  return 1;
}

/*
 *--------------------------------------------------------------
 *
 * toggle_segment_cleanup_func --
 *
 *      This procedure is called when a toggle is part of a line that's
 *      been modified in some way.  It's invoked after the
 *      modifications are complete.
 *
496 497 498 499
 * Arguments:
 *      segPtr : Segment to check
 *      line   : Line that now contains segment
 *
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
 * Results:
 *      The return value is the head segment in a new list
 *      that is to replace the tail of the line that used to
 *      start at segPtr.  This allows the procedure to delete
 *      or modify segPtr.
 *
 * Side effects:
 *      Toggle counts in the GtkTextBTreeNodes above the new line will be
 *      updated if they're not already.  Toggles may be collapsed
 *      if there are duplicate toggles at the same position.
 *
 *--------------------------------------------------------------
 */

static GtkTextLineSegment *
515
toggle_segment_cleanup_func (GtkTextLineSegment *segPtr, GtkTextLine *line)
516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544
{
  GtkTextLineSegment *segPtr2, *prevPtr;
  int counts;

  /*
   * If this is a toggle-off segment, look ahead through the next
   * segments to see if there's a toggle-on segment for the same tag
   * before any segments with non-zero size.  If so then the two
   * toggles cancel each other;  remove them both.
   */

  if (segPtr->type == &gtk_text_toggle_off_type)
    {
      for (prevPtr = segPtr, segPtr2 = prevPtr->next;
           (segPtr2 != NULL) && (segPtr2->byte_count == 0);
           prevPtr = segPtr2, segPtr2 = prevPtr->next)
        {
          if (segPtr2->type != &gtk_text_toggle_on_type)
            {
              continue;
            }
          if (segPtr2->body.toggle.info != segPtr->body.toggle.info)
            {
              continue;
            }
          counts = segPtr->body.toggle.inNodeCounts
            + segPtr2->body.toggle.inNodeCounts;
          if (counts != 0)
            {
545 546
              _gtk_change_node_toggle_count (line->parent,
                                             segPtr->body.toggle.info, -counts);
547 548
            }
          prevPtr->next = segPtr2->next;
549
          g_free ((char *) segPtr2);
550
          segPtr2 = segPtr->next;
551
          g_free ((char *) segPtr);
552 553 554 555 556 557
          return segPtr2;
        }
    }

  if (!segPtr->body.toggle.inNodeCounts)
    {
558 559
      _gtk_change_node_toggle_count (line->parent,
                                     segPtr->body.toggle.info, 1);
560 561 562 563 564 565 566 567 568 569 570 571 572
      segPtr->body.toggle.inNodeCounts = 1;
    }
  return segPtr;
}

/*
 *--------------------------------------------------------------
 *
 * toggle_segment_line_change_func --
 *
 *      This procedure is invoked when a toggle segment is about
 *      to move from one line to another.
 *
573 574 575 576
 * Arguments:
 *      segPtr : Segment to check
 *      line   : Line that used to contain segment
 *
577 578 579 580 581 582 583 584 585 586
 * Results:
 *      None.
 *
 * Side effects:
 *      Toggle counts are decremented in the GtkTextBTreeNodes above the line.
 *
 *--------------------------------------------------------------
 */

static void
587
toggle_segment_line_change_func (GtkTextLineSegment *segPtr, GtkTextLine *line)
588 589 590
{
  if (segPtr->body.toggle.inNodeCounts)
    {
Havoc Pennington's avatar
Havoc Pennington committed
591 592
      _gtk_change_node_toggle_count (line->parent,
                                     segPtr->body.toggle.info, -1);
593 594 595 596 597 598 599 600 601
      segPtr->body.toggle.inNodeCounts = 0;
    }
}

/*
 * Virtual tables
 */


Matthias Clasen's avatar
Matthias Clasen committed
602
const GtkTextLineSegmentClass gtk_text_char_type = {
603 604 605 606 607 608 609 610 611 612 613 614 615 616
  "character",                          /* name */
  0,                                            /* leftGravity */
  char_segment_split_func,                              /* splitFunc */
  char_segment_delete_func,                             /* deleteFunc */
  char_segment_cleanup_func,                            /* cleanupFunc */
  NULL,         /* lineChangeFunc */
  char_segment_check_func                               /* checkFunc */
};

/*
 * Type record for segments marking the beginning of a tagged
 * range:
 */

Matthias Clasen's avatar
Matthias Clasen committed
617
const GtkTextLineSegmentClass gtk_text_toggle_on_type = {
618 619 620 621
  "toggleOn",                                   /* name */
  0,                                            /* leftGravity */
  NULL,                 /* splitFunc */
  toggle_segment_delete_func,                           /* deleteFunc */
622 623 624
  toggle_segment_cleanup_func,                          /* cleanupFunc */
  toggle_segment_line_change_func,                      /* lineChangeFunc */
  _gtk_toggle_segment_check_func                        /* checkFunc */
625 626 627 628 629 630 631
};

/*
 * Type record for segments marking the end of a tagged
 * range:
 */

Matthias Clasen's avatar
Matthias Clasen committed
632
const GtkTextLineSegmentClass gtk_text_toggle_off_type = {
633 634 635 636 637 638
  "toggleOff",                          /* name */
  1,                                            /* leftGravity */
  NULL,                 /* splitFunc */
  toggle_segment_delete_func,                           /* deleteFunc */
  toggle_segment_cleanup_func,                          /* cleanupFunc */
  toggle_segment_line_change_func,                      /* lineChangeFunc */
Havoc Pennington's avatar
Havoc Pennington committed
639
  _gtk_toggle_segment_check_func                        /* checkFunc */
640
};
641 642 643

#define __GTK_TEXT_SEGMENT_C__
#include "gtkaliasdef.c"